new运算符执行原理深度解析
深入剖析 new 运算符的四步执行原理,理解对象创建的底层机制,以及箭头函数为何无法使用 new。
一句话概括
new 运算符本质上做了四件事:创建空对象 → 绑定原型 → 执行构造函数 → 返回对象,理解这四步是掌握 JavaScript 对象创建机制的核心。
背景
在 JavaScript 中,new 关键字是创建对象实例的标准方式。但很多开发者只知道”用 new 调用构造函数可以创建对象”,却不清楚背后发生了什么。面试中”手写 new”、”箭头函数能否 new”是高频考点,理解其原理至关重要。
概念与定义
new 运算符做了什么?
当执行 new Foo(args) 时,JavaScript 引擎依次执行以下四步:
- 创建一个空对象
obj = {} - 将空对象的原型指向构造函数的 prototype:
obj.__proto__ = Foo.prototype - 将构造函数的 this 绑定到新对象,并执行构造函数:
Foo.call(obj, args) - 判断构造函数返回值:
- 若构造函数返回一个对象类型的值,则 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 操作符做了哪四步?
答:
- 创建一个空对象
- 将空对象的
__proto__指向构造函数的prototype - 将构造函数的
this绑定到新对象并执行 - 若构造函数返回对象类型则返回该值,否则返回新对象
Q2:箭头函数为什么不能使用 new?
答:两个原因:
- 箭头函数没有自己的
this,无法在new时绑定this - 箭头函数没有
prototype属性,无法建立原型链
Q3:构造函数 return 一个对象会怎样?
答:new 表达式的结果会是该返回的对象,而不是新创建的实例。但如果 return 的是基本类型(数字、字符串等),则忽略该返回值,仍返回新创建的对象。
Q4:Object.create(null) 创建的对象有什么特点?
答:该对象没有原型(__proto__ 为 null),不继承 Object.prototype 上的任何方法(如 toString、hasOwnProperty),常用于创建纯粹的字典对象,避免原型污染。
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]]内部方法规范