深入浅出手写new操作符:从青铜到王者的进阶之路

470 阅读4分钟

大家好,我是你们的老朋友FogLetter,今天我们来聊聊JavaScript中那个既熟悉又神秘的new操作符。相信很多小伙伴在面试时都被问过:"能不能手写一个new的实现?"今天就让我们彻底搞懂它!

一、new操作符的日常用法

首先,我们来看一个简单的例子:

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

Person.prototype.sayHi = function() {
    console.log(`hi,我是${this.name}`)
}

const p = new Person('张三', 18)
p.sayHi() // hi,我是张三

这是我们最熟悉的用法:通过new关键字调用构造函数,创建一个新对象。但new背后到底做了什么呢?

二、new操作符的四步魔法

实际上,new操作符主要做了以下几件事:

  1. 创建一个空对象:这个对象将成为最终的实例
  2. 链接原型:将空对象的__proto__指向构造函数的prototype
  3. 绑定this:将构造函数的this指向这个新对象并执行
  4. 处理返回值:如果构造函数返回对象则使用该对象,否则返回新对象

三、手写new实现(ES5版本)

让我们先看一个ES5的实现方式:

function objectFactory() {
    var obj = {}; // 1. 创建空对象
    
    // 2. 获取构造函数(arguments第一个参数)
    // 类数组没有shift方法,借用数组方法
    var Constructor = [].shift.call(arguments); 
    
    // 3. 链接原型
    obj.__proto__ = Constructor.prototype;
    
    // 4. 绑定this并执行构造函数
    var ret = Constructor.apply(obj, arguments);
    
    // 5. 处理返回值
    // 如果构造函数返回对象则使用该对象,否则返回新对象
    return typeof ret === 'object' ? ret || obj : obj;
}

这个实现有几个关键点值得注意:

  1. [].shift.call(arguments):因为arguments是类数组对象,没有shift方法,我们通过借用数组的shift方法来获取构造函数
  2. obj.__proto__ = Constructor.prototype:这是实现原型继承的关键
  3. 返回值处理:这里有个小技巧,ret || obj是为了处理返回null的情况

四、手写new实现(ES6版本)

ES6的写法更加简洁:

function objectFactory(Constructor, ...args) {
    const obj = {}; // 创建空对象
    obj.__proto__ = Constructor.prototype; // 链接原型
    const ret = Constructor.apply(obj, args); // 执行构造函数
    
    // 处理返回值
    return typeof ret === 'object' ? ret || obj : obj;
}

ES6版本利用了剩余参数...args,代码更加清晰易读。

五、边界情况处理

我们的实现需要考虑构造函数有返回值的情况:

  1. 返回对象:使用返回的对象

    function Person() {
        return { custom: 'object' }
    }
    const p = objectFactory(Person)
    console.log(p) // { custom: 'object' }
    
  2. 返回基本类型:忽略返回值

    function Person() {
        return 1
    }
    const p = objectFactory(Person)
    console.log(p) // Person {}
    
  3. 返回null:虽然typeof null是'object',但我们仍然返回新对象

    function Person() {
        return null
    }
    const p = objectFactory(Person)
    console.log(p) // Person {}
    

六、深入理解原型链

让我们通过一个图来理解原型链:

实例(p) → Person.prototypeObject.prototypenull

当我们访问p.sayHi时,JavaScript会沿着这条原型链查找:

  1. 先在p自身查找
  2. 找不到就去Person.prototype查找
  3. 还找不到就去Object.prototype查找
  4. 最后到null停止

七、性能优化小技巧

虽然直接设置__proto__很方便,但在生产环境中,更推荐使用Object.create

function objectFactory(Constructor, ...args) {
    const obj = Object.create(Constructor.prototype); // 创建对象并链接原型
    const ret = Constructor.apply(obj, args);
    return typeof ret === 'object' ? ret || obj : obj;
}

Object.create是ES5引入的方法,性能更好,也更符合规范。

八、面试常见问题

在面试中,关于new可能会被问到以下问题:

  1. new操作符做了什么?

    • 创建空对象
    • 设置原型链
    • 绑定this执行构造函数
    • 处理返回值
  2. 如果构造函数返回一个对象会怎样?

    • new操作符会返回这个对象而不是新创建的对象
  3. 如何判断一个对象是否是通过某个构造函数new出来的?

    • 使用instanceof操作符
    • 或者检查对象的constructor属性

九、实际应用场景

手写new不仅仅是为了面试,在实际开发中也有应用:

  1. 框架开发:一些框架需要自己控制对象创建过程
  2. 性能优化:特殊场景下可能需要定制对象创建逻辑
  3. 库开发:提供更灵活的对象创建方式

十、总结

通过今天的学习,我们不仅掌握了如何手写new,更重要的是理解了JavaScript中对象创建的机制。记住:

  1. new操作符本质上是一个语法糖
  2. 原型继承是JavaScript的核心特性
  3. 理解this绑定和原型链是进阶的关键

希望这篇笔记能帮助大家彻底理解new的奥秘!如果觉得有帮助,别忘了点赞收藏哦~


这篇笔记从基础用法到实现原理,再到面试应用,全面讲解了new操作符的方方面面。通过代码示例和分步解析,让读者能够深入理解JavaScript对象创建的机制。