JavaScript中new操作符在创建一个实例时做了哪些事情?
JS中
new操作符创建实例时,会创建空对象,将其原型链接到构造函数,绑定this并执行构造函数,最后返回该新对象。
我们来详细拆解一下 JavaScript 中 new 操作符在创建一个对象实例时所执行的具体步骤。
简单来说,new 操作符会自动完成一个四步过程,将一个普通的函数调用转变为一个“构造函数调用”。
我们先用一个经典的构造函数作为例子:
// 定义一个构造函数
function Person(name, age) {
this.name = name;
// 这是一个实例属性,每个实例都会有自己独立的副本
this.age = age;
// 这是一个实例方法,每个实例也都会有自己独立的副本
this.sayHello = function() {
console.log('Hello, I am ' + this.name);
};
}
// 在 Person 的原型上添加一个共享方法
Person.prototype.introduce = function() {
console.log('My name is ' + this.name + ' and I am ' + this.age + ' years old.');
};
// 使用 new 操作符创建一个实例
const person1 = new Person('Alice', 30);
console.log(person1.name); // 'Alice'
person1.sayHello(); // 'Hello, I am Alice'
person1.introduce(); // 'My name is Alice and I am 30 years old.'
当你执行 new Person('Alice', 30) 这行代码时,JavaScript 引擎在背后具体做了以下四件事:
new 操作符的四个核心步骤
1. 创建一个空的简单JavaScript对象 (Create an empty object)
首先,new 操作符会在内存中创建一个全新的、空的对象。这个对象目前什么属性都没有。
- 手动模拟:
const obj = {};
2. 链接该对象到构造函数的原型 (Link to prototype)
这个新创建的空对象的内部 [[Prototype]] 属性(在旧的浏览器中可以通过 __proto__ 访问,标准方法是 Object.getPrototypeOf())会被设置为构造函数的 prototype 对象。
这一步是实现继承的关键。正是因为这个链接,person1 实例才能访问到 Person.prototype 上定义的 introduce 方法。
- 手动模拟:
Object.setPrototypeOf(obj, Person.prototype);或者更常用的是const obj = Object.create(Person.prototype);(这一步直接完成了第1步和第2步)。
3. 将构造函数的 this 指向这个新对象,并执行构造函数 (Bind this and execute)
构造函数 Person 会被调用,并且其内部的 this 关键字会被强制指向在第一步中创建的新对象。传入 new 的参数('Alice', 30)也会被传递给构造函数。
在我们的例子中,this.name = name 和 this.age = age 这两行代码就会把 name 和 age 属性添加到这个新对象上。
- 手动模拟:
Person.apply(obj, ['Alice', 30]);
4. 返回这个新对象 (Return the object)
在构造函数执行完毕后,new 表达式会返回这个被初始化过的新对象(也就是 person1)。
一个重要的特例:
- 如果构造函数没有显式地
return任何东西,或者return了一个原始类型的值(如string,number,boolean,null,undefined),那么new操作符会自动返回在第一步创建的新对象。这是最常见的情况。 - 如果构造函数显式地
return了一个对象(包括普通对象、数组、函数等),那么new操作符会返回这个被 return 的对象,而不是第一步创建的新对象。
javascriptfunction SpecialPerson() { this.name = 'Default'; return { customName: 'I am special' }; // 返回了一个新对象 } const p = new SpecialPerson(); console.log(p.name); // undefined console.log(p.customName); // 'I am special'- 如果构造函数没有显式地
手动实现一个 myNew 函数来模拟
为了加深理解,我们可以自己编写一个函数来模拟 new 的行为:
function myNew(constructor, ...args) {
// 步骤 1 & 2: 创建一个新对象,并将其原型链接到构造函数的 prototype
const obj = Object.create(constructor.prototype);
// 步骤 3: 将构造函数的 this 指向新对象,并执行构造函数
const result = constructor.apply(obj, args);
// 步骤 4: 判断构造函数的返回值类型
// 如果返回值是一个对象,则返回该对象,否则返回新创建的对象
return (result instanceof Object) ? result : obj;
}
// 使用我们自己的 myNew 函数来创建实例
const person2 = myNew(Person, 'Bob', 25);
console.log(person2.name); // 'Bob'
person2.sayHello(); // 'Hello, I am Bob'
person2.introduce(); // 'My name is Bob and I am 25 years old.'
console.log(person2 instanceof Person); // true
这个 myNew 函数完美地复现了 new 操作符的内部工作流程。
与 class 语法的关系
值得注意的是,ES6 引入的 class 语法本质上是基于原型继承的语法糖。当你使用 class 和 new 时,底层的机制和上述四个步骤是完全一样的。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
// new Animal('Dog') 仍然执行那四个步骤
const dog = new Animal('Dog');
dog.speak(); // 'Dog makes a noise.'
总结
new 操作符的核心作用可以归纳为以下四点:
- 创建空对象:在内存中创建一个新的空对象。
- 链接原型:将这个新对象的
[[Prototype]]指向构造函数的prototype对象。 - 绑定
this并执行:将构造函数的this绑定到新对象上,并执行构造函数内部的代码。 - 返回新对象:如果构造函数没有返回其他对象,则自动返回这个新创建的对象。