最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript 的原型鏈機(jī)制與繼承詳解

 更新時(shí)間:2026年05月23日 14:19:20   作者:HeyCeleste  
這段文章詳細(xì)解釋了JavaScript中的原型和原型鏈機(jī)制,以及它們?nèi)绾螌?shí)現(xiàn)屬性和方法的共享,文章還探討了通過原型鏈查找屬性的方法,并對(duì)比了多種實(shí)現(xiàn)繼承的方式,包括原型鏈繼承、構(gòu)造函數(shù)繼承、組合機(jī)制和寄生組合機(jī)制,感興趣的朋友一起看看吧

JavaScript 的原型和原型鏈,本質(zhì)是一種基于對(duì)象的繼承機(jī)制。通過原型鏈實(shí)現(xiàn)屬性查找和方法復(fù)用,使多個(gè)對(duì)象能夠共享同一份數(shù)據(jù)和行為,同時(shí)避免重復(fù)創(chuàng)建,提高性能。

原型

官方定義(MDN):

當(dāng)使用 new 調(diào)用函數(shù)時(shí),構(gòu)造函數(shù)的 prototype 屬性將成為結(jié)果對(duì)象的原型。

原型是幾乎每個(gè)對(duì)象實(shí)例都有的內(nèi)部屬性 [[Prototype]](可以通過定義在 Object.prototype 上的訪問器屬性 .__proto__訪問),在通過 new 創(chuàng)建對(duì)象時(shí)這個(gè)屬性就指向構(gòu)造函數(shù)的 prototype 屬性。

訪問器屬性: 這是與普通屬性(數(shù)據(jù)屬性)不同的概念,數(shù)據(jù)屬性用于存儲(chǔ)值,而訪問器屬性通過 getter/setter 定義屬性的讀取和賦值行為。普通屬性例如 obj.name = "G.E.M." 就像是一個(gè)箱子將對(duì)應(yīng)的值存儲(chǔ)起來,而訪問器屬性并不是一個(gè)儲(chǔ)物箱而是訪問器。

從底層來看,.__proto__ 其實(shí)是通過 getter/setter 這兩個(gè)函數(shù)觸發(fā)了引擎內(nèi)部方法 [[GetPrototypeOf]][[SetPrototypeOf]]從而間接讀寫內(nèi)部屬性 [[Prototype]]。ES5 標(biāo)準(zhǔn)更推薦 Object.getPrototypeOf()Object.setPrototypeOf() 這兩個(gè)方法讀寫對(duì)象實(shí)例的原型,.__proto__ 僅為保證瀏覽器向下兼容而存在。

這里值得注意的是,構(gòu)造函數(shù)也有原型,即內(nèi)部屬性 [[Prototype]],但這和它的 prototype 屬性并不是一個(gè)東西。構(gòu)造函數(shù)本質(zhì)上是對(duì)象,所以它自然也有原型,而 prototype 屬性是普通函數(shù)(非箭頭函數(shù))在創(chuàng)建時(shí)自帶的、用來為未來實(shí)例當(dāng)作公共池子的普通屬性,用于實(shí)現(xiàn)對(duì)象的繼承。

而原型對(duì)象上有個(gè) constructor 屬性指向構(gòu)造函數(shù)本身。也就是說,在默認(rèn)情況下,如果假設(shè) const obj = new Foo(),那么就存在 Object.getPrototypeof(obj).constructor === Foo。

綜上所述,每個(gè)對(duì)象身上都有一個(gè)內(nèi)部屬性 [[Prototype]],指向一個(gè)對(duì)象,即原型對(duì)象。想要訪問到這個(gè)對(duì)象可以通過 Object.getPrototypeof() 或者 .__proto__,這兩個(gè)方法都會(huì)指向原型對(duì)象。并且,在實(shí)例被創(chuàng)建時(shí),會(huì)將其原型指向構(gòu)造函數(shù)的普通屬性 prototype,而在這個(gè)原型對(duì)象上又有一個(gè) constructor 屬性指向構(gòu)造函數(shù)本身。關(guān)系如下圖所示:

核心作用:

原型的本質(zhì)作用就是實(shí)現(xiàn)屬性和方法的共享。原型機(jī)制通過“引用共享”,避免了函數(shù)的重復(fù)創(chuàng)建,提高了內(nèi)存的利用率。

原型鏈

當(dāng)訪問一個(gè)對(duì)象屬性時(shí),JavaScript 引擎會(huì)先查對(duì)象本身,如果本身沒有這個(gè)屬性就會(huì)查它的原型,如果還沒有就繼續(xù)往上找直到找到 null,這條路徑就是原型鏈。

原型鏈上最后一個(gè)真實(shí)存在的對(duì)象是 Object.prototype,它的內(nèi)部屬性 [[Prototype]] 指向 null,即原型鏈的終點(diǎn),因?yàn)榻^大多數(shù)對(duì)象都是從 Object.prototype 繼承而來的。

繼承

傳統(tǒng)面向?qū)ο笳Z言(如 Java/C++)中繼承意味著復(fù)制,派生類會(huì)將基類的屬性和方法硬拷貝一份到自己身上,而 JavaScript 是利用原型鏈機(jī)制模擬了一種“假繼承”。在 new 一個(gè)對(duì)象的時(shí)候,JavaScript 并沒有像傳統(tǒng)類那樣復(fù)制藍(lán)圖上的共享方法,而是通過原型機(jī)制將對(duì)象實(shí)例的原型鏈接到構(gòu)造函數(shù)的 prototype 上。

