朝花夕拾,從新介紹繼承與原型鏈

JavaScript 是動態的,而且自己不提供一個 class 實現。(在 ES2015/ES6 中引入了 class 關鍵字,但那只是語法糖,JavaScript 仍然是基於原型的)。javascript

只有對象類型纔有繼承與原型概念

不斷向上追溯的原型共同組成了原型鏈前端

  • 每一個實例對象( object )都有一個私有屬性(稱之爲 __proto__ )指向它的構造函數的原型對象(prototype )。
  • 該原型對象也有一個本身的原型對象( __proto__ ) ,層層向上直到一個對象的原型對象爲 null
  • 根據定義,null 沒有原型,並做爲這個原型鏈中的最後一個環節。
  • 幾乎全部 JavaScript 中的對象都是位於原型鏈頂端的 Object 的實例。

上面的解釋可能有些繞,看完下面應該就會清晰一些。java

基於原型鏈的繼承

繼承屬性

JavaScript 對象是動態的屬性「包」(指其本身的屬性)。JavaScript 對象有一個指向一個原型對象的鏈。當試圖訪問一個對象的屬性時,它不單單在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。函數

這裏演示當嘗試訪問屬性時會發生什麼:post

// 讓咱們從一個自身擁有屬性a和b的構造函數裏建立一個對象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}

console.log(o) // f { a: 1, b: 2 }

// 指向構造函數
console.log(o.constructor) 
// function () {
// this.a = 1;
// this.b = 2;
// } 

// 實例沒有原型對象
console.log(o.prototype) // undefined

//構造函數的原型對象
console.log(o.constructor.prototype, f.prototype)  f {} f {}

// 實例的__proto__指向構造函數的原型對象
console.log('o.proto', o.__proto__, typeof o.__proto__) // f {} object


複製代碼

綜上,整個原型鏈以下:ui

如今,下面四條就很清晰明確了this

  • 每一個實例對象( object )都有一個私有屬性(稱之爲 __proto__ )指向它的構造函數的原型對象(prototype )。
  • 該原型對象也有一個本身的原型對象( __proto__ ) ,層層向上直到一個對象的原型對象爲 null
  • 根據定義,null 沒有原型,並做爲這個原型鏈中的最後一個環節。
  • 不斷向上追溯的原型共同組成了原型鏈
  • 對象的原型爲__proto__屬性
  • 函數有2個屬性,一個是是__proto__,還有一個是函數專有的prototype屬性,由於函數有雙重身份,便可以是實例也能夠是構造器
  • Object.prototype{}
  • 箭頭函數雖然屬於函數,由Function產生,可是沒有prototype屬性沒有構造器特性,因此也就沒有所謂的constructor,不能做爲構造器使用

下面對構造函數的原型作些變動spa

// 在f函數的原型上定義屬性
f.prototype.b = 3;
f.prototype.c = 4;
複製代碼

不要在 f 函數的原型上直接定義對象, f.prototype = {b:3,c:4}; 這樣會直接打破原型鏈prototype

// 實例的__proto__指向構造函數的原型對象
console.log('o.proto', o.__proto__) // f { b: 3, c: 4 }
複製代碼

變動以後的原型鏈以下:code

// 遵循ECMAScript標準,someObject.[[Prototype]] 符號是用於指向 someObject 的原型。

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

console.log(o.b); // 2
// b是o的自身屬性嗎?是的,該屬性的值爲 2
// 原型上也有一個'b'屬性,可是它不會被訪問到。
// 這種狀況被稱爲"屬性遮蔽 (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
複製代碼

繼承方法

JavaScript 並無其餘基於類的語言所定義的「方法」。在 JavaScript 裏,任何函數均可以添加到對象上做爲對象的屬性。函數的繼承與其餘的屬性繼承沒有差異,包括上面的「屬性遮蔽」(這種狀況至關於其餘語言的方法重寫)。

當繼承的函數被調用時,this 指向的是當前繼承的對象,而不是繼承的函數所在的原型對象。

ECMAScript 5 中引入了一個新方法:Object.create()。能夠調用這個方法來建立一個新對象。新對象的原型就是調用 create 方法時傳入的第一個參數:

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

console.log(o.m()); // 3
// 當調用 o.m 時,'this' 指向了 o.

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

p.a = 4; // 建立 p 的自身屬性 'a'
console.log(p.m()); // 5
// 調用 p.m 時,'this' 指向了 p
// 又由於 p 繼承了 o 的 m 函數
// 因此,此時的 'this.a' 即 p.a,就是 p 的自身屬性 'a' 
複製代碼

相關係列: 從零開始的前端築基之旅(超級精細,持續更新~)

若是你收穫了新知識,請給做者點個贊吧~

參考文檔:

MDN:繼承與原型鏈

相關文章
相關標籤/搜索