JavaScript原型和原型鏈相關(guān)知識(shí)梳理

一、創(chuàng)建對(duì)象有幾種方法

請(qǐng)大家盡可能多的找到創(chuàng)建對(duì)象的方法,如有補(bǔ)充歡迎在評(píng)論區(qū)留言討論。更多參考詳見:JavaScript創(chuàng)建對(duì)象的7種方法

第一種方式:對(duì)象字面量表示法

var o1 = {
    name: 'o1'
};
var o2 = new Object({
    name: 'o2'
});

缺點(diǎn):字面量對(duì)象中默認(rèn)原型鏈指向Object,用同一個(gè)接口創(chuàng)建很多對(duì)象會(huì)產(chǎn)生大量冗余代碼。

第二種方式:使用顯式構(gòu)造函數(shù)

var M = function (name) {
    this.name = name;
};
var o3 = new M('o3');

第三種方式:Object.create

var P = {
    name: 'p'
};
var o4 = Object.create(P);

那么,在控制臺(tái)輸入o1, o2, o3, o4,會(huì)發(fā)生什么?

  1. o1, o2 輸出 Object 對(duì)象{ name: 'o1' }、{ name: 'o2' }
  2. o3輸出 M 對(duì)象{ name: 'o3' }
  3. o4是個(gè)空對(duì)象{}
  4. o1,o2, o3, o4 均有name屬性

二、原型系統(tǒng)的“復(fù)制操作”的實(shí)現(xiàn)思路

淺拷貝

并不是真的去復(fù)制一個(gè)原型對(duì)象,而是使得新對(duì)象持有一個(gè)原型的引用;

深拷貝

另一個(gè)是切實(shí)地復(fù)制對(duì)象,從此兩個(gè)對(duì)象再無(wú)關(guān)聯(lián)。

三、理解原型、構(gòu)造函數(shù)、實(shí)例、原型鏈

原型

JavaScript 是基于原型的編程語(yǔ)言的代表,它利用原型來(lái)描述對(duì)象。JavaScript 并非第一個(gè)使用原型的語(yǔ)言,在它之前,self、kevo 等語(yǔ)言已經(jīng)開始使用原型來(lái)描述對(duì)象了。 Brendan 最初的構(gòu)想是將 JavaScript 設(shè)計(jì)成一個(gè)擁有基于原型的面向?qū)ο竽芰Φ?scheme 語(yǔ)言,而基于原型系統(tǒng)的獨(dú)特之處是提倡運(yùn)行時(shí)的原型修改。

對(duì)象可以有兩種成員類型:

  • 實(shí)例成員:直接存在于對(duì)象實(shí)例中
  • 原型成員:從對(duì)象原型繼承

”基于類“和”基于原型“的編程之間的比較

基于類 基于原型
提倡使用一個(gè)關(guān)注分類和類之間關(guān)系的開發(fā)模型 提倡編碼人員關(guān)注一系列對(duì)象實(shí)例的行為
先有類,再?gòu)念惾?shí)例化一個(gè)對(duì)象 將對(duì)象劃分到最近的使用方式相似的原型對(duì)象
類之間的關(guān)系:繼承、組合等 通過(guò)復(fù)制的方式創(chuàng)建新對(duì)象
往往與語(yǔ)言的類型系統(tǒng)整合,形成一定編譯時(shí)的能力 一些語(yǔ)言中,復(fù)制一個(gè)空對(duì)象,就是創(chuàng)建一個(gè)全新的對(duì)象

JavaScript的原型

拋開JavaScript模擬Java類的復(fù)雜語(yǔ)法,如 new、Function Object、函數(shù)的 prototype 屬性等。其原型系統(tǒng)可以用以下兩條概括:

  1. 如果所有對(duì)象都有私有字段 [[prototype]],就是對(duì)象的原型;
  2. 讀一個(gè)屬性,如果對(duì)象本身沒有,則會(huì)繼續(xù)訪問(wèn)對(duì)象的原型,直到原型為空或者找到為止。

判斷對(duì)象是否包含特定的實(shí)例成員hasOwnProperty("成員的名稱")
確定對(duì)象是否包含特定的屬性:使用in操作符(既搜索實(shí)例又搜索原型)

ES6中,JavaScript提供能直接訪問(wèn)操縱原型的三個(gè)方法

  • Object.create根據(jù)指定的原型創(chuàng)建新對(duì)象,原型可以是null
  • Object.getPrototypeOf獲得一個(gè)對(duì)象的原型
  • Object.setPrototypeOf設(shè)置一個(gè)對(duì)象的原型

利用以上三個(gè)方法,我們可以完全拋開類的思維,利用原型來(lái)實(shí)現(xiàn)抽象和復(fù)用。

var cat = {
    say(){
        console.log("miao~miao~");
    },
    play(){
        console.log("ball")
    },
}
//布偶貓
var Ragdoll = Object.create(cat, {
    say: {
        writable: true,
        configurable: true,
        enumerable: true,
        value: function () {
            console.log("iao~iao~")
        }
    }    
})
var anotherCat = Object.create(cat);
anotherCat.say(); //miao~miao~
var anotherRagdollCat = Object.create(Ragdoll);
Ragdoll.say(); //iao~iao~

