# 原型(prototype)

# 对象的属性与原型

知识点一

  • 引用对象的属性时会触发默认的 [[Get]] 操作,步骤如下:
    1. 检查对象本身是否有这个属性,如果有的话就使用它,没有的话进入第2步;
    2. 继续访问对象的 [[Prototype]] 链,这个过程会持续到找到匹配的属性名或者查找完整条 [[Prototype]] 链(普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype)。如果是后者的话, [[Get]] 操作的返回值是 undefined
  • 设置对象的属性(eg: myObject.foo = "bar";)的步骤如下:
    1. 如果 myObject 对象中包含名为 foo 的普通数据访问属性,这条赋值语句只会修改已有的属性值, myObject[[Prototype]] 链上层如果有 foo 属性的话,会被屏蔽;
    2. 如果 foo 不是直接存在于 myObject 中, [[Prototype]] 链就会被遍历,类似上面提及的 [[Get]] 操作,此时有两种情况:
      • 如果原型链上找不到 foo , foo 就会被直接添加到 myObject 上;
      • 如果原型链上层有 foo 的话,会出现的三种情况:
        1. foo 为普通数据访问属性并且没有被标记为只读( writable:false ),那就会直接在 myObject 中添加一个名为 foo 的新属性,它是屏蔽属性;
        2. foo 为普通数据访问属性并且被标记为只读( writable:false ),那么无法修改已有属性或者在 myObject 上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽;
        3. foo 是一个 setter,那就一定会调用这个 setterfoo 不会被添加到(或者说屏蔽于) myObject ,也不会重新定义 foo 这个 setter

注意

如果使用Object.defineProperty(..) 来向 myObject 添加 foo,则不能使用上述的设置对象的属性的步骤。

# ”类“与原型

知识点二

  • JavaScript中的对象内置 [[prototype]] 属性;
  • 对象的 [[prototype]] 属性默认有一个公有并且不可枚举的属性 [[constructor]] ,返回创建该对象的函数的引用;
  • 函数是特殊的对象;
  • 使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:
    1. 创建(或者说构造)一个全新的对象。
    2. 这个新对象会被执行 [[prototype]] 连接(这个新对象的 [[prototype]] 属性指向构造函数的 [[prototype]] 属性)。
    3. 这个新对象会绑定到函数调用的 this
    4. 如果函数没有返回值或者返回的是基本类型值,那么 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

代码解析

    • 对象 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
  1. 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

代码解析

  1. 从之前讲的知识点可知:

    • 默认情况下Foo.prototype.constructor === Foo;
    • a 其实都是没有 [[constructor]] 属性的,执行 a.constructor会遍历整条 [[Prototype]] 链;

    所以,更改 Foo.prototype 后,执行 a.constructorFoo.prototype是找不到 [[constructor]] 属性的,然后继续找到 [[Prototype]] 链的终点——Object.prototype,故得a.constructor === Object; // true,所以constructor 并不表示被构造!

  2. instanceof 回答的问题是:在 对象 a 的整条 [[Prototype]] 链中是否有指向 Foo.prototype 的对象。

  3. isPrototypeOf(..) 回答的问题是:在 a 的整条 [[Prototype]] 链中是否出现过 Foo.prototype

知识点四

  • Object.create(..) 会创建一个新对象,并把这个对象的 [[Prototype]] 关联到我们指定的对象。
  • 访问对象的原型分两种情况:
    1. 普通对象:
      • obj.__proto__
      • obj.constructor.prototype
      • Object.getPrototypeOf(obj)
    2. 函数对象:
      • 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

代码解析

  • 对象 bar 本身没有 something 方法;
  • 执行 var bar = Object.create( foo ); 会把对象 bar[[Prototype]] 关联到我们指定的对象 foo;
  • 故执行 bar.something(); 会通过[[Prototype]]链引用到 foo.something()
Last Updated: 11/9/2022, 6:38:52 AM