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

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

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

第一種方式:對象字面量表示法

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

缺點:字面量對象中默認原型鏈指向Object讼载,用同一個接口創(chuàng)建很多對象會產(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);

那么钙姊,在控制臺輸入o1, o2, o3, o4,會發(fā)生什么埂伦?

  1. o1, o2 輸出 Object 對象{ name: 'o1' }煞额、{ name: 'o2' }
  2. o3輸出 M 對象{ name: 'o3' }
  3. o4是個空對象{}
  4. o1,o2, o3, o4 均有name屬性

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

淺拷貝

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

深拷貝

另一個是切實地復(fù)制對象膊毁,從此兩個對象再無關(guān)聯(lián)。

三基跑、理解原型婚温、構(gòu)造函數(shù)、實例媳否、原型鏈

原型

JavaScript 是基于原型的編程語言的代表栅螟,它利用原型來描述對象。JavaScript 并非第一個使用原型的語言篱竭,在它之前力图,self、kevo 等語言已經(jīng)開始使用原型來描述對象了掺逼。 Brendan 最初的構(gòu)想是將 JavaScript 設(shè)計成一個擁有基于原型的面向?qū)ο竽芰Φ?scheme 語言吃媒,而基于原型系統(tǒng)的獨特之處是提倡運行時的原型修改

對象可以有兩種成員類型:

  • 實例成員:直接存在于對象實例中
  • 原型成員:從對象原型繼承

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

基于類 基于原型
提倡使用一個關(guān)注分類和類之間關(guān)系的開發(fā)模型 提倡編碼人員關(guān)注一系列對象實例的行為
先有類坪圾,再從類去實例化一個對象 將對象劃分到最近的使用方式相似的原型對象
類之間的關(guān)系:繼承晓折、組合等 通過復(fù)制的方式創(chuàng)建新對象
往往與語言的類型系統(tǒng)整合,形成一定編譯時的能力 一些語言中兽泄,復(fù)制一個空對象漓概,就是創(chuàng)建一個全新的對象

JavaScript的原型

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

  1. 如果所有對象都有私有字段 [[prototype]]蜓陌,就是對象的原型觅彰;
  2. 讀一個屬性,如果對象本身沒有钮热,則會繼續(xù)訪問對象的原型填抬,直到原型為空或者找到為止。

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

ES6中隧期,JavaScript提供能直接訪問操縱原型的三個方法

  • Object.create根據(jù)指定的原型創(chuàng)建新對象飒责,原型可以是null
  • Object.getPrototypeOf獲得一個對象的原型
  • Object.setPrototypeOf設(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ù)和實例

實例:對象就是一個實例遣臼。

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

圖解原型鏈

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

  1. 函數(shù)都有prototype屬性揍堰,指的就是原型對象。(聲明一個函數(shù)時JS引擎會為這個構(gòu)造函數(shù)自動添加prototype屬性嗅义,該屬性會初始化一個空對象屏歹,也就是原型對象。)

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

    原型對象中有一個構(gòu)造器constructor西采,會默認聲明的函數(shù)凰萨,即通過constructor來確定是被哪一個構(gòu)造函數(shù)引用继控。

上圖工作原理代碼演示:

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

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

原型鏈

原型鏈就是js中數(shù)據(jù)繼承的繼承鏈胖眷。在訪問一個實例的屬性的時候武通,先在實例本身中找,如果沒找到珊搀,再從這個實例對象向上找構(gòu)造這個實例的相關(guān)聯(lián)的對象冶忱,還沒找到就再往上找,這個相關(guān)聯(lián)的對象又有創(chuàng)造它的上一級的原型對象境析。以此類推囚枪,到Object.prototype終止(整個原型鏈的頂端),原型鏈通過prototype__proto__屬性進行向上查找劳淆。

JavaScript object 猜想圖

原型對象和原型鏈之間起的作用

