基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

JavaScript中new操作符在创建一个实例时做了哪些事情?

知识点图片

JS中new操作符创建实例时,会创建空对象,将其原型链接到构造函数,绑定this并执行构造函数,最后返回该新对象。

我们来详细拆解一下 JavaScript 中 new 操作符在创建一个对象实例时所执行的具体步骤。

简单来说,new 操作符会自动完成一个四步过程,将一个普通的函数调用转变为一个“构造函数调用”。

我们先用一个经典的构造函数作为例子:

javascript
// 定义一个构造函数
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 = namethis.age = age 这两行代码就会把 nameage 属性添加到这个新对象上。

  • 手动模拟Person.apply(obj, ['Alice', 30]);

4. 返回这个新对象 (Return the object)

在构造函数执行完毕后,new 表达式会返回这个被初始化过的新对象(也就是 person1)。

  • 一个重要的特例

    • 如果构造函数没有显式地 return任何东西,或者 return 了一个原始类型的值(如 string, number, boolean, null, undefined),那么 new 操作符会自动返回在第一步创建的新对象。这是最常见的情况。
    • 如果构造函数显式地 return 了一个对象(包括普通对象、数组、函数等),那么 new 操作符会返回这个被 return 的对象,而不是第一步创建的新对象。
    javascript
    function 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 的行为:

javascript
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 语法本质上是基于原型继承的语法糖。当你使用 classnew 时,底层的机制和上述四个步骤是完全一样的。

javascript
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 操作符的核心作用可以归纳为以下四点:

  1. 创建空对象:在内存中创建一个新的空对象。
  2. 链接原型:将这个新对象的 [[Prototype]] 指向构造函数的 prototype 对象。
  3. 绑定 this 并执行:将构造函数的 this 绑定到新对象上,并执行构造函数内部的代码。
  4. 返回新对象:如果构造函数没有返回其他对象,则自动返回这个新创建的对象。
00:00
00:00