文章

new运算符执行原理深度解析

深入剖析 new 运算符的四步执行原理,理解对象创建的底层机制,以及箭头函数为何无法使用 new。

new运算符执行原理深度解析

一句话概括

new 运算符本质上做了四件事:创建空对象 → 绑定原型 → 执行构造函数 → 返回对象,理解这四步是掌握 JavaScript 对象创建机制的核心。


背景

在 JavaScript 中,new 关键字是创建对象实例的标准方式。但很多开发者只知道”用 new 调用构造函数可以创建对象”,却不清楚背后发生了什么。面试中”手写 new”、”箭头函数能否 new”是高频考点,理解其原理至关重要。


概念与定义

new 运算符做了什么?

当执行 new Foo(args) 时,JavaScript 引擎依次执行以下四步:

  1. 创建一个空对象 obj = {}
  2. 将空对象的原型指向构造函数的 prototypeobj.__proto__ = Foo.prototype
  3. 将构造函数的 this 绑定到新对象,并执行构造函数Foo.call(obj, args)
  4. 判断构造函数返回值
    • 若构造函数返回一个对象类型的值,则 new 表达式的结果为该返回值
    • 否则,返回第一步创建的 obj

最小示例

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  return `Hi, I'm ${this.name}`;
};

const p = new Person('Alice', 25);
console.log(p.name);       // 'Alice'
console.log(p.greet());    // "Hi, I'm Alice"
console.log(p instanceof Person); // true

核心知识点拆解

1. 原型链的建立

new 的第二步将新对象的 __proto__ 指向构造函数的 prototype,这是原型链继承的基础:

1
2
3
4
5
6
7
8
9
10
function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function() {
  return `${this.name} makes a sound.`;
};

const dog = new Animal('Dog');
console.log(dog.__proto__ === Animal.prototype); // true
console.log(dog.__proto__.__proto__ === Object.prototype); // true

2. 构造函数返回值的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 返回基本类型 → 忽略,返回新对象
function Foo() {
  this.x = 1;
  return 42; // 基本类型,被忽略
}
const f = new Foo();
console.log(f.x); // 1

// 返回对象类型 → 使用该返回值
function Bar() {
  this.x = 1;
  return { y: 2 }; // 对象类型,覆盖新对象
}
const b = new Bar();
console.log(b.x); // undefined
console.log(b.y); // 2

3. 箭头函数不能使用 new

箭头函数没有自己的 this,也没有 prototype 属性,因此无法作为构造函数:

1
2
3
4
5
6
7
8
const Arrow = () => {};
console.log(Arrow.prototype); // undefined

try {
  const a = new Arrow(); // TypeError: Arrow is not a constructor
} catch(e) {
  console.log(e.message);
}

原因new 的第二步需要访问 Foo.prototype,箭头函数没有 prototype,所以直接报错。

4. class 与 new 的关系

ES6 的 class 语法糖底层依然依赖 new 机制,但有一个额外限制:class 定义的构造函数必须通过 new 调用,否则报错:

1
2
3
4
5
class MyClass {
  constructor(x) { this.x = x; }
}

MyClass(); // TypeError: Class constructor MyClass cannot be invoked without 'new'

实战案例

手写 myNew 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function myNew(Constructor, ...args) {
  // 1. 创建空对象,并绑定原型
  const obj = Object.create(Constructor.prototype);
  
  // 2. 执行构造函数,绑定 this
  const result = Constructor.call(obj, ...args);
  
  // 3. 判断返回值
  return result instanceof Object ? result : obj;
}

// 测试
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.greet = function() {
  return `Hi, I'm ${this.name}`;
};

const p = myNew(Person, 'Bob', 30);
console.log(p.name);    // 'Bob'
console.log(p.greet()); // "Hi, I'm Bob"
console.log(p instanceof Person); // true

构造函数返回对象的场景

1
2
3
4
5
6
7
8
9
10
11
12
// 单例模式中利用返回对象特性
let instance = null;
function Singleton(data) {
  if (instance) return instance;
  this.data = data;
  instance = this;
}

const s1 = new Singleton('first');
const s2 = new Singleton('second');
console.log(s1 === s2); // true
console.log(s2.data);   // 'first'

底层原理

Object.create 的等价实现

new 的原型绑定步骤等价于:

1
2
3
4
5
6
// Object.create(proto) 的等价实现
function objectCreate(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

instanceof 的工作原理

instanceof 通过原型链判断,与 new 建立的原型链密切相关:

1
2
3
4
5
6
7
8
function instanceOf(obj, Constructor) {
  let proto = Object.getPrototypeOf(obj);
  while (proto !== null) {
    if (proto === Constructor.prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
  return false;
}

高频面试题解析

Q1:new 操作符做了哪四步?

  1. 创建一个空对象
  2. 将空对象的 __proto__ 指向构造函数的 prototype
  3. 将构造函数的 this 绑定到新对象并执行
  4. 若构造函数返回对象类型则返回该值,否则返回新对象

Q2:箭头函数为什么不能使用 new?

:两个原因:

  1. 箭头函数没有自己的 this,无法在 new 时绑定 this
  2. 箭头函数没有 prototype 属性,无法建立原型链

Q3:构造函数 return 一个对象会怎样?

new 表达式的结果会是该返回的对象,而不是新创建的实例。但如果 return 的是基本类型(数字、字符串等),则忽略该返回值,仍返回新创建的对象。

Q4:Object.create(null) 创建的对象有什么特点?

:该对象没有原型(__proto__ 为 null),不继承 Object.prototype 上的任何方法(如 toStringhasOwnProperty),常用于创建纯粹的字典对象,避免原型污染。

Q5:如何判断一个函数是否被 new 调用?

:可以使用 new.target

1
2
3
4
5
6
function Foo() {
  if (!new.target) {
    throw new Error('必须使用 new 调用');
  }
  this.x = 1;
}

总结与扩展

特性普通函数箭头函数class
可以 new✅(必须 new)
有 prototype
有自己的 this
new.target可用不可用可用

扩展阅读

  • Reflect.construct(Target, args, NewTarget) — 更灵活的 new 调用方式
  • Symbol.hasInstance — 自定义 instanceof 行为
  • ES6 class 的 [[Construct]] 内部方法规范
本文由作者按照 CC BY 4.0 进行授权