原型Prototype

prototype应该是Javascript里最核心的内容了。原型链继承的概念神马的估计都背熟了,但其实在应用中又该怎么玩呢?

原型链是什么

如果myObject上不存在a属性时,我们就将注意力转向对象的[[Prototype]]链:

1
2
3
4
5
6
var anotherObject = {
a: 2
};
// 创建一个链接到`anotherObject`的对象
var myObject = Object.create( anotherObject );
myObject.a; // 2

这个处理持续进行,直到找到名称匹配的属性,或者[[Prototype]]链终结。如果在链条的末尾都没有找到匹配的属性,那么[[Get]]操作的返回结果为undefined

原型链的屏蔽

而以下这个案例又说明了什么?

1
2
3
4
5
6
7
8
9
10
11
12
var anotherObject = {
a: 2
};
// 创建一个链接到`anotherObject`的对象
var myObject = Object.create( anotherObject );
myObject.a; // 2
anotherObject.a = 3;
console.log(myObject.a);//3 仍然通过[[Prototype]]查找到a
myObject.a = 4;//直接拥有了属性a,屏蔽[[Prototype]]
console.log(anotherObject.a);//3
anotherObject.a = 5;
console.log(myObject.a);//4 [[Prototype]]被屏蔽,直接读取自身属性a

[[Prototype]]链怎么访问呢,现在浏览器提供了一个.__proto__属性来查找原型链。可以这么实现:

1
2
3
4
5
6
7
8
9
10
Object.defineProperty( Object.prototype, "__proto__", {
get: function() {
return Object.getPrototypeOf( this );
},
set: function(o) {
// setPrototypeOf(..) as of ES6
Object.setPrototypeOf( this, o );
return o;
}
} );

其实,我们并不需要一堆new.prototype就能很好的利用原型的特性来完成代码,直接使用对象的字面形式将拥有更简洁的语法和更好的可读性。

Object.create(..)和替补

Object.create(..)在ES5中被加入。你可能需要支持ES5之前的环境:

1
2
3
4
5
6
7
if (!Object.create) {
Object.create = function(o) {
function F(){}
F.prototype = o;
return new F();
};
}

Object.create(..)的这种用法是目前最常见的用法,因为他的这一部分是可以填补的。ES5标准的内建Object.create(..)还提供了一个附加的功能,它是 不能 被ES5之前的版本填补的。如此,这个功能的使用远没有那么常见。为了完整性,让我么看看这个附加功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var anotherObject = {
a: 2
};
var myObject = Object.create( anotherObject, {
b: {
enumerable: false,
writable: true,
configurable: false,
value: 3
},
c: {
enumerable: true,
writable: false,
configurable: false,
value: 4
}
} );
myObject.hasOwnProperty( "a" ); // false
myObject.hasOwnProperty( "b" ); // true
myObject.hasOwnProperty( "c" ); // true
myObject.a; // 2
myObject.b; // 3
myObject.c; // 4

当然我们经常使用一些polyfill代码来实现并没有被广泛支持的新增功能,其中的可维护性和兼容性之间的取舍就看你自己啦。