前言
原文來自MDN JavaScript主題的高階教程部分,一共5篇铣缠。分別涉及繼承與原型烘嘱、嚴(yán)格模式、類型數(shù)組蝗蛙、內(nèi)存管理蝇庭、并發(fā)模型和事件循環(huán)。本篇是第一部分捡硅,關(guān)于繼承和原型遗契。
下面是正文部分:
對于熟悉基于類的編程語言(例如 Java 和 C++)的開發(fā)者來說,JavaScript 會讓他們感到困惑病曾,因?yàn)?JS 的動態(tài)性以及其本身并不提供class
的實(shí)現(xiàn)(ES2015 中提出的class
關(guān)鍵字僅僅是語法糖牍蜂,JS 仍然是基于原型的)
提到繼承,JavaScript 只有一個結(jié)構(gòu):對象(objects)泰涂。每個對象都有一個私有屬性鲫竞,該屬性鏈接到另一個對象(稱為該對象的原型(prototype))。這個原型對象自身也有一個原型逼蒙,直到一個對象的原型為null
从绘。根據(jù)定義,null
不存在原型,它代表這條原型鏈的終點(diǎn)僵井。
在 JavaScript 中陕截,幾乎所有對象都是Object
的實(shí)例,Object
在原型鏈頂端批什。
盡管這種困惑經(jīng)常被認(rèn)為是 JavaScript 的缺點(diǎn)农曲,但是這種原型式的繼承模型實(shí)際上比一些經(jīng)典的模型更為強(qiáng)大。例如驻债,在一個原型式模型的基礎(chǔ)上再構(gòu)造一個經(jīng)典模型是非常簡單的乳规。
通過原型鏈繼承
繼承屬性
JavaScript 對象就像一堆屬性的動態(tài)“包裹”(這堆屬性稱為對象自身屬性)(譯者注:原文為 JavaScript objects are dynamic "bags" of properties (referred to as own properties).)。
JavaScript 對象有一個指向原型對象的鏈接合呐。當(dāng)訪問一個對象的屬性時暮的,不僅會在該對象上查找,還會在該對象的原型淌实,以及這個原型的原型上查找冻辩,直到匹配上這個屬性名或者遍歷完該原型鏈。
根據(jù) ECMAScript 標(biāo)準(zhǔn)拆祈,
someObject.[[Prototype]]
用于指定someObject
的原型微猖。從 ECMAScript 2015 開始,[[Prototype]]
可以通過Object.getPrototypeOf()
和Object.setPrototypeOf()
訪問缘屹。這和通過 JavaScript 中的__proto__
訪問是一樣的凛剥,盡管這不標(biāo)準(zhǔn),但是已經(jīng)被很多瀏覽器所實(shí)現(xiàn)轻姿。
最好不要和函數(shù)的<code>func.prototype</code>屬性混淆犁珠。當(dāng)一個函數(shù)被當(dāng)做構(gòu)造器(constructor)調(diào)用時,會生成一個對象互亮,而函數(shù)上的<code>func.prototype</code>屬性引用的對象會作為生成對象的[[Prototype]]
存在犁享。Object.prototype
就表示了Object
這一函數(shù)的 prototype。
下面例子展示了訪問對象屬性的過程:
// 讓我們使用構(gòu)造函數(shù)f創(chuàng)建一個對象o豹休,o上面有屬性a和b:
let f = function () {
this.a = 1;
this.b = 2;
};
let o = new f(); // {a: 1, b: 2}
// 在f的prototype對象上添加一些屬性
f.prototype.b = 3;
f.prototype.c = 4;
// 不要對prototype重新賦值比如: f.prototype = {b:3,c:4}; 這會打斷原型鏈
// o.[[Prototype]] 上有屬性b和c
// o.[[Prototype]].[[Prototype]] 就是 Object.prototype
// 最終, o.[[Prototype]].[[Prototype]].[[Prototype]] 為 null
// 這就是原型鏈的終端, 等于 null,
// 根據(jù)定義, null不再有 [[Prototype]]
// 因此, 整條原型鏈看起來類似:
// {a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null
console.log(o.a); // 1
// o上存在自身屬性'a'嗎炊昆?當(dāng)然,該屬性值為1
console.log(o.b); // 2
// o上存在自身屬性'b'嗎威根?當(dāng)然凤巨,該屬性值為2
// prototype 上也有屬性'b', 但是并不會被訪問到
// 這叫做“屬性覆蓋”
console.log(o.c); // 4
// o上存在自身屬性'c'嗎?不存在, 繼續(xù)查找它的原型
// o.[[Prototype]]上存在自身屬性'c'嗎洛搀?當(dāng)然敢茁,該屬性值為4
console.log(o.d); // undefined
// o上存在自身屬性'd'嗎?不存在, 繼續(xù)查找它的原型
// o.[[Prototype]]上存在自身屬性'd'嗎留美?不存在, 繼續(xù)查找o.[[Prototype]]的原型
// o.[[Prototype]].[[Prototype]] 為 Object.prototype, 上面不存在屬性'd', 繼續(xù)查找o.[[Prototype]].[[Prototype]]的原型
// o.[[Prototype]].[[Prototype]].[[Prototype]] 為 null, 停止查找
// 沒找到屬性'd'彰檬,返回undefined
在一個對象上設(shè)置屬性稱為創(chuàng)建了一個”自身屬性“(譯者注:原文為Setting a property to an object creates an own property.)伸刃。唯一會影響屬性 set 和 get 行為的是當(dāng)該屬性使用getter 或者 setter定義。
繼承“方法”
JavaScript 中并沒有像在基于類語言中定義的”方法“逢倍。在 JavaScript 中捧颅,任何函數(shù)也是以屬性的形式被添加到對象中,繼承的函數(shù)和其他繼承的屬性一樣较雕,也存在上面提到的”屬性覆蓋”(這里叫做方法覆蓋(method overriding))碉哑。
當(dāng)一個繼承的函數(shù)被執(zhí)行時,函數(shù)內(nèi)的this
指向當(dāng)前繼承的對象郎笆,而不一定是將該函數(shù)作為“自身屬性“的對象本身谭梗。
var o = {
a: 2,
m: function () {
return this.a + 1;
},
};
console.log(o.m()); // 3
// 當(dāng)調(diào)用 o.m 時, 'this' 指向 o
var p = Object.create(o);
// p 是一個繼承o的對象
p.a = 4; // 在p上創(chuàng)建一個'a'屬性
console.log(p.m()); // 5
// 當(dāng)調(diào)用 p.m 時, 'this' 指向 p.
// 所以當(dāng) p 從 o 上繼承了方法 m時,
// 'this.a' 等于 p.a
在 JavaScript 中使用原型
讓我們更詳細(xì)地來看看背后的原理忘晤。
在 JavaScript 中宛蚓,正如上面提到,函數(shù)也可以擁有屬性设塔。所有函數(shù)都有一個特殊的屬性prototype
凄吏。請注意下面的代碼是獨(dú)立的(可以安全地假設(shè)網(wǎng)頁中除了下面的代碼就沒有其他代碼了)。為了更好的學(xué)習(xí)體驗(yàn)闰蛔,非常推薦你打開瀏覽器的控制臺痕钢,點(diǎn)擊'console'標(biāo)簽,復(fù)制粘貼以下代碼序六,點(diǎn)擊 Enter/Return 鍵來執(zhí)行它任连。(大多數(shù)瀏覽器的開發(fā)者工具(Developer Tools)中都包含控制臺。詳情請查看Firefox 開發(fā)者工具例诀、Chrome 開發(fā)者工具随抠,以及[Edge 開發(fā)者工具](Edge DevTools))
function doSomething() {}
console.log(doSomething.prototype);
// 不管你如何聲明函數(shù),
// JavaScript中的函數(shù)都有一個默認(rèn)的
// prototype 屬性
// (Ps: 這里有一個意外,箭頭函數(shù)上沒有默認(rèn)的 prototype 屬性)
var doSomething = function () {};
console.log(doSomething.prototype);
可以在 console 中看到繁涂,doSomething()
有一個默認(rèn)的prototype
屬性拱她,打印的內(nèi)容和下面類似:
{
constructor: ? doSomething(),
__proto__: {
constructor: ? Object(),
hasOwnProperty: ? hasOwnProperty(),
isPrototypeOf: ? isPrototypeOf(),
propertyIsEnumerable: ? propertyIsEnumerable(),
toLocaleString: ? toLocaleString(),
toString: ? toString(),
valueOf: ? valueOf()
}
}
如果我們在doSomething()
的prototype
上添加屬性,如下:
function doSomething() {}
doSomething.prototype.foo = "bar";
console.log(doSomething.prototype);
結(jié)果為:
{
foo: "bar",
constructor: ? doSomething(),
__proto__: {
constructor: ? Object(),
hasOwnProperty: ? hasOwnProperty(),
isPrototypeOf: ? isPrototypeOf(),
propertyIsEnumerable: ? propertyIsEnumerable(),
toLocaleString: ? toLocaleString(),
toString: ? toString(),
valueOf: ? valueOf()
}
}
現(xiàn)在我們可以通過new
操作符來基于這個 prototype 對象創(chuàng)建doSomething()
的實(shí)例扔罪。使用new
操作符調(diào)用函數(shù)只需要在調(diào)用前加上new
前綴秉沼。這樣該函數(shù)會返回其自身的一個實(shí)例對象。接著我們便可以往該實(shí)例對象上添加屬性:
function doSomething() {}
doSomething.prototype.foo = "bar"; // 往prototype上添加屬性'foo'
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // 往實(shí)例對象上添加屬性'prop'
console.log(doSomeInstancing);
打印結(jié)果和如下類似:
{
prop: "some value",
__proto__: {
foo: "bar",
constructor: ? doSomething(),
__proto__: {
constructor: ? Object(),
hasOwnProperty: ? hasOwnProperty(),
isPrototypeOf: ? isPrototypeOf(),
propertyIsEnumerable: ? propertyIsEnumerable(),
toLocaleString: ? toLocaleString(),
toString: ? toString(),
valueOf: ? valueOf()
}
}
}
可以得知矿酵,doSomeInstancing
的__proto__
就是doSomething.prototype
唬复。但是,這代表什么呢全肮?放你訪問doSomeInstancing
的一個屬性時盅抚,瀏覽器會首先查看doSomeInstancing
自身是否存在該屬性。
如果不存在倔矾,瀏覽器會繼續(xù)查找doSomeInstancing
的__proto__
(或者說是 doSomething.prototype
)妄均。如果存在柱锹,則doSomeInstancing
的__proto__
的這個屬性會被使用。
否則丰包,會繼續(xù)查找doSomeInstancing
的__proto__
的__proto__
禁熏。默認(rèn)情況下,任何函數(shù) prototype 屬性的__proto__
屬性就是window.Object.prototype
邑彪。因此瞧毙,會在doSomeInstancing
的__proto__
的__proto__
(或者說是doSomething.prototype.__proto__
,或者說是Object.prototype
)繼續(xù)查找對應(yīng)屬性寄症。
最終宙彪,直到所有的__proto__
被查找完畢,瀏覽器會斷言該屬性不存在有巧,因此得出結(jié)論:該屬性的值為 undefined释漆。
然我們在 console 上再添加一些代碼:
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é)果如下:
doSomeInstancing.prop: some value
doSomeInstancing.foo: bar
doSomething.prop: undefined
doSomething.foo: undefined
doSomething.prototype.prop: undefined
doSomething.prototype.foo: bar
使用不同的方法創(chuàng)建對象和原型鏈
使用語法結(jié)構(gòu)(字面量)創(chuàng)建對象
var o = { a: 1 };
// 新創(chuàng)建的對象以 Object.prototype 作為它的 [[Prototype]]
// o 沒有叫做'hasOwnProperty'的自身屬性
// hasOwnProperty 是 Object.prototype 的自身屬性
// 也就是說 o 從Object.prototype 上繼承了 hasOwnProperty
// Object.prototype 的原型為 null
// o ---> Object.prototype ---> null
var b = ["yo", "whadup", "?"];
// 數(shù)組繼承自 Array.prototype
// (Array.prototype 上擁有方法例如 indexOf, forEach 等等)
// 原型鏈如下:
// b ---> Array.prototype ---> Object.prototype ---> null
function f() {
return 2;
}
// 函數(shù)繼承自 Function.prototype
// (Function.prototype 上擁有方法例如 call, bind, 等等)
// f ---> Function.prototype ---> Object.prototype ---> null
使用構(gòu)造器函數(shù)
構(gòu)造器函數(shù)和普通函數(shù)的差別就在于其恰好使用new
操作符調(diào)用
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function (v) {
this.vertices.push(v);
},
};
var g = new Graph();
// g 是一個有 'vertices' 和 'edges' 作為屬性的對象
// 當(dāng)執(zhí)行 new Graph() 時,g.[[Prototype]] 的值就是 Graph.prototype
使用 Object.create
ECMAScript 提出了一個新方法:Object.create()
篮迎。調(diào)用該方法時會創(chuàng)建一個新對象璧亚。這個對象的原型為傳入該函數(shù)的第一個參數(shù):
var a = { a: 1 };
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (繼承自 a )
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty);
// undefined, 因?yàn)?d 并沒有繼承自 Object.prototype
與Object.create
和new
操作符一起裸扶,使用delete
操作符
下面的示例使用Object.create
創(chuàng)建一個對象,并使用delete
操作符來展示原型鏈的變化
var a = { a: 1 };
var b = Object.create(a);
console.log(a.a); // 1
console.log(b.a); // 1
b.a = 5;
console.log(a.a); // 1
console.log(b.a); // 5
delete b.a;
console.log(a.a); // 1
console.log(b.a); // 1(b.a 的值 5 已經(jīng)被刪除,因此展示其原型鏈上的值)
delete a.a; // 也可以使用 'delete b.__proto__.a'
console.log(a.a); // undefined
console.log(b.a); // undefined
如果換成new
操作符創(chuàng)建對象罪治,原型鏈更短:
function Graph() {
this.vertices = [4, 4];
}
var g = new Graph();
console.log(g.vertices); // print [4,4]
g.vertices = 25;
console.log(g.vertices); // print 25
delete g.vertices;
console.log(g.vertices); // print undefined
使用 class 關(guān)鍵字
ECMAScript 2015 提出了一系列新的關(guān)鍵字用于實(shí)現(xiàn)類牡彻。包括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);
關(guān)于性能
如果需要查找的對象屬性位于原型鏈的頂端乃戈,查找時間會對性能有影響,尤其對于對性能要求很高的應(yīng)用來說堰乔,影響會進(jìn)一步放大偏化。另外,如果是訪問一個不存在的屬性镐侯,總是會遍歷整條原型鏈侦讨。
此外,當(dāng)對對象的屬性進(jìn)行迭代查找時苟翻,原型鏈上所有可枚舉的屬性都會被遍歷韵卤。為了檢查哪些屬性是對象的自身屬性而不是來自其原型鏈,很有必要使用繼承自Object.prototype
的hasOwnProperty
方法崇猫。下面來看一個具體的例子沈条,該例子繼續(xù)使用上一個圖形的例子:
console.log(g.hasOwnProperty("vertices"));
// true
console.log(g.hasOwnProperty("nope"));
// false
console.log(g.hasOwnProperty("addVertex"));
// false
console.log(g.__proto__.hasOwnProperty("addVertex"));
// true
hasOwnProperty
是 JavaScript 中查找對象屬性時唯一不遍歷原型鏈的方法。
注意:僅僅檢查屬性是undefined
并不能代表該屬性不存在诅炉,也許是因?yàn)樗闹登『帽辉O(shè)置為了undefined
蜡歹。
不好的實(shí)踐:對原生的 prototypes 進(jìn)行擴(kuò)展
經(jīng)常容易犯的一個錯誤是擴(kuò)展Object.prototype
或者是一些其他內(nèi)置的 prototype屋厘。
這被稱為是”猴子補(bǔ)丁“,會打破程序的封裝性月而。盡管在一些出名的框架中也這樣做汗洒,例如 Prototype.js,但是仍然沒有理由在內(nèi)置類型上添加非標(biāo)準(zhǔn)的功能父款。
擴(kuò)展內(nèi)置類型的唯一理由是保證一些早期 JavaScript 引擎的兼容性溢谤,例如Array.forEach
(譯者注:Array.forEach
是在 ECMA-262-5 中提出,部分早期瀏覽器引擎沒有實(shí)現(xiàn)該標(biāo)準(zhǔn)憨攒,因此需要 polyfill)
繼承原型鏈的方法總結(jié)
下面表格展示了四種方法以及它們各自的優(yōu)缺點(diǎn)世杀。以下例子創(chuàng)建的inst
對象完全一致(因此控制臺打印的結(jié)果也一樣),除了它們之間有不同的優(yōu)缺點(diǎn)肝集。
<table>
<tr>
<th>名稱</th>
<th>舉例</th>
<th>優(yōu)點(diǎn)</th>
<th>缺點(diǎn)</th>
</tr>
<tr>
<td>使用<code>new</code>初始化</td>
<td>
<pre lang="javascript">
function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
function bar(){}
var proto = new foo;
proto.bar_prop = "bar val";
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);
</pre>
</td>
<td>支持所有瀏覽器(甚至到IE 5.5)瞻坝,同時,運(yùn)行速度包晰、標(biāo)準(zhǔn)化以及JIT優(yōu)化性都非常好</td>
<td>
問題是湿镀,為了使用該方法函數(shù)必須被初始化炕吸。在初始化過程中伐憾,構(gòu)造函數(shù)可能會為每個創(chuàng)建對象創(chuàng)建一些特有屬性,然而例子中只會構(gòu)造一次赫模,因此這些特有信息只會生成一次树肃,可能存導(dǎo)致潛在問題。
之外瀑罗,構(gòu)造函數(shù)初始化時可能會添加冗余的方法到實(shí)例對象上胸嘴。不過,只要這是你自己的代碼且你明確這是干什么的斩祭,這些通常來說也不是問題(實(shí)際上是利大于弊)劣像。
</td>
</tr>
<tr>
<td>使用<code>Object.create</code></td>
<td>
<pre lang="javascript">
function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
function bar(){}
var proto = Object.create(
foo.prototype
);
proto.bar_prop = "bar val";
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);
</pre>
<pre lang="javascript">
function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
function bar(){}
var proto = Object.create(
foo.prototype,
{
bar_prop: {
value: "bar val"
}
}
);
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop)
</pre>
</td>
<td>支持目前所有的現(xiàn)代瀏覽器,包括非IE瀏覽器以及IE9及以上版本瀏覽器摧玫。相當(dāng)于允許一次性設(shè)置<code>proto</code>耳奕,這樣有利于瀏覽器優(yōu)化該對象。同時也允許創(chuàng)建沒有原型的對象例如:<code>Object.create(null)</code></td>
<td>
不支持IE8以及以下版本瀏覽器诬像,不過屋群,微軟目前已不再支持運(yùn)行這些瀏覽器的操作系統(tǒng),對大多數(shù)應(yīng)用來說這也不是一個問題坏挠。
之外芍躏,如果使用第二個參數(shù),則對象的初始化會變慢降狠,這也許會成為性能瓶頸对竣,因?yàn)榈诙€參數(shù)作為對象描述符屬性庇楞,每個對象的描述符屬性是另一個對象。當(dāng)以對象形式處理成千上萬的對象描述符時否纬,可能會嚴(yán)重影響運(yùn)行速度姐刁。
</td>
</tr>
<tr>
<td>使用<code>Object.setPrototypeOf</code></td>
<td>
<pre lang="javascript">
function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
function bar(){}
var proto = {
bar_prop: "bar val"
};
Object.setPrototypeOf(
proto, foo.prototype
);
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);
</pre>
<pre lang="javascript">
function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
function bar(){}
var proto;
proto = Object.setPrototypeOf(
{ bar_prop: "bar val" },
foo.prototype
);
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop)
</pre>
</td>
<td>支持目前所有的現(xiàn)代瀏覽器,包括非IE瀏覽器以及IE9及以上版本瀏覽器烦味。支持動態(tài)的操作對象的原型聂使,甚至可以為<code>Object.create(null)</code>創(chuàng)建的對象強(qiáng)制添加一個原型</td>
<td>
由于性能不佳,應(yīng)該會被棄用谬俄。如果你敢在生產(chǎn)環(huán)境中使用這樣的語法柏靶,JavaScript代碼快速運(yùn)行幾乎不可能。因?yàn)樵S多瀏覽器優(yōu)化了原型溃论,舉個例子屎蜓,在訪問一個對象上的屬性之前,編譯器會提前確定原型上的屬性在內(nèi)存中的位置钥勋,但是如果使用了<code>Object.setPrototypeOf</code>對原型進(jìn)行動態(tài)更改炬转,這相當(dāng)于擾亂了優(yōu)化,甚至?xí)尵幾g器重新編譯并放棄對這部分的優(yōu)化算灸,僅僅是為了能讓你這段代碼跑起來扼劈。
同時,不支持IE8以及以下版本瀏覽器
</td>
</tr>
<tr>
<td>使用<code>proto</code></td>
<td>
<pre lang="javascript">
function foo(){}
foo.prototype = {
foo_prop: "foo val"
};
function bar(){}
var proto = {
bar_prop: "bar val",
proto: foo.prototype
};
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);
</pre>
<pre lang="javascript">
var inst = {
proto: {
bar_prop: "bar val",
proto: {
foo_prop: "foo val",
proto: Object.prototype
}
}
};
console.log(inst.foo_prop);
console.log(inst.bar_prop)
</pre>
</td>
<td>支持目前幾乎所有的現(xiàn)代瀏覽器菲驴,包括非IE瀏覽器以及IE11及以上版本瀏覽器荐吵。將<code>proto</code>設(shè)置為非對象的類型不會拋出異常,但是會導(dǎo)致程序運(yùn)行失敗</td>
<td>
嚴(yán)重過時而且性能不佳赊瞬。如果你敢在生產(chǎn)環(huán)境中使用這樣的語法先煎,JavaScript代碼快速運(yùn)行幾乎不可能。因?yàn)樵S多瀏覽器優(yōu)化了原型巧涧,舉個例子薯蝎,在訪問一個對象上的屬性之前,編譯器會提前確定原型上的屬性在內(nèi)存中的位置谤绳,但是如果使用了<code>proto</code>對原型進(jìn)行動態(tài)更改占锯,這相當(dāng)于擾亂了優(yōu)化,甚至?xí)尵幾g器重新編譯并放棄對這部分的優(yōu)化闷供,僅僅是為了能讓你這段代碼跑起來烟央。
同時,不支持IE10及以下版本瀏覽器歪脏。
</td>
</tr>
</table>
prototype
和Object.getPrototypeOf
對于從 Java 和 C++過來的開發(fā)者來說疑俭,JavaScript 會讓他們感到有些困惑,因?yàn)?JavaScript 是動態(tài)類型婿失、代碼無需編譯可以在 JS Engine 直接運(yùn)行(譯者注:Java 代碼需要編譯成機(jī)器碼后在 JVM 執(zhí)行)钞艇,同時它還沒有類啄寡。所有的幾乎都是實(shí)例(objects)。盡管模擬了class
哩照,但其本質(zhì)還是函數(shù)對象挺物。
你也許注意到了function A
上有一個特殊的屬性prototype
。這個特殊屬性與 JavaScriptnew
操作符一起使用飘弧。當(dāng)使用new
操作符創(chuàng)建出來一個實(shí)例對象识藤,這個特殊屬性prototype
會被復(fù)制給該對象的內(nèi)部[[Prototype]]
屬性。舉個例子次伶,當(dāng)運(yùn)行var a1 = new A()
代碼時痴昧,JavaScript(在內(nèi)存中創(chuàng)建完新實(shí)例對象之后且準(zhǔn)備運(yùn)行函數(shù)A()
之前,運(yùn)行函數(shù)時函數(shù)內(nèi)部的this
會指向該對象)會設(shè)置:a1.[[Prototype]] = A.prototype
冠王。
當(dāng)你之后訪問創(chuàng)建的對象屬性時赶撰,JavaScript 首先會檢查屬性是否存在于對象本身,如果不存在柱彻,則繼續(xù)查找其[[Prototype]]
豪娜。這意味著你在prototype
上定義的屬性實(shí)際上被所有實(shí)例對象共享,如果你愿意哟楷,甚至可以修改prototype
瘤载,這些改動會同步到所有存在的實(shí)例對象中。
如果在上面的例子中吓蘑,你執(zhí)行:var a1 = new A(); var a2 = new A();
惕虑,那么a1.doSomething
就是Object.getPrototypeOf(a1).doSomething
坟冲,這和你定義的A.prototype.doSomething
是同一個對象磨镶,所以:Object.getPrototypeOf(a1).doSomething === Object.getPrototypeOf(a2).doSomething === A.prototype.doSomething
。
簡而言之健提,prototype
是針對類型的琳猫,而Object.getPrototypeOf()
對于實(shí)例對象是一致的。(譯者注:原文為In short, prototype
is for types, while Object.getPrototypeOf()
is the same for instances.)私痹。
[[Prototype]]
會被遞歸地查找脐嫂,例如:a1.doSomething
, Object.getPrototypeOf(a1).doSomething
, Object.getPrototypeOf(Object.getPrototypeOf(a1)).doSomething
等等,直到Object.getPrototypeOf
返回null
紊遵。
因此账千,當(dāng)你執(zhí)行:
var o = new Foo();
實(shí)際上是執(zhí)行:
var o = new Object();
o[[Prototype]] = Foo.prototype;
Foo.call(o);
接著如果你訪問:
o.someProp;
JavaScript 會檢查是否 o 上存在自身屬性someProp
。如果不存在暗膜,繼續(xù)檢查Object.getPrototypeOf(o).someProp
是否存在匀奏,如果還不存在繼續(xù)檢查Object.getPrototypeOf(Object.getPrototypeOf(o)).someProp
,依次類推学搜。
總結(jié)
在編寫基于原型的復(fù)雜代碼之前娃善,很有必要先理解原型式的繼承模型论衍。同時,請注意代碼中原型鏈的長度聚磺,并且在必要時將其分解以避免可能存在的性能問題坯台。此外,應(yīng)該杜絕在原生的原型對象上進(jìn)行擴(kuò)展瘫寝,除非是為了考慮兼容性蜒蕾,例如在老的 JavaScript 引擎上適配新的語言特性。
Tags: Advanced
Guide Inheritance JavaScript OOP
本篇文章由一文多發(fā)平臺ArtiPub自動發(fā)布