Prototype
JavaScript 中的对象有一个特殊的 [[Prototype]] 内置属性,其实就是对于其他对象的引
用。几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值。
使用for…in…,会查找对象的整条原型链。因此,当你通过各种语法进行属性查找时都会查找 [[Prototype]] 链,直到找到属性或者
查找完整条原型链。
Object.prototype
所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype。
属性设置和屏蔽
在于原型链上层时myObject.foo = “bar”会出现的三种情况。
- 如果在[[Prototype]]链上层存在名为foo的普通数据访问属性(参见第3章)并且没 有被标记为只读(writable:false),那就会直接在 myObject 中添加一个名为 foo 的新 属性,它是屏蔽属性。
- 如果在[[Prototype]]链上层存在foo,但是它被标记为只读(writable:false),那么 无法修改已有属性或者在 myObject 上创建屏蔽属性。如果运行在严格模式下,代码会 抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。
- 如果在[[Prototype]]链上层存在foo并且它是一个setter(参见第3章),那就一定会 调用这个 setter。foo 不会被添加到(或者说屏蔽于)myObject,也不会重新定义 foo 这 个 setter。
有些情况下会隐式产生屏蔽,一定要当心。思考下面的代码:
1 | var anotherObject = { a:2 |
++ 操作相当于 myObject.a = myObject.a + 1。因此 ++ 操作首先会通过 [[Prototype]] 查找属性 a 并从 anotherObject.a 获取当前属性值 2,然后给这个值加 1,接着用 [[Put]] 将值 3 赋给 myObject 中新建的屏蔽属性 a
“类”
JavaScript 和面向类的语言不同,它并没有类来作为对象的抽象模式或者说蓝图。JavaScript 中只有对象。
实际上,JavaScript 才是真正应该被称为“面向对象”的语言,因为它是少有的可以不通过类,直接创建对象的语言。
JavaScript 会在两 个对象之间创建一个关联,这样一个对象就可以通过委托访问另一个对象的属性和函数。 委托
构造函数”
使用 new 调用时,它就会构造一个对象并赋值 给 a,这看起来像是 new 的一个副作用(无论如何都会构造一个对象)。这个调用是一个构 造函数调用,但是 NothingSpecial 本身并不是一个构造函数。
换句话说,在 JavaScript 中对于“构造函数”最准确的解释是,所有带 new 的函数调用。 函数不是构造函数,但是当且仅当使用 new 时,函数调用会变成“构造函数调用”。
回顾“构造函数”
一些随意的对象属性引用,比如 a1.constructor,实际上是不被信任的,它们不一 定会指向默认的函数引用。此外,很快我们就会看到,稍不留神 a1.constructor 就可能会 指向你意想不到的地方。
a1.constructor 是一个非常不可靠并且不安全的引用。通常来说要尽量避免使用这些引用。
小结
如果要访问对象中并不存在的一个属性,[[Get]] 操作(参见第 3 章)就会查找对象内部
[[Prototype]] 关联的对象。这个关联关系实际上定义了一条“原型链”(有点像嵌套的作用域链),在查找属性时会对它进行遍历。
所有普通对象都有内置的 Object.prototype,指向原型链的顶端(比如说全局作用域),如 果在原型链中找不到指定的属性就会停止。toString()、valueOf() 和其他一些通用的功能 都存在于 Object.prototype 对象上,因此语言中所有的对象都可以使用它们。
关联两个对象最常用的方法是使用 new 关键词进行函数调用,在调用的 4 个步骤(第 2 章)中会创建一个关联其他对象的新对象。
使用 new 调用函数时会把新对象的 .prototype 属性关联到“其他对象”。带 new 的函数调用 通常被称为“构造函数调用”,尽管它们实际上和传统面向类语言中的类构造函数不一样。
虽然这些 JavaScript 机制和传统面向类语言中的“类初始化”和“类继承”很相似,但 是 JavaScript 中的机制有一个核心区别,那就是不会进行复制,对象之间是通过内部的 [[Prototype]] 链关联的。