區(qū)分JS中的

2018-06-09 17:58 更新

我們知道javascript是基于原型(prototype)繼承的語言。關(guān)于javascript原型繼承的更多內(nèi)容請參閱之前的這篇文章淺談Javascript中的原型繼承,內(nèi)容絕對不會讓你失望。

不過本篇文章將會詳細闡述javascript中用于原型繼承的兩個重要的東西__proto__prototype,以及由它們延伸出的一點容易讓人疑惑的方面。

__proto__prototype的概念

prototype就是原型的意思,就是函數(shù)(構(gòu)造器)的一個屬性,每個函數(shù)都將會有一個prototype屬性。此屬性是一個引用類型(指針),它指向函數(shù)的原型集對象。我們一般可以通過prototype修改函數(shù)的原型屬性。而__proto__是一個對象(可以是某個構(gòu)造器的實例)的屬性,它在對象(實例)被創(chuàng)建時跟隨被創(chuàng)建,它也是一個引用類型(指針),指向函數(shù)(構(gòu)造器)的prototype屬性。

兩者的關(guān)系如下,

function Foo() {
}
var foo = new Foo();
console.log(foo.__proto__ === Foo.prototype); // true

額外提一點,__proto__屬性目前在IE瀏覽器中貌似不能直接訪問,不過在Chrome和Firefox中是可以直接訪問的。

new的過程

如下代碼,

function Foo() {
}
var foo = new Foo();

我們使用new創(chuàng)建了構(gòu)造器Foo的一個實例foo。那么這個過程究竟是怎么樣的呢?它將分為以下幾步,

  • 創(chuàng)建一個對象foo,并且foo = {}
  • 將構(gòu)造器的原型鏈到foo__proto__上,即foo.__proto__ = Foo.prototype。這一步至關(guān)重要,為實例能夠訪問構(gòu)造器原型方法原型鏈查找等操作做好了鋪墊。
  • 執(zhí)行構(gòu)造器Foo,并將對象foo綁定到其上下文環(huán)境中,即Foo.call(foo)。其實就是將Foo中的this指針指向foo。

示例

第一個示例

下面讓我們來看個完整的例子來探索一下prototype__proto屬性的相互關(guān)系,

function Animal() {
    this.eats = true;
}
var cat = new Animal();
Animal.prototype.jumps = true;
console.log(cat.eats); // true;
console.log(cat.jumps); // true
// change constructor's prototype
Animal.prototype = {
    bark: true
};
var dog = new Animal();
console.log(cat.bark); // undefined
console.log(dog.bark); // true
console.log(cat.__proto === Animal.prototype); // false;
console.log(dog.__proto === Animal.prototype); // true;

下面是一張關(guān)于上述代碼中Animal(構(gòu)造器)及兩個實例(catdog)的原型關(guān)系圖,

從圖中我們可以看出,

  • catdogAnimal構(gòu)造出的實例
  • cat__proto__屬性指向一個對象,此對象是Animal改變prototype之的指向。
  • dog__proto__屬性指向一個對象,此對象是Animal改變prototype之的指向。和Animal.prototype的指向一致。
  • 通過cat或者dog__proto__屬性,經(jīng)過層層查找,最終的都會指向同一個對象,即Object.prototype,而Object.prototype__proto__指向null,此時原型鏈已經(jīng)到頂。

第二個示例

下面我們再來看個例子來探索一下javascript是如何在其原型鏈上進行屬性查找的,

function Person() {
    this.name = 'gejiawen';
    this.secret = function() {
        console.log('I am a secret!');
    };
};
Person.prototype.sayName = function() {
    console.log('My name is ', this.name);
};
Person.prototype.secret = function() {
    console.log('I am a secret on prototype!');
};
var p = new Person();
console.log(p.name); // gejiawen
console.log(p.secret()); // I am a secret!
console.log(p.sayName()); // My name is gejiawen

下面是實例對象p在Chrome console中的打印,

所以,各個打印的解釋如下,

  • p.name,直接在對象p中找到了,將其打印出來。
  • p.secret,在對象p中也直接找到了,執(zhí)行相應(yīng)方法。
  • p.sayName,在對象p中并未找到相關(guān)定義,此時將會開始檢索p.__proto__中對象,發(fā)現(xiàn)了sayName的定義,此時就終止檢索,執(zhí)行相應(yīng)函數(shù)。

有兩點需要額外提出來,

  1. 我們可以看到在構(gòu)造器和prototype中定義了一個同名方法secret,從上述代碼中可以看出,是不會執(zhí)行prototype中的secret函數(shù)。因為javascript在示例對象p自身的定義找到secret時就終止檢索了。
  2. 針對上面的第三點解釋,如果我們在p.__proto__仍然未找到sayName的相關(guān)定義,那么javascript會繼續(xù)向上檢索,繼續(xù)檢查p.__proto__.__proto__,如此反復,直至到Object.prototype。

示例總結(jié)

通過上面的示例,我們可以看出,示例對象的__proto__在javascript的原型繼承模型中扮演著不可或缺的角色。

可以說__proto__就是將一個個的對象串成一條完整原型鏈的粘合器。

個人覺得prototype相對于__proto__更加像是一個開放的接口,而__proto__更傾向是原型鏈模型的內(nèi)部實現(xiàn)。我們在平時進行javascript開發(fā)時,可以在適當?shù)臅r候應(yīng)用一些prototype的知識來提高代碼質(zhì)量。

一些額外的問題

Object.create方法

Object.create是ES5中引入的方法,

Object.create(proto[, propertiesObject])

MDN上給出的定義如下,

The Object.create() method creates a new object with the specified prototype object and properties.

意思就是我們可以在創(chuàng)建對象時,可以自定義其原型。(需要注意的是,當給Object.create傳入的參數(shù)不是一個對象或者null時,它將會拋出一個錯誤)

看下面的代碼,

var animal = {
    eats: true
};
var rabbit = Object.create(animal);
console.log(rabbit.eats); // true

此時,rabbit.__proto__上將會有eats屬性,如關(guān)系如下圖,

請注意各個對象的__proto__之間的關(guān)系。

instanceof背后的邏輯

我們知道instanceof可以判斷某個實例對象是否是某個構(gòu)造器的實例。那么instanceof判斷的依據(jù)是什么呢?

function A() {
}
var a = new A();
console.log(a instanceof A); // true

instanceof的過程如下,

  1. 對比a.__proto__A.prototype
  2. 若第一步的返回為true,則表示a即為A的實例。
  3. 若第一步的返回為false,此時執(zhí)行類似這樣的操作a = a.__proto__,然后重復第一步。

其實說白了,instanceof的實質(zhì)就是比較__proto__prototype,我們來看個示例來看下是否能說明這個問題,

function Animal() {
   this.name = 'wangcai';
}
function Dog() {
}
Dog.prototype =  new Animal();
var dog = new Dog();
console.log(dog.name); // 'wangcai'
console.log(dog instanceof Animal); // true
// change the prototype of Class
Dog.prototype = {
  name: 'yingcai'
};
console.log(dog instanceof Dog); // false
console.log(dog instanceof Animal); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true

這個例子中,我們中間改變了Dogprototype,此時造成的后果就是破壞了原先dog對象的__proto__Dog.prototype的引用關(guān)系,從而dog instanceof Dog返回的結(jié)果為false

參考列表



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號