探索JS原型鏈的起源

一、一切皆為對象

JavaScript是一個(gè)面向?qū)ο螅ㄔ蛯ο螅┱Z言,除了一些基礎(chǔ)類型,一切皆為對象,所有的對象都是函數(shù)(Function也是對象)創(chuàng)建的。

面向?qū)ο蟮睦^承等一些特性,像極了人類的傳承和繁衍。我們先解釋下一切皆為對象:都是爹養(yǎng)娘生的。對于JS原型鏈的鼻祖Object.prototype就像是人類的祖先一樣,是其他物種進(jìn)化而來的,或者是女媧捏出來的,就不是Function構(gòu)造出來的。

1.1 原型對象

原型對象是面向?qū)ο笾械囊环N。區(qū)別于面向?qū)ο?,JavaScript中的原型對象是無類型(class)的,雖然ES6中加入了class這一特性,但是這個(gè)class就只是一個(gè)語法糖,并不是一個(gè)面向?qū)ο箢惖膶?shí)現(xiàn)。JavaScript通過原型鏈這一特性來實(shí)現(xiàn)對象的繼承。

  /**
   * 通過構(gòu)造函數(shù)創(chuàng)建對象
   */
  function Obj() {}; // Obj.propotype 的構(gòu)造函數(shù)
  console.log(Obj.propotype.constructor); // function Obj
  var obj = new Obj(); // Obj.propotype 的實(shí)例對象
  console.log(obj.__proto__); // Obj.propotype

以上代碼實(shí)現(xiàn)了用Obj.propotype原型對象的構(gòu)造函數(shù)Obj創(chuàng)建了實(shí)例對象obj的一個(gè)示例,他們的關(guān)系如下圖:


Obj.propotype示意圖

解釋一下Obj.propotype示意圖:

 function Obj();  // 構(gòu)造函數(shù)
 Obj.propotype; // 原型對象
 var obj = new Obj(); // 實(shí)例對象

1.2 原型對象與面向?qū)ο蟮膮^(qū)別

一些后端開發(fā)的同學(xué)對于面向?qū)ο蟮牡谝挥∠罂赡芫褪荗bject。像Java這樣的語言中類都是繼承Object的,恰好JavaScript也有Object,大家可能先入為主的認(rèn)為JS中的Object就是Java中的Object,這種想法是錯(cuò)誤的。

非常典型的一個(gè)例子(反例)就是許多人在問:Function和Object到底是什么關(guān)系?為什么Object是Function構(gòu)造出來的?為什么Function能夠構(gòu)造出它自己?Object大兄弟,你的家庭關(guān)系有點(diǎn)亂哦。

Object.__proto__ === Function.prototype;  // true
Function.__proto__ === Function.prototype; // true

說好的實(shí)例的__proto__(隱式原型)等于對象的prototype(原型)?。徽f好的一切都是對象,函數(shù)也是對象?。粸槭裁碠bject反而是Function構(gòu)造出來的?這不是逗我玩呢?

引起這樣的誤解是因?yàn)槠綍r(shí)我們都以構(gòu)造函數(shù)的名稱來簡稱一個(gè)原型對象,其實(shí)Function和Object都只是構(gòu)造函數(shù),JavaScript的所有對象都是繼承Object.prototype的,并不是Object!不是Object!不是Object!Function和Object都是function聲明的函數(shù),所以它們是繼承Function.prototype的,是Function構(gòu)造出來的。這里就不舉人類起源的例子了,有點(diǎn)不可描述,留在第三節(jié)說。

不知道上面的反例是把你們掰直了還是掰得更彎了。。。。

舉一反三這種能力對于學(xué)習(xí)是很重要的,但是非常忌諱先入為主的思想,虛心使人進(jìn)步。

二、繼承

繼承的意思不難理解,就是子承父業(yè)或者是子承祖業(yè)。在ES6之前,JavaScript沒有明確規(guī)定繼承的概念,ES6中才使用了extentds來規(guī)范繼承的方式。

2.1 new 的實(shí)現(xiàn)原理

看了很多同行實(shí)現(xiàn)js繼承的方案,很多都是基于new和Object.create。其實(shí)在構(gòu)造函數(shù)沒有返回值的情況下,new和Ojbect.create(Obj.prototype)實(shí)現(xiàn)的效果是差不多的,都能夠繼承Obj.prototype,大家可以跑一下下面的代碼對比效果:

function Obj() {
    console.log('I am Obj constructor...');
    this.name = 'Obj';
}

// 這里的參數(shù)絕對不是Obj,如過是Obj,則是繼承了Function
var obj2 = Object.create(Obj.prototype);
var obj3 = new Obj();