構(gòu)造函數(shù)和實(shí)例

實(shí)例:對(duì)象就是一個(gè)實(shí)例。

構(gòu)造函數(shù):任何一個(gè)函數(shù)只要被new操作了,該函數(shù)就可以被叫做構(gòu)造函數(shù)。

圖解原型鏈

上圖對(duì)應(yīng)關(guān)系闡述:

  1. 函數(shù)都有prototype屬性,指的就是原型對(duì)象。(聲明一個(gè)函數(shù)時(shí)JS引擎會(huì)為這個(gè)構(gòu)造函數(shù)自動(dòng)添加prototype屬性,該屬性會(huì)初始化一個(gè)空對(duì)象,也就是原型對(duì)象。)

  2. 原型對(duì)象怎么區(qū)分出被哪一個(gè)構(gòu)造函數(shù)所引用?

    原型對(duì)象中有一個(gè)構(gòu)造器constructor,會(huì)默認(rèn)聲明的函數(shù),即通過(guò)constructor來(lái)確定是被哪一個(gè)構(gòu)造函數(shù)引用。

上圖工作原理代碼演示:

上面的例子中o3是實(shí)例,M是構(gòu)造函數(shù)

//構(gòu)造函數(shù)和原型對(duì)象的關(guān)系
M.prototype.constructor === M //true
//實(shí)例和原型對(duì)象之間的關(guān)系
o3.__proto__ === M.prototype //true

原型鏈

原型鏈就是js中數(shù)據(jù)繼承的繼承鏈。在訪問(wèn)一個(gè)實(shí)例的屬性的時(shí)候,先在實(shí)例本身中找,如果沒找到,再?gòu)倪@個(gè)實(shí)例對(duì)象向上找構(gòu)造這個(gè)實(shí)例的相關(guān)聯(lián)的對(duì)象,還沒找到就再往上找,這個(gè)相關(guān)聯(lián)的對(duì)象又有創(chuàng)造它的上一級(jí)的原型對(duì)象。以此類推,到Object.prototype終止(整個(gè)原型鏈的頂端),原型鏈通過(guò)prototype__proto__屬性進(jìn)行向上查找。

JavaScript object 猜想圖

原型對(duì)象和原型鏈之間起的作用

構(gòu)造函數(shù)中增加很多屬性和方法。當(dāng)有多個(gè)實(shí)例,想共用一個(gè)方法,不能每個(gè)實(shí)例都拷貝一份(若每個(gè)實(shí)例都要拷貝一份,會(huì)占內(nèi)存,沒有必要),多個(gè)實(shí)例之間有相同的方法時(shí)要考慮存到共同的東西上,這個(gè)共同的東西就是原型對(duì)象。這就是JS引擎支持的原型鏈的功能,任何一個(gè)實(shí)例對(duì)象通過(guò)原型鏈找到它上面的原型對(duì)象,上面的實(shí)例和方法都可以被共享。

var M = function(name){this.name = name;};
M.prototype.say = function() {
    console.log('say hi');
}
var o5 = new M('o5');

控制臺(tái)輸出:

o3.say()
say hi
o5.say()
say hi

注意:

  1. 構(gòu)造函數(shù)才會(huì)有prototype,對(duì)象是沒有prototype 的。

  2. 實(shí)例對(duì)象才有__proto__屬性。

    特殊的,函數(shù)即是函數(shù)也是對(duì)象,也有__proto__屬性:

    M.__proto__ === Function.prototype //true
    

    其他的:

    Array.__proto__ === Function.prototype //true
    
    let arr = [1,2,3,4] 
    arr.__proto__ === Array.prototype //true
    
    Array.prototype.__proto__ === Object.prototype //true
    
    
    Object.prototype.__proto__ === null //true
    

    參考:JavaScript:構(gòu)造函數(shù)和原型鏈

四、instanceof 原理

圖解instanceof

原理:實(shí)例對(duì)象的__proto__屬性和構(gòu)造函數(shù)沒什么關(guān)聯(lián),其實(shí)是引用的原型對(duì)象。instanceof用來(lái)判斷實(shí)例對(duì)象的屬性和構(gòu)造函數(shù)的屬性是不是同一個(gè)引用

原型對(duì)象上可能還會(huì)有原型鏈,用實(shí)例對(duì)象instanceof判斷原型的構(gòu)造函數(shù),這條原型鏈上的函數(shù)返回都是 true:

o3 instanceof M //true
//只要是原型鏈上都可以看作instanceof的構(gòu)造函數(shù)
o3 instanceof Object //true

解釋:

o3.__proto__=== M.prototype //true
M.prototype.__proto___=== Object.prototype //true
//判斷是哪個(gè)構(gòu)造函數(shù)直接生成的,比instanceof更嚴(yán)謹(jǐn)
o3.__proto__.constructor === M //true
o3.__proto__.constructor === Object //false

五、new運(yùn)算符

