【譯】繼承與原型鏈(Inheritance and the prototype chain)

前言

原文來自MDN JavaScript主題的高階教程部分,一共5篇铣缠。分別涉及繼承與原型烘嘱、嚴(yán)格模式、類型數(shù)組蝗蛙、內(nèi)存管理蝇庭、并發(fā)模型和事件循環(huán)。本篇是第一部分捡硅,關(guān)于繼承和原型遗契。

原文鏈接請點(diǎ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.createnew操作符一起裸扶,使用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友存、staticextends以及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.prototypehasOwnProperty方法崇猫。下面來看一個具體的例子沈条,該例子繼續(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>


prototypeObject.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ā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末焕阿,一起剝皮案震驚了整個濱河市滥搭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捣鲸,老刑警劉巖瑟匆,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異栽惶,居然都是意外死亡愁溜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門外厂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冕象,“玉大人,你說我怎么就攤上這事汁蝶〗グ纾” “怎么了?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵掖棉,是天一觀的道長墓律。 經(jīng)常有香客問我,道長幔亥,這世上最難降的妖魔是什么耻讽? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮帕棉,結(jié)果婚禮上针肥,老公的妹妹穿的比我還像新娘。我一直安慰自己香伴,他們只是感情好慰枕,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著即纲,像睡著了一般具帮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天匕坯,我揣著相機(jī)與錄音束昵,去河邊找鬼。 笑死葛峻,一個胖子當(dāng)著我的面吹牛锹雏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播术奖,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼礁遵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了采记?” 一聲冷哼從身側(cè)響起佣耐,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唧龄,沒想到半個月后兼砖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡既棺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年讽挟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丸冕。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡耽梅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胖烛,到底是詐尸還是另有隱情眼姐,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布佩番,位于F島的核電站众旗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏答捕。R本人自食惡果不足惜逝钥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拱镐。 院中可真熱鬧,春花似錦持际、人聲如沸沃琅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽益眉。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間郭脂,已是汗流浹背年碘。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留展鸡,地道東北人屿衅。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像莹弊,于是被迫代替她去往敵國和親涤久。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容