JS繼承與原型鏈

其實(shí)MDN上寫的非常清晰了,我這里只是為了自己做筆記。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

繼承時(shí),JavaScript 只有一種結(jié)構(gòu):對(duì)象。每個(gè)實(shí)例對(duì)象( object )都有一個(gè)私有屬性(稱之為 proto )指向它的構(gòu)造函數(shù)的原型對(duì)象(prototype )。該原型對(duì)象也有一個(gè)自己的原型對(duì)象( proto ) ,層層向上直到一個(gè)對(duì)象的原型對(duì)象為 null。根據(jù)定義,null 沒有原型,并作為這個(gè)原型鏈中的最后一個(gè)環(huán)節(jié)。

幾乎所有 JavaScript 中的對(duì)象都是位于原型鏈頂端的 Object 的實(shí)例。

基于原型鏈的繼承

繼承屬性

JavaScript 對(duì)象是動(dòng)態(tài)的屬性“包”(指其自己的屬性)。JavaScript 對(duì)象有一個(gè)指向一個(gè)原型對(duì)象的鏈。當(dāng)試圖訪問一個(gè)對(duì)象的屬性時(shí),它不僅僅在該對(duì)象上搜尋,還會(huì)搜尋該對(duì)象的原型,以及該對(duì)象的原型的原型,依次層層向上搜索,直到找到一個(gè)名字匹配的屬性或到達(dá)原型鏈的末尾。

遵循ECMAScript標(biāo)準(zhǔn),someObject.[[Prototype]] 符號(hào)是用于指向 someObject 的原型。從 ECMAScript 6 開始,[[Prototype]] 可以通過 Object.getPrototypeOf()Object.setPrototypeOf() 訪問器來訪問。這個(gè)等同于 JavaScript 的非標(biāo)準(zhǔn)但許多瀏覽器實(shí)現(xiàn)的屬性 __proto__。

但它不應(yīng)該與構(gòu)造函數(shù) funcprototype 屬性相混淆。被構(gòu)造函數(shù)創(chuàng)建的實(shí)例對(duì)象的 [[prototype]] 指向 funcprototype 屬性。**Object.prototype **屬性表示 Object 的原型對(duì)象。

// 讓我們從一個(gè)自身擁有屬性a和b的函數(shù)里創(chuàng)建一個(gè)對(duì)象o:
let f = function () {
   this.a = 1;
   this.b = 2;
}
/* 這么寫也一樣
function f() {
  this.a = 1;
  this.b = 2;
}
*/
let o = new f(); // {a: 1, b: 2}

// 在f函數(shù)的原型上定義屬性
f.prototype.b = 3;
f.prototype.c = 4;

// 不要在 f 函數(shù)的原型上直接定義 f.prototype = {b:3,c:4};這樣會(huì)直接打破原型鏈
// o.[[Prototype]] 有屬性 b 和 c
//  (其實(shí)就是 o.__proto__ 或者 o.constructor.prototype)
// o.[[Prototype]].[[Prototype]] 是 Object.prototype.
// 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null
// 這就是原型鏈的末尾,即 null,
// 根據(jù)定義,null 就是沒有 [[Prototype]]。

// 綜上,整個(gè)原型鏈如下: 

// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null

console.log(o.a); // 1
// a是o的自身屬性嗎?是的,該屬性的值為 1

console.log(o.b); // 2
// b是o的自身屬性嗎?是的,該屬性的值為 2
// 原型上也有一個(gè)'b'屬性,但是它不會(huì)被訪問到。
// 這種情況被稱為"屬性遮蔽 (property shadowing)"

console.log(o.c); // 4
// c是o的自身屬性嗎?不是,那看看它的原型上有沒有
// c是o.[[Prototype]]的屬性嗎?是的,該屬性的值為 4

console.log(o.d); // undefined
// d 是 o 的自身屬性嗎?不是,那看看它的原型上有沒有
// d 是 o.[[Prototype]] 的屬性嗎?不是,那看看它的原型上有沒有
// o.[[Prototype]].[[Prototype]] 為 null,停止搜索
// 找不到 d 屬性,返回 undefined

給對(duì)象設(shè)置屬性會(huì)創(chuàng)建自有屬性。獲取和設(shè)置屬性的唯一限制是內(nèi)置 getter 或 setter 的屬性。

繼承方法

在 JavaScript 里,任何函數(shù)都可以添加到對(duì)象上作為對(duì)象的屬性。
當(dāng)繼承的函數(shù)被調(diào)用時(shí),this 指向的是當(dāng)前繼承的對(duì)象,而不是繼承的函數(shù)所在的原型對(duì)象。

var o = {
  a: 2,
  m: function(){
    return this.a + 1;
  }
};

console.log(o.m()); // 3
// 當(dāng)調(diào)用 o.m 時(shí),'this' 指向了 o.

var p = Object.create(o);
// p是一個(gè)繼承自 o 的對(duì)象