構(gòu)造函數(shù)中增加很多屬性和方法链沼。當有多個實例,想共用一個方法沛鸵,不能每個實例都拷貝一份(若每個實例都要拷貝一份括勺,會占內(nèi)存,沒有必要)曲掰,多個實例之間有相同的方法時要考慮存到共同的東西上疾捍,這個共同的東西就是原型對象。這就是JS引擎支持的原型鏈的功能栏妖,任何一個實例對象通過原型鏈找到它上面的原型對象乱豆,上面的實例和方法都可以被共享

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

控制臺輸出:

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

注意:

  1. 構(gòu)造函數(shù)才會有prototype吊趾,對象是沒有prototype 的宛裕。

  2. 實例對象才有__proto__屬性房官。

    特殊的,函數(shù)即是函數(shù)也是對象续滋,也有__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

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

原型對象上可能還會有原型鏈,用實例對象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òu)造函數(shù)直接生成的湿颅,比instanceof更嚴謹
o3.__proto__.constructor === M //true
o3.__proto__.constructor === Object //false

五、new運算符

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

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

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

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

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

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

  3. 如果構(gòu)造函數(shù)返回了一個對象绊率,那么這個對象會取代整個 new 出來的結(jié)果。換句話說究履,如果構(gòu)造函數(shù)沒有任何返回對象滤否,那么new出來的結(jié)果為第一步創(chuàng)建的新對象;有返回對象挎袜,直接返回顽聂。

實現(xiàn)一個new運算符效果

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

控制臺輸出:

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ù)對象在語法上跟類變得相似紊搪,但是,它客觀上提供了兩種方式全景,一是在構(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 屬性指向的對象滞伟,它是從這個構(gòu)造器構(gòu)造出來的所有對象的原型揭鳞。
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 運算是唯一一個可以指定 [[prototype]] 的方法(當時的 mozilla 提供了私有屬性 proto野崇,但是多數(shù)環(huán)境并不支持),所以亩钟,當時已經(jīng)有人試圖用它來代替后來的 Object.create乓梨,我們甚至可以用它來實現(xiàn)一個 Object.create 的不完整的 pollyfill,見以下代碼:

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

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

六、補充問題

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

Object.create方法創(chuàng)建的對象是用原型鏈連接的辱志,當JS引擎查找o4是一個空對象蝠筑,這個空對象上是沒有name屬性的,name屬性在它的原型對象上荸频,也就是說菱肖,Object.create方法是把參數(shù)中的對象作為一個新對象的原型對象賦給o4的客冈,o4本身不具備這個屬性旭从。

o4.__proto__ === p
true

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末和悦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子渠缕,更是在濱河造成了極大的恐慌鸽素,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亦鳞,死亡現(xiàn)場離奇詭異馍忽,居然都是意外死亡,警方通過查閱死者的電腦和手機燕差,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門遭笋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人徒探,你說我怎么就攤上這事瓦呼。” “怎么了测暗?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵央串,是天一觀的道長磨澡。 經(jīng)常有香客問我,道長质和,這世上最難降的妖魔是什么稳摄? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮饲宿,結(jié)果婚禮上秩命,老公的妹妹穿的比我還像新娘。我一直安慰自己褒傅,他們只是感情好弃锐,可當我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著殿托,像睡著了一般霹菊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上支竹,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天旋廷,我揣著相機與錄音,去河邊找鬼礼搁。 笑死饶碘,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的馒吴。 我是一名探鬼主播扎运,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饮戳!你這毒婦竟也來了豪治?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤扯罐,失蹤者是張志新(化名)和其女友劉穎负拟,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體歹河,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡掩浙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了秸歧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厨姚。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡煮甥,死狀恐怖幅骄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情姑食,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布芭梯,位于F島的核電站险耀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏玖喘。R本人自食惡果不足惜甩牺,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望累奈。 院中可真熱鬧贬派,春花似錦、人聲如沸澎媒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽戒努。三九已至请敦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間储玫,已是汗流浹背侍筛。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留撒穷,地道東北人匣椰。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像端礼,于是被迫代替她去往敵國和親禽笑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內(nèi)容