# 原型(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.prototype
Object.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()
。