前端面试题 · 手写 new 操作符,15 行代码搞清楚 JS 对象是怎么创建的

4 阅读3分钟

面试常问:

new 一个对象的时候, JS 到底做了什么? 能不能手写一个?

看着简单,但真让你写出 myNew(Fn, ...args),能一步搞对的人不多。这篇文章拆解 new 的 4 个内部步骤,一步步实现完整版本。

一、new 做了 4 件事

先说结论,new Fn(...args) 内部做了 4 步:

  1. 创建一个新对象 obj = {}
  2. 把 obj 的原型指向 Fn.prototype(建立原型链)
  3. 执行 Fn,把 this 绑定到 obj
  4. 判断返回值:如果 Fn 返回对象则用它,否则返回 obj

理解这 4 步,手写就是把它们翻译成代码。

二、一步步实现

Step 1:创建新对象

function myNew(Fn, ...args) {
  const obj = {};
  // ...
}

Step 2:建立原型链

关键点:让 obj 能访问到 Fn.prototype 上的方法。

function myNew(Fn, ...args) {
  const obj = {};
  Object.setPrototypeOf(obj, Fn.prototype);
  // 或者:obj.__proto__ = Fn.prototype;
}

为什么用 Object.setPrototypeOf 不用 __proto__?

__proto__非标准属性(虽然主流引擎都支持),Object.setPrototypeOf 是 ES6 标准 API,更规范。

Step 3:执行构造函数,绑定 this

function myNew(Fn, ...args) {
  const obj = {};
  Object.setPrototypeOf(obj, Fn.prototype);
  
  const result = Fn.apply(obj, args);
  // ...
}

applythis 绑到 obj 上,同时收集参数。

Step 4:处理返回值

这一步是最容易被忽略的关键

function myNew(Fn, ...args) {
  const obj = {};
  Object.setPrototypeOf(obj, Fn.prototype);
  
  const result = Fn.apply(obj, args);
  
  // 如果构造函数返回对象,就用它;否则用 obj
  return result instanceof Object ? result : obj;
}

为什么要判断返回值?

因为 JS 允许构造函数显式返回一个对象,这时 new 会用返回的对象,而不是新创建的 obj。

function Person(name) {
  this.name = name;
  return { age: 18 }; // 显式返回对象
}

const p = new Person('张三');
console.log(p); // { age: 18 } —— 用了返回值,不是新 obj
console.log(p.name); // undefined —— name 丢了

如果返回的是基础类型(字符串、数字、布尔、undefined、null),new 会忽略,继续返回新对象。

function Person(name) {
  this.name = name;
  return 'hello'; // 返回基础类型
}

const p = new Person('张三');
console.log(p.name); // '张三' —— 返回值被忽略了

三、完整版

function myNew(Fn, ...args) {
  // 1. 创建新对象,建立原型链(可以一步合并)
  const obj = Object.create(Fn.prototype);
  
  // 2. 执行构造函数,绑定 this
  const result = Fn.apply(obj, args);
  
  // 3. 处理返回值
  return result instanceof Object ? result : obj;
}

Object.create(proto) 一步完成"创建对象 + 设置原型",更简洁。

四、测试

function Person(name, age) {
  this.name = name;
  this.age = age;
}

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

// 用原生 new
const p1 = new Person('张三', 25);

// 用手写 myNew
const p2 = myNew(Person, '张三', 25);

console.log(p1.name === p2.name); // true
console.log(p1.sayHi() === p2.sayHi()); // true
console.log(p2 instanceof Person); // true

验证返回值处理:

function Cat(name) {
  this.name = name;
  return { type: 'cat' };
}

const c1 = new Cat('小花');
const c2 = myNew(Cat, '小花');

console.log(c1); // { type: 'cat' }
console.log(c2); // { type: 'cat' } —— 一致

五、进阶:instanceof Object 的坑

上面用 result instanceof Object 判断是否返回对象,其实还有一个更严谨的写法:

return (typeof result === 'object' && result !== null) || 
       typeof result === 'function' 
       ? result 
       : obj;

为什么?

  • typeof null === 'object',但 null 不是对象,要排除
  • 函数也是对象,如果构造函数返回一个函数,也应该用返回值

不过面试时用 instanceof Object 已经能过关,进阶版了解就行。

六、面试怎么答

面试官:"手写一个 new"

分层答法:

  1. 先说清楚 new 做了 4 件事(展示理解)
  2. 写出核心 3 行代码(展示动手)
  3. 补充返回值处理(展示细节)
  4. 说明基础类型和对象的返回差异(展示深度)
function myNew(Fn, ...args) {
  const obj = Object.create(Fn.prototype);
  const result = Fn.apply(obj, args);
  return result instanceof Object ? result : obj;
}

能一步步讲清楚 = 加分,直接甩终极代码 = 面试官会觉得你在背。

总结

new 内部就 4 步:

  1. 创建对象
  2. 建立原型链
  3. 绑定 this 执行
  4. 判断返回值

15 行代码手写完成。关键在于第 4 步的返回值判断 —— 这是区分"背过"和"真理解"的分水岭。