如下翻译英文原版: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
Javascript是一种基于类的语言(像Java,C++),对于开发者来说是有一点费解.因为它是动态的,却未提供一个类的实现本质(一个在ES2015介绍的类关键字,而不是语法糖,Javascript保留了基于原型的特性).
当一出现继承,Javascript仅有一种结构:对象.每个对象有一个私有属性(叫做[[Prototype]]),可以与另一个对象通过它的 prototype来保持一个连接的关系.原型对象都有自己独有的prototype.每个对象都会层层遍历,直到prototype为null.从定义上来讲,null是没有prototiype的.这就构成了最终的原型链.
差不多所有的Javascript对象都是先从原型链的top曾开始访问.当原型继承模型常被看作是Javascript的弱类型.事实上比类模型要更强大.比如说,经典模型可以很平常地创建在原型继承模型顶部.
一原型链的继承性
1(1)继承属性
javascript对象是具备动态包属性(也通常被看作是自己的属性).Javascript对象通常连接到它的prototype对象.当我们访问一个对象的属性.不仅仅只是找此对象的属性而且会找对象的原型.原型的原型一直找到属性的名字匹配能匹配上或者直到原型链的最末尾.
跟着ECMAScript的标准.用作标记一些对象.[[prototype]]被用来标记一些对象的原型.[[prototype]]一般同过object.getPrototypeOf()和object.setPrototypeOf()来访问.此方法相当于Javascript的__proto__属性.虽然有点非标准.但是可以被许多浏览器实现.通过使用作为一个构造器来给一个[[prototype]]的所有对象的实例赋值.这跟函数的func.prototype属性是不可以混淆的.
1 (2)获取属性的例子如下:
// Let's assume we have object o, with its own properties a and b:
// {a: 1, b: 2}
// o.[[Prototype]] has properties b and c:
// {b: 3, c: 4}
// Finally, o.[[Prototype]].[[Prototype]] is null.
// This is the end of the prototype chain, as null,
// by definition, has no [[Prototype]].
// Thus, the full prototype chain looks like:
// {a: 1, b: 2} ---> {b: 3, c: 4} ---> null
console.log(o.a); // 1
// Is there an 'a' own property on o? Yes, and its value is 1.
console.log(o.b); // 2
// Is there a 'b' own property on o? Yes, and its value is 2.
// The prototype also has a 'b' property, but it's not visited.
// This is called "property shadowing."
console.log(o.c); // 4
// Is there a 'c' own property on o? No, check its prototype.
// Is there a 'c' own property on o.[[Prototype]]? Yes, its value is 4.
console.log(o.d); // undefined
// Is there a 'd' own property on o? No, check its prototype.
// Is there a 'd' own property on o.[[Prototype]]? No, check its prototype.
// o.[[Prototype]].[[Prototype]] is null, stop searching,
// no property found, return undefined.
设置一个对象的属性赋值,这样就创造了新的属性.当有一个可以继承的setter或者getter属性,是一个值得期待的规范规则.
1(3)继承方法Javascript在基于类的定义的形式下没有所谓的方法.在Javascript里.任何的函数可以以属性的形式添加到对象中.
当一个可继承的函数被调用.它的值就会指向可继承对象.而不是具备函数独有属性的原型对象.
var o = {
a: 2,
m: function() {
return this.a + 1;
}
};
console.log(o.m()); // 3
// When calling o.m in this case, 'this' refers to o
var p = Object.create(o);
// p is an object that inherits from o
p.a = 4; // creates an own property 'a' on p
console.log(p.m()); // 5
// when p.m is called, 'this' refers to p.
// So when p inherits the function m of o,
// 'this.a' means p.a, the own property 'a' of p
2.不同的方式创建对象和原型链的结果
2(1)通过语法构造器创建对象
var o = {a: 1};
// The newly created object o has Object.prototype as its [[Prototype]]
// o has no own property named 'hasOwnProperty'
// hasOwnProperty is an own property of Object.prototype.
// So o inherits hasOwnProperty from Object.prototype
// Object.prototype has null as its prototype.
// o ---> Object.prototype ---> null
var b = ['yo', 'whadup', '?'];
// Arrays inherit from Array.prototype
// (which has methods indexOf, forEach, etc.)
// The prototype chain looks like:
// b ---> Array.prototype ---> Object.prototype ---> null
function f() {
return 2;
}
// Functions inherit from Function.prototype
// (which has methods call, bind, etc.)
// f ---> Function.prototype ---> Object.prototype ---> null
2(2)用构造器创建
一个构造器在Javascript里仅仅是一个通过新操作可以被调用的函数.
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v) {
this.vertices.push(v);
}
};
var g = new Graph();
// g is an object with own properties 'vertices' and 'edges'.
// g.[[Prototype]] is the value of Graph.prototype when new Graph() is executed.
2(3) 通过Object.create()
ECMAScrpt5 介绍了一个新的方法object.create().通过调用这个方法可以创建一个新的对象.这个对象的原型是这个function的第一个参数.
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (inherited)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty);
// undefined, because d doesn't inherit from Object.prototype
2(3)通过类关键字
ECMAScript2015介绍了新的关键字集合来实现Classes.尽管这些构造器对那些从事基于类的语言的开发者来说是有些类似.但它们是不同的.Javascript保留了基于原型.新的关键字包括.Class,构造器,static,extends和supper.
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);
3(1)在原型链高处的那些属性的查询时间在性能上有负面的影响.这可能很重要在哪些较挑剔的代码上.另外,尝试访问不存在的属性将会贯穿整个原型链.
同时,当重复获取一个对象的属性.每个在原型链的枚举器将会产生枚举计数.为了检查一个对象的属性是否在其自身而不是在原型链的某一处.所以很有必要使用object.hasOwnProperty()方法,此方法继承自object.prototype.hasOwnProperty()是Javascriptl里唯一的方法可以处理属性是否横穿在整个原型链.
注意:检查一个属性是否undefined是能够的.这个属性可能真的存在,但恰好此属性的值被设置为undefined.
4.坏练习:原生原型的扩展
性能不足的对象常使用扩展object.prototype或者在其它对象上重建其属性.这个技术被叫做猴子补丁和解封装.当使用流行的框架.像prototype.js对于增加内置类型的非标函数来说没有更好不用的理由了.对于扩充的原型来说.将更新的Javascript特性应用到像Array,ForEach是唯一的好理由.
例如:
B应该继承自A
function A(a) {
this.varA = a;
}
A.prototype = {
varA: null, // Shouldn't we strike varA from the prototype as doing nothing?
doSomething: function() {
// ...
}
};
function B(a, b) {
A.call(this, a);
this.varB = b;
}
B.prototype = Object.create(A.prototype, {
varB: {
value: null,
enumerable: true,
configurable: true,
writable: true
},
doSomething: {
value: function() { // override
A.prototype.doSomething.apply(this, arguments); // call super
// ...
},
enumerable: true,
configurable: true,
writable: true
}
});
B.prototype.constructor = B;
var b = new B();
b.doSomething();
最重要的部分:
类型必须定义在.prototype中.
你应该使用object.create()来继承.
5.Prototype和object.getPrototypeof()
5(1)Javascript对于来自Java或者C++的开发者是有一点困惑.因为全是动态的,运行时,没有一点类的概念.它全都是实例或者是对象.我们甚至被之为“类”,其实只不是是函数对象.
你可能注意到我们的函数functionA有一个特别的被称为prototype的属性.这个特别的属性产生新的操作.prototype对象的属性的引用被复制到新实例属性的内部的[[prototype]]里.举个例子,当你执行 var a1 = new A();javascript(在内存创建对象之后,在运行function A()之前)设置a1.[[prototype]]=A.prototype.当你访问其实例的属性,javascript首先会检查其是否存在对象中.如果不在,那应该在[[prototype]]中.那也就意味着你在prototype里定义的任务被所有的实例共享.如果你想的话,你甚至可以可以改变prototype的部分内容和在已经存在的实例的外观.
在上述例子中,如果你执行var a1 = newA() var b1 = newA();a1.dosomething将会指向object.getPrototypeOf(a1).dosomething.这个自己定义的A.prototype是一样的. Object.getPrototypeOf(a1).doSomething == Object.getPrototypeOf(a2).doSomething == A.prototype.doSomething
.简而言之,prototype即是type.obect.getPrototypeOf()和实例的是一样的.
所以,当你调用
var o = new Foo();
javascript会帮你执行
var o = new Object();
o.[[Prototype]] = Foo.prototype;
Foo.call(o);
在写可以充分使用的复杂代码之前,理解原型继承模型的本质.同时,应意识到在你的代码中原型链的长度.如果在必要下,拆散它们,避免可能出现的性能问题.原生的属性不应该被扩展,除非这是为了更新的Javascript特性兼容.