我們可以看到,對比于Object.create,new不僅繼承了Obj.prototype,還調(diào)用了構(gòu)造函數(shù)Obj。由此我們可以試著推理下new的實(shí)現(xiàn):

function fnew(constructor) {
    var newObj = Object.create(constructor.prototype);  //  繼承Obj.prototype
    constructor.call(newObj);    //   調(diào)用父對象的構(gòu)造函數(shù)
    return newObj;
}
var obj4 = fnew(Obj);

我們通過fnew得到的obj4和new實(shí)現(xiàn)的obj3是一樣的!

2.2 extends 的實(shí)現(xiàn)原理

按照我們對于原型鏈的理解,new出來的實(shí)例對象是不是也是js的繼承呢?我理解的繼承是祖?zhèn)鞲?,父傳子,子傳孫。但是new出來的實(shí)例對象因?yàn)槿鄙贅?gòu)造函數(shù)而失去了再繼承和實(shí)例化的能力,就是說子不能傳孫了,這不是我想要的繼承!我要的繼承是像Function這樣繼承Object后還具備‘生育能力’的,像extends這樣的。

結(jié)合我們?nèi)祟惖男袨閬硭伎枷拢肷⒆拥糜械湍锇 ,F(xiàn)在new出來的實(shí)例對象再嫁給一個(gè)構(gòu)造函數(shù)就可以愉快的生孩子了,讓我們改造下fnew:

function fextends(protoConstructor, constructor) {
    var newObj = Object.create(protoConstructor.prototype);  //  繼承Obj.prototype
    protoConstructor.call(newObj); //  調(diào)用父對象的構(gòu)造函數(shù)
    constructor.prototype = newObj;
    constructor.prototype.constructor = constructor;
    return constructor;
}
var ObjChild = fextends(Obj, function() {});

這樣ObjChild就實(shí)現(xiàn)了Function extend Object的效果啦。那我們以此分析下ES6的class和extends其實(shí)就是類似于fextends(Object, function() {})的語法糖,class A extends B 就是類似于 var A = fextends(B, function A(){})的效果咯。

2.3 instansof原理和實(shí)現(xiàn)

MDN對instansof的解釋是:instanceof運(yùn)算符用于測試構(gòu)造函數(shù)的prototype屬性是否出現(xiàn)在對象的原型鏈中的任何位置。比如Function instanceof Object就是判斷Object.prototype是否出現(xiàn)在Function的原型鏈( __proto__ )中。

instansof示意圖
// instansof
function finstansof(a, b) {
    if(a.__proto__) {
        if(a.__proto__ === b.prototype) {
            return true;
        } else {
            return finstansof(a.__proto__, b);
        }
    }
    return false;
}

看完instansof示意圖和代碼,相信大家都掌握了instansof這一特性的原理。下次再有人跟你說A instansof B的意思是判斷A是否繼承B的這樣不嚴(yán)謹(jǐn)?shù)闹囌?,你告訴我,我給他寄一盒的刀片。

下面再給大家舉一個(gè)反例:世界上是先有Object還是先有Function?

Function instanceof Object //  true
Object instanceof Function // true

毋庸置疑,F(xiàn)unction(Function.prototype的構(gòu)造函數(shù))和Object(Object.prototype的構(gòu)造函數(shù))都是函數(shù),那就是繼承Function.prototype的;結(jié)論就是先有Object.prototype的。

參考

深入理解javascript原型和閉包(完結(jié))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JavaScript,通??s寫為 JS,是一種解釋執(zhí)行的編程語言。它是現(xiàn)在最流行的腳本語言之一。 JavaScri...
    神齊閱讀 5,475評論 1 32
  • 一、 JS面向?qū)ο缶幊?1、 面向?qū)ο蠼榻B 什么是對象? Everything is object (萬物皆對象)...
    寵辱不驚丶?xì)q月靜好閱讀 912評論 0 2
  • 學(xué)習(xí)目標(biāo): 理解面向?qū)ο箝_發(fā)思想 掌握 JavaScript 面向?qū)ο箝_發(fā)相關(guān)模式 掌握在 JavaScript ...
    金桔檸檬加冰閱讀 435評論 0 0
  • 夸克瀏覽器是我非常喜歡的一款瀏覽器,整體非常簡潔,UI做的也很精致。今天我就來仿寫主頁底部的工具欄。先來看看原本的...
    大頭呆閱讀 2,537評論 3 17
  • 很忙很累卻沒有效率,是我最近的狀態(tài),有沒有什么方法結(jié)束這種狀態(tài)呢?今天晨讀文章分享的戴維·艾倫的《Getting ...
    唯其時(shí)物閱讀 219評論 0 2

友情鏈接更多精彩內(nèi)容