# 原型(prototype)
# 对象的属性与原型
知识点一:
- 引用对象的属性时会触发默认的
[[Get]]操作,步骤如下:- 检查对象本身是否有这个属性,如果有的话就使用它,没有的话进入第2步;
- 继续访问对象的
[[Prototype]]链,这个过程会持续到找到匹配的属性名或者查找完整条[[Prototype]]链(普通的[[Prototype]]链最终都会指向内置的Object.prototype)。如果是后者的话,[[Get]]操作的返回值是undefined。
- 设置对象的属性(eg:
myObject.foo = "bar";)的步骤如下:- 如果
myObject对象中包含名为foo的普通数据访问属性,这条赋值语句只会修改已有的属性值,myObject的[[Prototype]]链上层如果有foo属性的话,会被屏蔽; - 如果
foo不是直接存在于myObject中,[[Prototype]]链就会被遍历,类似上面提及的[[Get]]操作,此时有两种情况:- 如果原型链上找不到
foo,foo就会被直接添加到myObject上; - 如果原型链上层有
foo的话,会出现的三种情况:foo为普通数据访问属性并且没有被标记为只读(writable:false),那就会直接在myObject中添加一个名为foo的新属性,它是屏蔽属性;foo为普通数据访问属性并且被标记为只读(writable:false),那么无法修改已有属性或者在myObject上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽;foo是一个setter,那就一定会调用这个setter。foo不会被添加到(或者说屏蔽于)myObject,也不会重新定义foo这个setter。
- 如果原型链上找不到
- 如果
注意
如果使用Object.defineProperty(..) 来向 myObject 添加 foo,则不能使用上述的设置对象的属性的步骤。
# ”类“与原型
知识点二:
- JavaScript中的对象内置
[[prototype]]属性; - 对象的
[[prototype]]属性默认有一个公有并且不可枚举的属性[[constructor]],返回创建该对象的函数的引用; - 函数是特殊的对象;
- 使用
new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行
[[prototype]]连接(这个新对象的[[prototype]]属性指向构造函数的[[prototype]]属性)。 - 这个新对象会绑定到函数调用的
this。 - 如果函数没有返回值或者返回的是基本类型值,那么
new表达式中的函数调用会自动返回这个新对象;如果返回的是引用类型值,则构造函数调用的返回值就是这个引用类型值
- 在面向类的语言中,实例化(或者继承)一个类就意味着“把类的行为复制到物理对象中”,对于每一个新实例来说都会重复这个过程。但是在
JavaScript中,并没有类似的复制机制。你不能创建一个类的多个实例,只能创建多个对象,它们[[Prototype]]关联的是同一个对象。但是在默认情况下并不会进行复制,因此这些对象之间并不会完全失去联系,它们是互相关联的。
请看以下代码来理解知识点:
function Foo() {
// ...
}
Foo.prototype.constructor === Foo; // true 见代码解析1
var a = new Foo();
var b = new Foo();
Object.getPrototypeOf(a) === Foo.prototype; // true 见代码解析2
Object.getPrototypeOf(a) === Object.getPrototypeOf(b); // true 见代码解析3
a.constructor === Foo; // true
Foo.constructor === Foo; // false
Foo.constructor === Function; // true 见代码解析4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
代码解析:
- 对象
Foo由函数Foo创建,并且内置[[Prototype]]属性; - 对象
Foo的属性Foo.prototype是一个对象,默认有属性[[constructor]],它指向创建对象Foo的函数——函数Foo的引用; - 故得
Foo.prototype.constructor === Foo; // true
- 对象
- 调用
new Foo()时会创建一个全新的对象,并使这个对象的[[Prototype]]属性指向Foo.prototype指向的那个对象; - 然后将这个全新的对象赋值给了 a;
- 故得
Object.getPrototypeOf(a) === Foo.prototype; // true
- 调用
- 对象 a 和对象 b 都是 "类Foo"的"实例",它们的
[[Prototype]]关联的是同一个对象——Foo.prototype; - 故得
Object.getPrototypeOf(a) === Object.getPrototypeOf(b); // true
- 对象 a 和对象 b 都是 "类Foo"的"实例",它们的
- a 和
Foo其实都是没有[[constructor]]属性的:a.constructor只是通过默认的[[Prototype]]委托指向Foo.prototype,而Foo.prototype.constructor默认指向Foo;- 同理,
Foo.constructor是通过默认的[[Prototype]]委托指向Function.prototype,而Function.prototype.constructor指向Function,因为函数Foo由引用类型Function创建;
知识点三:
constructor并不表示被构造!instanceof操作符;isPrototypeOf(..)。
请看以下代码来理解知识点:
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; // 创建一个新原型对象
var a = new Foo();
a.constructor === Foo; // false
a.constructor === Object; // true 见代码解析1
a instanceof Foo; // true 见代码解析2
Foo.prototype.isPrototypeOf( a ); // true 见代码解析3
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
代码解析:
从之前讲的知识点可知:
- 默认情况下
Foo.prototype.constructor === Foo;; - a 其实都是没有
[[constructor]]属性的,执行a.constructor会遍历整条[[Prototype]]链;
所以,更改
Foo.prototype后,执行a.constructor在Foo.prototype是找不到[[constructor]]属性的,然后继续找到[[Prototype]]链的终点——Object.prototype,故得a.constructor === Object; // true,所以constructor并不表示被构造!- 默认情况下
instanceof回答的问题是:在 对象 a 的整条[[Prototype]]链中是否有指向Foo.prototype的对象。isPrototypeOf(..)回答的问题是:在 a 的整条 [[Prototype]] 链中是否出现过Foo.prototype。
知识点四:
Object.create(..)会创建一个新对象,并把这个对象的[[Prototype]]关联到我们指定的对象。- 访问对象的原型分两种情况:
- 普通对象:
obj.__proto__obj.constructor.prototypeObject.getPrototypeOf(obj)
- 函数对象:
fun.prototype
- 普通对象:
请看以下代码来理解知识点:
var foo = {
something: function() {
console.log( "Tell me something good..." );
}
};
var bar = Object.create( foo );
bar.something(); // Tell me something good...
1
2
3
4
5
6
7
2
3
4
5
6
7
代码解析:
- 对象
bar本身没有something方法; - 执行
var bar = Object.create( foo );会把对象bar的[[Prototype]]关联到我们指定的对象foo; - 故执行
bar.something();会通过[[Prototype]]链引用到foo.something()。