JavaScript的繼承與原型鏈詳解

JavaScript的繼承與原型鏈詳解

對於有基於類的語言經驗 (如 Java 或 C++) 的開發人員來說,JavaScript 有點令人困惑,因為它是動態的,並且本身不提供一個class實現。(在 ES2015/ES6 中引入了class關鍵字,但只是語法糖,JavaScript 仍然是基於原型的)。

JavaScript 只有一種繼承結構:對象。每個實例對象(object )都有一個私有屬性(稱之為[[prototype]])指向它的原型對象(prototype)。該原型對象也有一個自己的原型對象 ,層層向上直到一個對象的原型對象為 null。根據定義,null 沒有原型,並作為這個原型鏈中的最後一個環節。

幾乎所有 JavaScript 中的對象都是位於原型鏈頂端的Object的實例。

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

但它不應該與構造函數 func 的 prototype 屬性相混淆。被構造函數創建的實例對象的 [[prototype]] 指向 func 的 prototype 屬性。Object.prototype 屬性表示Object的原型對象。

// 讓我們從一個自身擁有屬性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}
// 在f函數的原型上定義屬性
f.prototype.b = 3;
f.prototype.c = 4;
//不要在f函數的原型上直接定義 f.prototype = {b:3,c:4};這樣會直接打破原型鏈
// o.[[Prototype]] 有屬性 b 和 c (其實就是o.__proto__或者o.constructor.prototype)
// o.[[Prototype]].[[Prototype]] 是 Object.prototye.
// 最後o.[[Prototype]].[[Prototype]].[[Prototype]]是null
// 這就是原型鏈的末尾,即 null,
// 根據定義,null 沒有[[Prototype]].
// 綜上,整個原型鏈如下:
// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototye---> null
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

當繼承的函數被調用時,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是一個繼承自 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 的屬性
// hasOwnProperty 是 Object.prototype 的屬性
// 因此 o 繼承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型為 null
// 原型鏈如下:
// o ---> Object.prototype ---> null
var a = ["yo", "whadup", "?"];
// 數組都繼承於 Array.prototype

// (Array.prototype 中包含 indexOf, forEach等方法)
// 原型鏈如下:
// a ---> Array.prototype ---> Object.prototype ---> null
function f(){
return 2;
}
// 函數都繼承於Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型鏈如下:
// f ---> Function.prototype ---> Object.prototype ---> null

在 JavaScript 中,構造器其實就是一個普通的函數。當使用 new 操作符 來作用這個函數時,它就可以被稱為構造方法(構造函數)。

function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertices.push(v);
}
};
var g = new Graph();
// g是生成的對象,他的自身屬性有'vertices'和'edges'.
// 在g被實例化時,g.[[Prototype]]指向了Graph.prototype.

ECMAScript 5 中引入了一個新方法:Object.create()。可以調用這個方法來創建一個新對象。新對象的原型就是調用 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

ECMAScript6 引入了一套新的關鍵字用來實現 class。使用基於類語言的開發人員會對這些結構感到熟悉,但它們是不同的。JavaScript 仍然基於原型。這些新的關鍵字包括 class, constructor,static,extends 和 super。

"use strict";
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
var square = new Square(2);


分享到:


相關文章: