JavaScript
中的原型和原型鏈問題,一直是困擾新手乃至於廣大前端工程師的重要問題,的確,相對於普通語法來講,它會更加難以理解,在平常開發過程當中也不常見。可是……它的重要性是不言而喻的。下面咱們就來探究一下。javascript
進入主題前端
要理解原型問題,先要了解如下三個屬性。java
prototype
__protp__
constructor
進入逐一講解階段:面試
JavaScript
中的每一個構造函數都有prototype
對象。全部實例對象須要共享的屬性和方法都放在這個對象裏。而那些不須要共享的,就放在構造函數中。使用過程當中,不須要咱們手動聲明一個prototype
屬性。有了它,咱們就能夠將全部須要共享的方法提取到一處,避免冗餘。經過一個小栗子來看下前端工程師
// 定義一個動物的構造函數。 function Animal(name) { // 構造函數中存放的是不須要共享的方法和屬性 this.name = name; // 定義每一個動物的名字 } // 將每一個動物須要共享的方法和屬性放到一塊 Animal.prototype.eat = function () { console.log('吃東西') } // 輸出本身的名字 Animal.prototype.sayName = function () { console.log(this.name) } let dog = new Animal('狗'); // 實例化一個狗的對象 let cat = new Animal('貓'); // 實例化一個貓的對象 dog.sayName(); // 狗 cat.sayName(); // 貓 dog.eat(); cat.eat();
這裏,咱們經過Animal
實例化的dog
和cat
類,都沒有聲明sayName
函數,可是它們都有這個函數能夠執行。而實例對象一旦建立,將自動引用prototype對象的屬性和方法。函數
相信你已經get到了這個知識點,別急,繼續看下面這種狀況。this
// 再次定義Animal。 function Animal() {} Animal.prototype.eat = function () { console.log('吃東西') } let dog = new Animal(); dog.eat = function () { console.log('我只吃骨頭!') } dog.eat(); // 我只吃骨頭
注意:prototype
上述例子說明:只有在dog
和cat
上找不到eat
方法的時候纔會向Animal
查找,若是能查找到,則不會執行Animal
的方法。這種狀況也叫作方法的重寫。code
上面的例子相信你已經看的很明白了,不明白也問題不大,下面的講解咱們還會經過實際的問題來介紹。接下來看第二個重要的屬性。對象
看的累了就休息會兒吧,下面的內容更精彩…………
__proto__
不少人容易將 prototype
與 __proto__
(雙下劃線) 混淆,由於它們之間的指向有點兒繞。不過,一通則百通😝,等你真正理解了,就會發現,其實挺簡單的。
在不少狀況下,__proto__
能夠理解爲 構造器的原型,這是爲何呢?接着看小栗子吧。
function Animal () {} let dog = new Animal(); // 重點來了………… console.log(dog.__proto__ === Animal.prototype) // true
能夠看到,dog.__proto__
和Animal.prototype
是徹底相等的。好了,基本使用看完了,咱們來探究下__proto__
的指向問題吧。
__proto__
的指向問題可能你們不太理解,爲何__proto__
還有指向。這裏須要作個說明,經過不一樣方式建立出來的對象,它的__proto__
指向是不一樣的。
這個你們比較容易理解,平常工做中咱們也是使用的比較多。
let dog = {} console.log(dog.__proto__) // Object {} --> Object的原型 // 驗證是否相等 console.log(dog.__proto__ === Object.prototype) // true
這裏由於直接給dog
賦值爲了全局的對象,因此__proto__
指向了Object.prototype
😎。
話很少說,直接上栗子。
function Animal() {} let dog = new Animal() console.log(dog.__proto__) // A {} --> A的原型 // 驗證 console.log(dog.__proto__ === Animal.prototype) // true
這是由於dog
是Animal
的一個實例,因此dog
的__proto__
指向了Animal.prototype
。
let Animal = { c: 1 // 爲了區分,咱們這裏加一個自定義屬性。 } let dog = Object.create(Animal) console.log(dog.__proto__) // Animal {c: 1} //驗證 console.log(dog.__proto__ === Animal) // true
注意:
Object.create()
方法建立一個新對象,使用現有的對象來提供新建立的對象的__proto__
。也就是說,這裏直接將Animal
做爲了dog
的__proto__
。
這個屬性沒必要多說,指的就是構造函數,每一個對象都會有一個constructor
屬性來指向本身的構造函數
function Animal() {} // 繼續使用這個使用了不少次的Animal構造函數 console.log(Animal === Animal.prototype.constructor) // true
這裏的Animal.prototype.constructor
就指向了 Animal
函數。
重點來了👇👇👇👇👇👇👇
__proto__、prototype和constructor
的關係關係太複雜,只能經過圖解來講明瞭。
<center><img src="./1.jpeg"></center>
知道了三者的概念,瞭解了它們的用途,接下來想弄懂原型鏈就會變得簡單了。原型鏈就是無數個上圖的串聯。咱們仍是經過圖解的方式來看。
<center><img src="./5.jpeg"></center>
圖太長了,不過千萬別被圖嚇到喲。原型鏈就是從實例對象開始,經過 __proto__
來向上查找,直到找到null
爲止。
看到這裏,你們可能會有一個疑問。每一個構造函數都有 prototype
對象, 那麼這個對象到底指向到了哪兒?
其實,每一個prototype
對象的最終指向都是Object.prototype
。爲何這麼說,是由於每一個構造函數在建立的時候會分配一起空間來存儲須要共享的方法和屬性,因此 prototype
是經過對象建立來的,那麼它的最終指向確定也是Object.prototype
。老規矩,咱們來經過栗子來驗證一下。
function dog(){} function cat(){} function monkey(){} console.log(dog.prototype.__proto__ === Object.prototype) // true console.log(cat.prototype.__proto__ === Object.prototype) // true console.log(monkey.prototype.__proto__ === Object.prototype) // true
function F() {} Object.prototype.a = function () {} Function.prototype.b = function () {} var f = new F() F.a() F.b() f.a() f.b()
解析:
F.a F.b f.a
執行沒問題,由於即便在構造函數總找不到這兩個函數,可是經過原型鏈查找到。
F.a
函數的查找順序是 F構造函數自己 --> Function.prototype --> Function.prototype.__proto__
F.b
函數的查找順序是 F構造函數自己 --> Function.prototype
f.a
函數的查找順序是 f自己 --> f._proto__(Function.prototype) --> f.__proto__._proto__(Object.prototype)
而 f.b
則會出現執行出錯的狀況
緣由是這樣的:
查找順序和f.a
相同,可是,在f.__proto__._proto__
,也就是Object.prototype
上查找不到 b
函數,最終致使查找失敗,出現錯誤。
聰明的你確定學會了,接下來挑戰一下吧!!!
function User() {} User.prototype.hello = function () {} let u1 = new User() let u2 = new User() console.log(u1.hello === u2.hello) console.log(User.prototype.constructor) console.log(User.prototype === Function.prototype) console.log(User.__proto__ === Function.prototype) console.log(User.prototype === Function.__proto__) console.log(u1.__proto__ === u2.__proto__) console.log(u1.__proto__ === User.__proto__) console.log(Function.__proto__ === Object.__proto__) console.log(Function.prototype.__proto__ === Object.prototype.__proto__) console.log(Function.prototype.__proto__ === Object.prototype)