定義:JavaScript 的 new 運(yùn)算符創(chuàng)建一個(gè)繼承于其運(yùn)算數(shù)的原型的新對(duì)象,然后調(diào)用該運(yùn)算數(shù),把新創(chuàng)建的對(duì)象綁定給this。

按照慣例,打算與 new 結(jié)合使用的函數(shù)命名應(yīng)該首字母大寫,并謹(jǐn)慎使用 new 。

new運(yùn)算接收一個(gè)構(gòu)造器和一組調(diào)用參數(shù),其工作原理為:

  1. new 后面加上構(gòu)造函數(shù)(構(gòu)造器),一個(gè)新對(duì)象被創(chuàng)建,它繼承自構(gòu)造函數(shù)原型對(duì)象Foo.prototype屬性

  2. 將this和調(diào)用參數(shù)傳給構(gòu)造器:

    構(gòu)造函數(shù) Foo 被執(zhí)行的時(shí)候,相應(yīng)的傳參會(huì)被傳入,同時(shí)上下文this 會(huì)被指定為這個(gè)新實(shí)例。(new Foo() 在不傳遞任何參數(shù)的時(shí)候可以寫成 new Foo。)

  3. 如果構(gòu)造函數(shù)返回了一個(gè)對(duì)象,那么這個(gè)對(duì)象會(huì)取代整個(gè) new 出來(lái)的結(jié)果。換句話說(shuō),如果構(gòu)造函數(shù)沒有任何返回對(duì)象,那么new出來(lái)的結(jié)果為第一步創(chuàng)建的新對(duì)象;有返回對(duì)象,直接返回。

實(shí)現(xiàn)一個(gè)new運(yùn)算符效果

var newFunc = function (func) {
    //1.創(chuàng)建空對(duì)象,關(guān)聯(lián)指定構(gòu)造函數(shù)原型對(duì)象
    var a = Object.create(func.prorotype);
    //2.把上下文轉(zhuǎn)移給b對(duì)象
    var b = func.call(a);
    //3.判斷執(zhí)行之后的結(jié)果是不是對(duì)象類型
    if (typeof b === 'object') {
        return b;
    } else {
        return a;
    }
}

控制臺(tái)輸出:

o6 = newFunc(M)
o6 instanceof M //true
o6 instanceof Object //true
o6.__proto__.constructor === M //true
M.prototype.walk = function() {
    console.log('walk')
}
o6.walk()
walk
o3.walk()
walk

new 這樣的行為,試圖讓函數(shù)對(duì)象在語(yǔ)法上跟類變得相似,但是,它客觀上提供了兩種方式,一是在構(gòu)造器中添加屬性,二是在構(gòu)造器的 prototype 屬性上添加屬性。

用構(gòu)造器模擬類的兩種方法:

//第一種方法:直接在構(gòu)造器中修改this,給this添加屬性
function Cls1(){
    this.p1 = 1;
    this.p2 = function(){
        console.log(this.p1);
    }
} 
var o1 = new Cls1;
o1.p2();
//第二種方法:修改構(gòu)造器的 prototype 屬性指向的對(duì)象,它是從這個(gè)構(gòu)造器構(gòu)造出來(lái)的所有對(duì)象的原型。
function Cls2(){
}
Cls2.prototype.p1 = 1;
Cls2.prototype.p2 = function(){
    console.log(this.p1);
}

var o2 = new Cls2;
o2.p2();

沒有 Object.create、Object.setPrototypeOf 的早期版本中,new 運(yùn)算是唯一一個(gè)可以指定 [[prototype]] 的方法(當(dāng)時(shí)的 mozilla 提供了私有屬性 proto,但是多數(shù)環(huán)境并不支持),所以,當(dāng)時(shí)已經(jīng)有人試圖用它來(lái)代替后來(lái)的 Object.create,我們甚至可以用它來(lái)實(shí)現(xiàn)一個(gè) Object.create 的不完整的 pollyfill,見以下代碼:

Object.create = function(prototype){
    var cls = function(){}
    cls.prototype = prototype;
    return new cls;
}

這段代碼創(chuàng)建了一個(gè)空函數(shù)作為類,并把傳入的原型掛在了它的 prototype上,最后創(chuàng)建了一個(gè)它的實(shí)例,根據(jù) new 的行為,這將產(chǎn)生一個(gè)以傳入的第一個(gè)參數(shù)為原型的對(duì)象。

六、補(bǔ)充問(wèn)題

為什么o4直接拿不到name屬性?

Object.create方法創(chuàng)建的對(duì)象是用原型鏈連接的,當(dāng)JS引擎查找o4是一個(gè)空對(duì)象,這個(gè)空對(duì)象上是沒有name屬性的,name屬性在它的原型對(duì)象上,也就是說(shuō),Object.create方法是把參數(shù)中的對(duì)象作為一個(gè)新對(duì)象的原型對(duì)象賦給o4的,o4本身不具備這個(gè)屬性。

o4.__proto__ === p
true

總結(jié):原型鏈真的很重要,值得反復(fù)理解推敲。

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

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

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