p.a = 4; // 創(chuàng)建 p 的自身屬性 'a'
console.log(p.m()); // 5
// 調(diào)用 p.m 時(shí),'this' 指向了 p
// 又因?yàn)?p 繼承了 o 的 m 函數(shù)
// 所以,此時(shí)的 'this.a' 即 p.a,就是 p 的自身屬性 'a' 

在 JavaScript 中使用原型

在 JavaScript 中,函數(shù)(function)是允許擁有屬性的。所有的函數(shù)會(huì)有一個(gè)特別的屬性 —— prototype 。
運(yùn)行如下代碼

function doSomething(){}
console.log( doSomething.prototype );
// 和聲明函數(shù)的方式無關(guān),
// JavaScript 中的函數(shù)永遠(yuǎn)有一個(gè)默認(rèn)原型屬性。
var doSomething = function(){}; // doSomething從Object.prototype繼承對(duì)象的方法。
console.log( doSomething.prototype );

展開

我們可以給doSomething函數(shù)的原型對(duì)象添加新屬性,如下:

function doSomething(){}
doSomething.prototype.foo = "bar";
console.log( doSomething.prototype );

通過new操作符來創(chuàng)建基于這個(gè)原型對(duì)象的doSomething實(shí)例。

function doSomething(){}
doSomething.prototype.foo = "bar"; // add a property onto the prototype
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // add a property onto the object
console.log( doSomeInstancing );

接下來這段看似話很多,但是非常清晰,不繞??赐昃湍芎芡笍氐亩玩?層層向上。

如上所示, doSomeInstancing 中的_ _ proto _ _是 doSomething.prototype. 但這是做什么的呢?當(dāng)你訪問doSomeInstancing 中的一個(gè)屬性,瀏覽器首先會(huì)查看doSomeInstancing 中是否存在這個(gè)屬性。

如果 doSomeInstancing 不包含屬性信息, 那么瀏覽器會(huì)在 doSomeInstancing 的 _ _ proto _ _ 中進(jìn)行查找(同 doSomething.prototype). 如屬性在 doSomeInstancing 的 _ _ proto _ _ 中查找到,則使用 doSomeInstancing 中 _ _ proto _ _ 的屬性。

否則,如果 doSomeInstancing 中 _ _ proto _ _ 不具有該屬性,則檢查doSomeInstancing 的 _ _ proto _ _ 的 _ _proto _ _ 是否具有該屬性。默認(rèn)情況下,任何函數(shù)的原型屬性 _ _ proto _ _ 都是 window.Object.prototype. 因此, 通過doSomeInstancing 的 _ _ proto _ _ 的 _ _ proto _ _ ( 同 doSomething.prototype 的 _ _ proto _ _ (同 Object.prototype)) 來查找要搜索的屬性。

如果屬性不存在 doSomeInstancing 的 _ _ proto _ _ 的 _ _ proto _ _ 中, 那么就會(huì)在doSomeInstancing 的 _ _ proto _ _ 的 _ _ proto _ _ 的 _ _ proto _ _ 中查找。然而, 這里存在個(gè)問題:doSomeInstancing 的 _ _ proto _ _ 的 _ _ proto _ _ 的 _ _ proto _ _ 其實(shí)不存在。因此,只有這樣,在 _ _ proto _ _ 的整個(gè)原型鏈被查看之后,這里沒有更多的 _ _ proto _ _ , 瀏覽器斷言該屬性不存在,并給出屬性值為 undefined 的結(jié)論。

在控制臺(tái)窗口中輸入更多的代碼,如下:

function doSomething(){}
doSomething.prototype.foo = "bar";
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";
console.log("doSomeInstancing.prop:      " + doSomeInstancing.prop);
console.log("doSomeInstancing.foo:       " + doSomeInstancing.foo);
console.log("doSomething.prop:           " + doSomething.prop);
console.log("doSomething.foo:            " + doSomething.foo);
console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
console.log("doSomething.prototype.foo:  " + doSomething.prototype.foo);

結(jié)果如下:


其實(shí)我在這里一直一直想不通,我總覺得 doSomething.foo的結(jié)果應(yīng)該是bar。直到讀了犀牛書的第六章--對(duì)象,

通過new創(chuàng)建對(duì)象
new運(yùn)算符創(chuàng)建并初始化一個(gè)新對(duì)象。關(guān)鍵字new后跟隨一個(gè)函數(shù)調(diào)用。這里的函數(shù)稱做構(gòu)造函數(shù)(constructor),構(gòu)造函數(shù)用以初始化一個(gè)新創(chuàng)建的對(duì)象。

再翻上去看引用中的那段話,才能明白。function doSomething(){}是定義一個(gè)空構(gòu)造函數(shù)。doSomething.prototype指向其原型對(duì)象。doSomeInstancing()doSomething()的實(shí)例,是doSomething原型的繼承對(duì)象,也因此,doSomething.anything的答案都是undefined。doSomething.prototype才訪問到了它的原型對(duì)象。

掘金中有一篇文章講得非常非常好!鏈接 --> JS原型鏈與繼承別再被問倒了

我的個(gè)人感覺是:如果你看的不是很理解的話,八成是代碼寫少了,多從例子中體會(huì)。

最后編輯于
?著作權(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ù)。

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