constructor and new

构造函数

构造函数在技术上是常规函数。不过有两个约定:

  • 它们的命名以大写字母开头。
  • 它们只能由 "new" 操作符来执行。
js
1
2
3
4
5
6
7
8
9
function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");

alert(user.name); // Jack
alert(user.isAdmin); // false

当一个函数被使用 new 操作符执行时,它按照以下步骤:

  1. 一个新的空对象被创建并分配给 this
  2. 函数体执行。通常它会修改 this,为其添加新的属性。
  3. 返回 this 的值。

构造器模式测试:new.target

在一个函数内部,我们可以使用 new.target 属性来检查它是否被使用 new 进行调用了。
对于常规调用,它为 undefined,对于使用 new 的调用,则等于该函数:

js
1
2
3
4
5
6
7
8
9
function User() {
  alert(new.target);
}

// 不带 "new":
User(); // undefined

// 带 "new":
new User(); // function User { ... }

它可以被用在函数内部,来判断该函数是被通过 new 调用的“构造器模式”,还是没被通过 new 调用的“常规模式”。
我们也可以让 new 调用和常规调用做相同的工作,像这样:

js
1
2
3
4
5
6
7
8
9
10
function User(name) {
  if (!new.target) { // 如果你没有通过 new 运行我
    return new User(name); // ……我会给你添加 new
  }

  this.name = name;
}

let john = User("John"); // 将调用重定向到新用户
alert(john.name); // John

new 操作符做了什么

  • 在内存中创建一个新对象。
  • 将新对象内部的 __proto__ 赋值为构造函数的 prototype 属性。
  • 将构造函数内部的 this 被赋值为新对象(即 this 指向新对象)。
  • 执行构造函数内部的代码(给新对象添加属性)。
  • 如果构造函数返回非空对象,则返回该对象。否则返回 this。

new 操作符的模拟实现

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function fakeNew() {
  // 创建新对象
  var obj = Object.create(null);
  var Constructor = [].shift.call(arguments);
  // 将对象的 __proto__ 赋值为构造函数的 prototype 属性
  obj.__proto__ = Constructor.prototype;
  // 将构造函数内部的 this 赋值为新对象
  var ret = Constructor.apply(obj, arguments);
  // 返回新对象
  return typeof ret === "object" && ret !== null ? ret : obj;
}

function Group(name, member) {
  this.name = name;
  this.member = member;
}

var group = fakeNew(Group, "hzfe", 17);