區(qū)分JS中的

2018-06-09 17:58 更新

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

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

__proto__prototype的概念

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

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

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

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

new的過程

如下代碼,

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

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

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

示例

第一個(gè)示例

下面讓我們來看個(gè)完整的例子來探索一下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)造器)及兩個(gè)實(shí)例(catdog)的原型關(guān)系圖,

從圖中我們可以看出,

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

第二個(gè)示例

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

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

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

所以,各個(gè)打印的解釋如下,

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

有兩點(diǎn)需要額外提出來,

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

示例總結(jié)

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

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

個(gè)人覺得prototype相對于__proto__更加像是一個(gè)開放的接口,而__proto__更傾向是原型鏈模型的內(nèi)部實(shí)現(xiàn)。我們在平時(shí)進(jìn)行javascript開發(fā)時(shí),可以在適當(dāng)?shù)臅r(shí)候應(yīng)用一些prototype的知識(shí)來提高代碼質(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)建對象時(shí),可以自定義其原型。(需要注意的是,當(dāng)給Object.create傳入的參數(shù)不是一個(gè)對象或者null時(shí),它將會(huì)拋出一個(gè)錯(cuò)誤)

看下面的代碼,

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

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

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

instanceof背后的邏輯

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

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

instanceof的過程如下,

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

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

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

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

參考列表



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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)