簡話 prototype 和 __proto__

在js中,prototype和__proto__是很難理解的兩個知識點,一是它倆名字差很少,容易混淆;二是在工做中能直接用到這兩個知識點的地方可能也不是特別多,可是這兩個知識點倒是很是重要的,尤爲在實現繼承方面。函數

1、理解這倆名字

在我學習它倆的時候,最大的障礙就是名字,第一印象就感受它倆是一個東西。若是以這種印象做爲前提的話,就很容易走入一個迷宮。因此怎麼理解prototype和__proto__呢?學習

我我的理解的是:prototype指的是擴展; __proto__指的鏈子(原型鏈),因此我以爲若是用__chain__來替代__proto__能更好的理解這個鏈子,或者把proto兩邊的下劃線想象做鏈條,看到鏈條就想起原型鏈。ui

2、鏈子__proto__鏈到了哪

先來個例子:this

function Dog(age){
    this.age = age;
}
Dog.prototype.name = "狗";

var dog = new Dog(3);
複製代碼

此時dog是Dog的一個實例,咱們能夠打印下dog看看: spa

發現dog私有屬性只有age,打印下dog.name也能獲得"狗"。這是爲何呢?dog除了私有屬性age外,還有個__proto__,點開dog的__proto__看一下:
發現dog的__proto__中有了name這個屬性,那打印dog.name是否是就返回的這個name呢?

實際就是返回的這個name,js中訪問一個對象的屬性,會遵循一個規則:先在這個對象自己找私有屬性,若是找到了就返回,若是找不到就沿着原型鏈往上找,直到最頂層null還找不到就會返回undefinedprototype

那dog.__proto__爲何會有name這個屬性呢?這是由於在構建dog實例的時候,會把dog的__proto__指向它的構造函數的prototype,簡單說就是實例的鏈子會鏈到構造函數的擴展上,驗證一下:3d

console.log(dog.__proto__ === Dog.prototype) //true
複製代碼

一個實例的鏈子默認會往上連接到它的構造函數的擴展,而且能訪問這個擴展上面的屬性,除非手動改變這個鏈子連接的擴展code

function Dog(age){
    this.age = age;
}
Dog.prototype.name = "狗";

function Cat(){
    this.type = "cat";
}
Cat.prototype.say = "miao~";

var dog = new Dog(3);
//手動改變了dog的鏈子
dog.__proto__ = Cat.prototype;
console.log(dog.name); //undefined
console.log(dog.say); //miao~
console.log(dog.age); //3
console.log(dog.type); //undefined

//手動改變__proto__指向Cat.prototype 還會出現下面的狀況
console.log(dog instanceof Dog); //false
console.log(dog instanceof Cat); //true
複製代碼

雖然把dog的鏈子鏈到了Cat的擴展上了,可是打印了dog.type仍是undefined,這說明屬性只能在鏈子所鏈的擴展上找,跟是誰的擴展沒有很大的關聯。這種狀況很相似使用Object.create()來生成的實例:cdn

function Cat(){
    this.type = "cat";
}
Cat.prototype.say = "miao~";
var dog = Object.create(Cat.prototype, {
    age:{
        value:10
    }
})
console.log(dog.age); //10
console.log(dog.say); //miao~
conosle.log(dog.type); //undefined

//生成的實例的鏈子指向了 Object.create傳入的第一個參數
console.log(dog.__proto__ === Cat.prototype); //true
複製代碼

3、誰具備默認的prototype

js只有函數默認擁有prototype屬性,由構造函數構造出來的實例默認是不具備擴展的,除非手動給這個實例加上擴展:對象

function Dog(age){
    this.age = age;
}
Dog.prototype.name = "狗";

var dog = new Dog(3);
console.log(dog.prototype) //undefined
//給dog添加一個叫prototype的屬性
dog.prototype = {
    say:"wangwang~";
}
複製代碼

手動給實例添加prototype,並無什麼實際意義,就只是給一個對象添加一個屬性,如上給dog添加了prototype,跟如下相似:

dog = {
    age:3,
    prototype:{
        say:"wangwang~"
    },
    prototype2:{
        say:"wangwangwang~"
    }
}
複製代碼

4、prototype裏面都有啥

以一個普通的函數爲例,函數的prototype首先會有constructor屬性,constructor指向了這個函數自己:

function test(a) {
    console.log(a)
}

console.log(test.prototype.constructor === test); //true
複製代碼

還會有__proto__屬性,爲了方便咱們把函數的prototype稱爲fnPrototype,fnPrototypey也是個實例對象,它也會有構造函數,所以它的鏈子也會鏈到了構造它的函數的擴展

其實fnPrototypey是Object的一個實例,它的鏈子鏈到Object的擴展上,咱們能夠驗證下:

function test(a) {
    console.log(a)
}

var fnPrototype = test.prototype;
console.log(fnPrototype.__proto__ === Object.prototype); //true
複製代碼

咱們平時用到prototype比較多的狀況會有:給Array、String、Function或者Object等js的源生構造器重寫或者添加擴展方法和屬性:

Array.prototype.replaceAll = function(){
    //code...
}
String.prototype.trim = function(){
    //code...
}
Function.prototype.bindFn = function(){
    //code...
}
Object.prototype.__type = "object";
複製代碼

這些js源生的構造器也會有prototype屬性,每一個構造器會有本身的一些對應方法,他們構造出來的實例的鏈子會鏈到這些擴展上,所以實例就能使用這些方法和屬性。 Function.prototype是個比較特殊的狀況,它實際是一個源生封裝的函數,可是實現了__proto__連接到了Object.prototype,Object.prototype.__proto__指向了null,因此說js中的一切皆對象(雖然Object也是一個函數,這裏就不糾結究竟是函數層級高仍是對象層級高了,就認爲Object層級是最高的吧):

console.log(Function.prototype.__proto__ === Object.prototype); //true
複製代碼

5、總結

簡單總結一下,實例的鏈子會連接到構造函數的擴展。訪問實例的屬性,先在自己找,找不到就沿着鏈子往上找鏈的擴展,再在這個擴展上找,還找不到就沿着這個擴展鏈子再往上找。

相關文章
相關標籤/搜索