1. 程式人生 > >javascript的繼承與原型鏈

javascript的繼承與原型鏈

show array this 需要 子類 行為 依次 ann struct

由於js功底一直很差勁,所以覺得自己很多基礎知識都不牢靠,現在花點時間了解下,盡管完成這篇博文我可能還是沒有了如指掌,但是記錄下來至少我還會再三翻閱的,並加深印象或者獲得與現在不一樣的理解。

什麽是繼承?

往往很多代碼你已經完成構造後,又需要構造另一個與之雷同的功能函數;很多類似的舉例比如:交通工具/汽車,Person/man,建築物/學校,等;個人理解成:繼承?子類繼承父類?一個對象復制另一個對象?差不多,這樣容易理解,類與類之間的復制(覆蓋)行為,一個對象擁有另一個對象的屬性和方法。這也解決了如何實現繼承的思路及邏輯。

什麽是原型鏈?

原型?我簡單理解成===> Object.prototype

某個對象的一個屬性,外界一般是不可見(在chrome中可以通過__proto__獲取),我們一般把它叫作[[Prototype]]。原型鏈?obj1.[[Prototype]] ===> obj2.[[Prototype]] ===> obj3.[[Prototype]]…. ===> Object.prototype。

基於原型鏈的繼承

繼承屬性

js對象是動態的屬性包(有自己的屬性),有一個指向一個原型對象的鏈。當試圖訪問一個對象的屬性時,它不僅僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。如上面。

遵循ECMAScript標準,someObject.[[Prototype]] 符號是用於指向 someObject的原型。從 ECMAScript 6 開始,[[Prototype]] 可以用Object.getPrototypeOf()和Object.setPrototypeOf()訪問器來訪問。這個等同於 JavaScript 的非標準但許多瀏覽器實現的屬性 __proto__。

它不應該與函數(function)的func.prototype屬性相混淆,func.prototype的作用是使用 new func() 創建的對象的實例的 [[Prototype]]。Object.prototype屬性表示Object的原型對象。

舉個栗子(嘗試訪問屬性時會發生什麽?將屬性設置為對象將創建自己的屬性。獲取和設置屬性的唯一限制是內置 getter 或 setter 的屬性。):

// 讓我們假設我們有一個對象 o, 其有自己的屬性 a 和 b:
// {a: 1, b: 2}
// o 的原型 o.__proto__有屬性 b 和 c:
// {b: 3, c: 4}
// 最後, o.__proto__.__proto__ 是 null.
// 這就是原型鏈的末尾,即 null,
// 根據定義,null 沒有__proto__.
// 綜上,整個原型鏈如下: 
// {a:1, b:2} ---> {b:3, c:4} ---> null

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

console.log(o.b); // 2
// b是o的自身屬性嗎?是的,該屬性的值為2
// o.__proto__上還有一個‘b‘屬性,但是它不會被訪問到.這種情況稱為"屬性遮蔽 (property shadowing)".

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

console.log(o.d); // undefined
// d是o的自身屬性嗎?不是,那看看o.__proto__上有沒有.
// d是o.__proto__的自身屬性嗎?不是,那看看o.__proto__.__proto__上有沒有.
// o.__proto__.__proto__為null,停止搜索,
// 沒有d屬性,返回undefined

繼承方法

在 JavaScript 裏,任何函數都可以添加到對象上作為對象的屬性。函數的繼承與其他的屬性繼承沒有差別,包括上面的“屬性覆蓋”(這種情況相當於其他語言的方法重寫)。

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

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是一個對象, p.__proto__是o.

p.a = 4; // 創建 p 的自身屬性a.
console.log(p.m()); // 5
// 調用 p.m 時, ‘this‘指向 p. 
// 又因為 p 繼承 o 的 m 函數
// 此時的‘this.a‘ 即 p.a,即 p 的自身屬性 ‘a‘

使用不同的方法來創建對象和生成原型鏈

下面的方法就簡單舉個例子便於自己理解

普通

var o = {a: 1};

// o這個對象繼承了Object.prototype上面的所有屬性
// 所以可以這樣使用 o.hasOwnProperty(‘a‘).
// hasOwnProperty 是Object.prototype的自身屬性。
// Object.prototype的原型為null。
// 原型鏈如下:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];

// 數組都繼承於Array.prototype 
// (indexOf, forEach等方法都是從它繼承而來).
// 原型鏈如下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// 函數都繼承於Function.prototype
// (call, bind等方法都是從它繼承而來):
// f ---> Function.prototype ---> Object.prototype ---> null

構造器(即new)

new僅僅是函數的調用一種方式,

用new來調用函數有什麽不同的呢?new其實做了三件事:

  1. 創建一個新對象
  2. 將這個新對象的[[Prototype]]連接到調用函數的prototype
  3. 綁定調用函數的this並調用

這樣能解決很多思路比如先舉個簡單易懂的栗子:

function A(){
}
var b = {
   show:function(){
       console.log("這是b的show")
    }
}
//怎樣讓對象a和對象b的__proto__相連實現a繼承b?
//a的“構造函數”的[[prototype]]鏈接b
A.prototype = b;
A.prototype.construtor = A;
var a = new A();//已經實現繼承了;
a.show();

樓下與樓上互不聯系,只是多舉一例而已

function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertices.push(v);
  }
};

var g = new Graph();
// g是生成的對象,他的自身屬性有‘vertices‘和‘edges‘.
// 在g被實例化時,g.__proto__指向了Graph.prototype.

Object.create

var a = {a: 1}; 
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (繼承而來)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因為d沒有繼承Object.prototype

模擬類繼承

/**
 * 實現 A 繼承 B
 */
function B(b) {
    this.b = b
}
function A(a, b) {
    // 調用B並綁定this
    B.call(this, b)
    this.a = a
}
A.prototype = Object.assign({}, B.prototype)
A.prototype.constructor = A
var c = new A(1, 2)
console.log(c.a) // 1
// c 擁有了只有B的實例才擁有的 b 屬性
console.log(c.b) // 2

javascript的繼承與原型鏈