《You Don’t Know JS》中的觀點(diǎn)是 JS 底層并沒有類的概念,所謂的“繼承”是將兩個(gè)對(duì)象通過 [[Prototype]] 建立的一條單向引用鏈條(也就是原型鏈)。作者認(rèn)為這種行為應(yīng)該稱之為“行為委托”而不是繼承:當(dāng)我們訪問一個(gè)對(duì)象 A 身上不存在的屬性/方法時(shí),它并不是從其他地方復(fù)制或繼承了這個(gè)屬性/方法,而是順著原型鏈找到另一個(gè)對(duì)象 B,并將這個(gè)行為委托給 B 讓它幫執(zhí)行,此時(shí) A 的執(zhí)行上下文(this)依舊綁定在它身上。

但由于普遍的用語習(xí)慣,下面還是將這一委托行為稱為繼承。

JS 實(shí)現(xiàn)繼承的幾種方式:

原型鏈繼承

function Base() {
    this.names = ['G.E.M.', 'Gloria'];
}
Base.prototype.say = function() {}
function Derived() {}
Derived.prototype = new Base();
const d1 = new Derived();
const d2 = new Derived();
d1.say(); // 能夠訪問到

這是早起最容易想到的辦法,直接將派生類的原型換成基類的實(shí)例。但這樣實(shí)現(xiàn)的一個(gè)痛點(diǎn)就是引用類型的數(shù)據(jù)會(huì)被所有派生類實(shí)例共享,例如此處 d1.names.push('Tang') 會(huì)導(dǎo)致 d2.names 也跟著改變。

構(gòu)造函數(shù)繼承

function Base(name) {
    this.name = name;
    this.say = function() {console.log(this.name);}
}
function Derived(name, age) {
    Base.call(this, name); // 借用基類的構(gòu)造函數(shù)并把 this 綁定到當(dāng)前實(shí)例上
    this.age = age;
}

這樣實(shí)現(xiàn)繼承會(huì)使得每次實(shí)例化派生類都會(huì)把方法重新創(chuàng)建一遍,沒有實(shí)現(xiàn)函數(shù)引用的復(fù)用,造成內(nèi)存嚴(yán)重浪費(fèi)。

組合機(jī)制

這是將上述二者結(jié)合了起來:

function Base(name) {
    this.name = name;
    this.names = ['G.E.M.', 'Gloria'];
}
Base.prototype.say = function() {} // 將方法掛在原型上實(shí)現(xiàn)復(fù)用
function Derived(name, age) {
    Base.call(this, name); // 拿到獨(dú)立的屬性
    this.age = age;
}
Derived.prototype = new Base(); // 將原型鏈拉通
Derived.prototype.constructor = Derived; // 手動(dòng)修復(fù) constructor

組合機(jī)制的痛點(diǎn)是調(diào)用了兩次基類的構(gòu)造函數(shù),執(zhí)行 Base.call(this, name) 后派生類實(shí)例就創(chuàng)建了自己獨(dú)立的屬性,但 Derived.prototype = new Base()Derived.prototype 上也有了 Base 的那份屬性,但這上面的屬性并不會(huì)用到,造成內(nèi)存浪費(fèi)。

寄生組合機(jī)制

這是 ES5 時(shí)代最完美的手寫方案。

function Base(name) {
    this.name = name;
}
Base.prototype.say = function() {}
function Derived(name, age) {
    Base.call(this, name);
    this.age = age;
}
// 最優(yōu)方案是利用 Object.create() 直接建立干凈的原型鏈
// 這個(gè)方法實(shí)現(xiàn)了 Derived.prototype.[[Prototype]] = Base.prototype
Derived.prototype = Object.create(Base.prototype);
Derived.prototype.constructor = Derived;

ES6 的 class/extends

class 和 extends 是 ES6 官方推出的基于寄生組合機(jī)制繼承的語法糖。

class Base {
    constructor(name) {
        this.name = name;
    }
    say() {}
}
class Derived extends Base {
    constructor(name, age) {
        super(name); // 這句相當(dāng)于 Base.call(this, name)
        this.age = age;
    }
}

ES5 寄生組合機(jī)制中存在靜態(tài)屬性的丟失,也就是沒有建立起基類和派生類兩個(gè)函數(shù)對(duì)象之間的直接連接,ES6 推出的 extends 補(bǔ)全了這一點(diǎn),底層實(shí)現(xiàn)了 Derived.__proto__ = Base

到此這篇關(guān)于JavaScript 的原型鏈機(jī)制與繼承詳解的文章就介紹到這了,更多相關(guān)js原型鏈機(jī)制與繼承內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

华安县| 巢湖市| 满洲里市| 柘荣县| 莱芜市| 佛坪县| 富裕县| 宁乡县| 怀集县| 临夏市| 黎城县| 南投市| 湾仔区| 沐川县| 龙里县| 蒲江县| 普洱| 玉门市| 邯郸市| 新野县| 象山县| 阿拉善盟| 荥经县| 林周县| 台中市| 昭觉县| 蓬安县| 新巴尔虎右旗| 台中县| 玛曲县| 儋州市| 仪陇县| 永吉县| 崇义县| 芦山县| 松原市| 吉安市| 弥渡县| 鹿泉市| 济源市| 廉江市|