js原型鏈詳解
在講原型之前,我們先要了解一些東西。在JavaScript語言中,萬物皆物件,物件分為兩種,函式物件和普通物件。
function f1() {}; console.log(typeof f1) //"function"
var f2 = function() {} console.log(typeof f2) // "function"
var o1 = new f1(); console.log(typeof o1) //"object"
var o2 = {}; console.log(typeof o2) //"object"
1、每一個函式物件都有一個prototype屬性,但是普通物件是沒有的;prototype下面又有個construetor,指向這個函式。
2、每個物件都有一個名為_proto_的內部屬性,指向它所對應的建構函式的原型物件,原型鏈基於_proto_;
function Fun(){}; console.log(Fun.prototype); //{constructor: ƒ} constructor:ƒ Fun() __proto__:Object
大家自己執行一下,可以看到 constructor和__proto__。
原型物件
原型物件就只是個普通物件,裡面存放著所有例項物件需要共享的屬性和方法。說到原型物件,我們先了解下建構函式是什麼?建構函式與其他函式唯一的區別在於呼叫方式不同。任何函式只要通過new來呼叫就可以作為建構函式,它是用來建立特定型別的物件。
下面定義一個建構函式Animal:
function Animal (name){
this.name = name;
this.species = '動物';
}
通過new命令來生成一個Animal例項:
var cat = new Animal ("貓")
這裡,建構函式Animal就是例項物件cat的原型。Animal裡的this關鍵字就指的是cat這個物件。new出來的cat物件此時已經和Animal再無聯絡了,也就是說每一個new出來的例項都有自己的屬性和方法的副本,是獨立的!修改其中一個不會影響另一個。
var dog = new Animal("狗");
console.log(cat.species) // 動物
console.log(dog.species) // 動物
但是,我們希望建構函式中的species屬性是一個共有屬性。上面的方法中,每個例項中都有一個相同的species屬性,會造成資源的浪費,因為每個例項中的這個共有屬性都要佔用記憶體,建立100個例項同樣也建立了100個species屬性。我們是否可以建立一次species屬性,讓所有例項化的物件都可以用呢?
那麼原型物件就即將登場了!我們把需要共享的放到原型物件裡,把那些不需要共享的屬性和方法存在建構函式裡!
那麼上面的程式碼怎麼修改呢?
function Animal(name) {
this.name = name;
}
Animal.prototype.species = '動物';
var cat = new Animal("貓");
var dog = new Animal("狗");
console.log(cat.species) // 動物
console.log(dog.species) // 動物
Animal.prototype.species = '食肉動物';
console.log(cat.species) // 食肉動物
console.log(dog.species) // 食肉動物
可以看出,修改prototype屬性會影響它的所有例項的species的值。
例項一旦創建出來就會自動引用prototype物件的屬性和方法!所以例項物件的屬性和方法一般分為兩種:一種是自身的,一種是引用自prototype的。
具體實現是這樣的:
每當程式碼讀取某個物件的某個屬性的時候,都會執行一次搜尋。首先從物件例項本身開始,如果在例項中找到了該屬性,則返回該屬性的值,如果沒有找到,則順著原型鏈指標向上,到原型物件中去找,如果找到就返回該屬性值。
原型鏈
事實上,js裡完全依靠"原型鏈"(prototype chain)模式來實現繼承。
上面說完原型物件。下面要扒一扒__proto__、prototype、constructor
__proto__:事實上就是原型鏈指標
prototype:上面說到這個是指向原型物件的
constructor:每一個原型物件都包含一個指向建構函式的指標,就是constructor
js中是如何通過原型來實現繼承的呢?來看下面的程式碼:
function AA(){ this.name="xxx"; this.age=18 } function BB(){ this.sex="man"; } BB.prototype=new AA(); //實現繼承 var gzb=new BB(); console.log(gzb.name); //xxx console.log(gzb.age); //18 console.log(gzb.sex); //man
關鍵在於BB.prototype=new AA();這行程式碼中以new的方式呼叫了AA(),使BB函式的原型成為了AA()的一個例項物件,重寫了BB函式的原型物件,實現了繼承。
如果我們也在BB函式中也新增一個name屬性,那獲取gzb.name時會輸出什麼呢?
function BB(){ this.sex="man";
this.name="zzz"; }
gzb.name //"zzz"
我們在訪問name屬性時,首先會查詢例項物件中是否存在這個屬性,如果存在直接返回,如果沒有會沿著原型鏈向上尋找,檢視原型物件中是否存在這個屬性,如果有則返回,如果沒有則返回undefined。