从new操作符看原型与原型链

ECMA-262中,把对象定义为:”无序属性的集合,其属性可以为基本类型值、对象或者函数。”而一般我们要创建对象(更准确一点叫做创建对象实例)时,会使用 new 操作符。最简单的如 new Object() ,然后我们把创建对象实例的那个东西(也就是 new 操作符后面跟着的那个东西,一般都是一个函数)叫做构造函数(有人也叫做构造器)。再然后我们会把 new Object() 的返回值赋值给一个变量如 obj ,写成 var obj=new Object(); ,而后我们就会对obj进行各种操作,但其中到底发生了什么?

new Foo(...) 被执行

引用 MDN 上关于当 new Foo(...) 被执行,会发生如下事情:

  1. 创建一个从Foo.prototype 继承的新对象;
  2. 构造函数 Foo 使用指定的参数调用,并将Foo 中的 this 绑定到新创建的对象。new Foo 相当于new Foo(),即如果没有指定参数列表,则使用无参调用Foo;
  3. 构造函数返回的对象成为整个 new 表达式的结果。如果构造函数没有显式地返回一个对象,那么将使用步骤1中创建的对象。(通常构造函数不返回值,但是它们可以选择这样做,如果它们想要覆盖正常的对象创建过程。)

上面那段说白了就是:

  1. 使用 new 操作符时,会创建一个新对象,该新对象的 __proto__ 属性指向其构造函数的原型,即 Foo.prototype
  2. 构造函数中的 this 会指向新对象;
  3. 执行构造函数中语句;
  4. 如果构造函数定义了返回值且返回值是对象(包括数组、函数之类的),则返回值为这些对象;如果构造函数未定义返回值(默认返回Undefined)或定义的返回值是基本类型(Undefined类型、Null类型、Boolean类型、Number类型、String类型),则返回 new 创建的那个新对象。

需注意的是:新对象的 __proto__ 属性指向其构造函数的原型,即 Foo.prototype,而不是指向 FooFoo.prototype 为创建 Foo (一般为使用 function Foo(){...})时创建的的属性,prototype中包括一个名为 constructor 的属性,该属性才是指向 Foo

2019.11.07 update
__proto__ 为浏览器(Mozilla)为访问 [[prototype]]私有属性而定义的,类似的访问 [[class]]属性要通过 Object.prototype.toString

举例说明:

  • 关于第一点的例子:

    new创建Foo对象举例

  • 关于第四点的例子:

    new返回值的例子

字面量定义 prototype

还需要注意的一点为,如果通过字面量形式定义 prototype 时,如下:

1
2
3
4
5
6
7
8
9
function Person(){ 
}

Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () { alert(this.name); }
};

此时 Person.prototype 的 constructor 属性就不是指向 Person 了 而是指向默认的 Object ,因为字面量形式相当于重写 prototype,如需像关键字声明那样使 constructor 指向 Person,需在字面量中手动声明,这个要特别注意。如下:

1
2
3
4
5
6
7
8
9
10
function Person(){ 
}

Person.prototype = {
constructor : Person,
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () { alert(this.name); }
};

例题

设计一个工具函数extend(A, B), 使 A 继承 B

1
2
3
4
5
6
function extend(A, B) {
// 要实现A继承B,通过 原型继承 方式,即
A.prototype = new B;
// 但 new 操作符会导致 constructor === B,因此重新指定,不然 var a = new A() 时,a的constructor就会指向B
A.prototype.constructor = A;
}

参考

© 2019 lvbin's Blog All Rights Reserved.
Theme by hiero