對(duì)象原型相信大家都這樣用過 let arr = [0, 1, 2]let doubleArr = arr.map(c => c * 2)console.log(doubleArr) // 0, 2, 4 不知道你有沒有想過, 把它打印出來看看: console.log(arr)// 0: 0// 1: 1// 2: 2// length: 3// __proto__: Array(0) 出現(xiàn)了一個(gè)名為 console.log(arr.map === arr.__proto__.map) // true 這里出現(xiàn)的 不同于 Java、C# 等基于類(Class) 的面向?qū)ο笳Z言,通過定義類、創(chuàng)建實(shí)例、指定繼承等方式來傳遞屬性及方法;Javascript 則是個(gè)基于原型(Prototype)的對(duì)語言 ,通過預(yù)先建立出的原型對(duì)象,當(dāng)新對(duì)象建立時(shí)指定對(duì)象的原型要參照哪個(gè)原型對(duì)象。 而當(dāng)我們調(diào)用對(duì)象的屬性或方法時(shí),若對(duì)象本身沒有這個(gè)屬性或方法,JavaScript 會(huì)自動(dòng)尋找它原型中的方法,這也就是為什么可以直接調(diào)用 原型鏈你可能已經(jīng)發(fā)現(xiàn)在前面的例子中, console.log(arr.__proto__) // Array 的 Prototypeconsole.log(arr.__proto__.__proto__) // Object 的 Prototypeconsole.log(arr.__proto__.__proto__.__proto__) // null 在上述機(jī)制中,每當(dāng)對(duì)象建立時(shí)都會(huì)綁定原型,既然對(duì)象都有原型,對(duì)象原型本身也是對(duì)象,自然也不例外;由這個(gè)例子中我們可以看出:
由于每個(gè)對(duì)象都有原型,這樣就形成了一個(gè)關(guān)聯(lián)一個(gè)、層層相互依賴的從屬關(guān)系,我們把它們叫做原型鏈(Prototype Chain) ;通過這種機(jī)制,讓對(duì)象得以使用原型中的屬性和方法,并憑借原型鏈一層一層的按順序繼承,讓對(duì)象能擁有原型鏈上所有原型的功能,這就是 JavaScript 對(duì)象背后的運(yùn)作機(jī)制。 補(bǔ)充:在 JavaScript 中,幾乎每個(gè)原型鏈的末端都會(huì)是 Object,并最后指向到 使用原型說了這么多,該來點(diǎn)代碼了,接下來就來練習(xí)一下原型的建立、設(shè)定及修改吧。 先來創(chuàng)建一個(gè)新的對(duì)象構(gòu)造函數(shù): function Person(name) { this.name = name}Person.prototype.hello = function () { console.log(`Hello ${this.name}.`)}let gary = new Person('Gary')gary.hello() // Hello Gary.Object.getPrototypeOf(gary) // {hello: ?, constructor: ?} 上面的例子創(chuàng)建了一個(gè)簡(jiǎn)單的對(duì)象構(gòu)造函數(shù) Q:為什么不直接用 原型繼承接著再創(chuàng)建新的對(duì)象原型,并繼承 function Engineer(name, skill) { Person.call(this, name) this.skill = skill}Engineer.prototype = Object.create(Person.prototype)Engineer.prototype.constructor = Engineerlet alice = new Engineer('Alice', 'JavaScript')alice.hello() // Hello Alice.console.log(alice.skill) // JavaScriptObject.getPrototypeOf(alice) // Person {constructor: ?} 這里建立了新的對(duì)象 Q:為什么需要重新設(shè)定 修改原型原型的引用、繼承是直接參照到原型對(duì)象上,并非是在每個(gè)對(duì)象都復(fù)制一份原型;因此可以利用這個(gè)特性,在原型上增加自定義的屬性和方法,讓所有該類型的對(duì)象都能得到新方法;許多針對(duì)舊版瀏覽器的 Polyfill 就是這樣實(shí)現(xiàn)的。 例如我們?cè)趯?Vue 項(xiàng)目的時(shí)候,可能都有做過類似的操作,把共用性高的屬性方法放到 Object.defineProperty(Vue.prototype, '$date', { value: dateTimeFormat })// 之后就可以這樣用vm.$date(dateObj) 這樣的確很方便,但也要提醒開大家,當(dāng)我們?cè)谧鲈托薷牡臅r(shí)候要特別小心。接著剛才的例子,如果嘗試對(duì) Person.prototype.hello = function () { console.log(`Bye ${this.name}.`)}gary.hello() // Bye Gary.alice.hello() // Bye Alice. 如結(jié)果所示,當(dāng)對(duì)象原型做修改時(shí),所有原型鏈上有這個(gè)原型的對(duì)象,通通都會(huì)受到影響,不管對(duì)象是在修改前還是修改后創(chuàng)建的。 建議大家除非是 Polyfill,否則應(yīng)該要極力避免對(duì)原生對(duì)象的原型進(jìn)行修改,防止造成可能的意外結(jié)果。 ES6 的 Class看完前面這一大段,是不是覺得心很累?別擔(dān)心,從 ES6 開始添加了 class Person { constructor (name){ this.name = name } // 方法會(huì)自動(dòng)放到 Person.prototype hello() { console.log(`Hello ${this.name}.`) }}class Engineer extends Person { constructor (name, skill){ super(name) // 調(diào)用 Person 的構(gòu)造函數(shù) this.skill = skill }}let alice = new Engineer('Alice', 'JavaScript')alice.hello() // Hello Alice.Object.getPrototypeOf(alice) // Person {constructor: ?} 很方便,同樣的功能,代碼的可讀性卻提高了不少,繁瑣的設(shè)定也都能交給語法自動(dòng)幫你完成。不過方便的語法背后,底層仍然是對(duì)象原型及原型鏈。 總結(jié)以上是 JavaScript 中關(guān)于對(duì)象原型的說明,希望能幫你理解對(duì)象原型,在這個(gè)什么都是對(duì)象的語言中,充分理解并掌握對(duì)象原型,是成為專業(yè)碼農(nóng)必須要突破的關(guān)卡之一。 |
|