JS基础-面试官:来手写一个new

30 阅读5分钟

引言

在这篇文章——继承和原型链中我们学习了prototype和__proto__的概念,我觉得这样能够更好的理解new的实现吧。

StackOverflow 上的一个被广泛认可的解释:

__proto__ is the actual object that is used in the lookup chain to resolve methods, etc. (真正用来查找原型链去获取方法的对象。)

prototype is the object that is used to build __proto__ when you create an object with new.
(用new创建对象时用来构建__proto__的对象)

new 发生了什么?

我们经常用到new Fun(),new即为新的,那么他怎么就new了呢?让我们看看到底怎么个事儿。

function Person(name,num) {
    this.name = name;
    this.num=num;
}
console.log( new Person('Bryant',24));

image.png image.png image.png

我们可以看见,在node环境和浏览器控制台都输出了一个Person对象,同时呢,创建一个新的实例对象(并赋给了shooter)还会引用构造函数Person(),并且还继承有Person的方法shoot(),所以,shooter确实是Person的实例对象。

那么这其中发生了什么呢?

从上面的例子中我们可以发现:

  • 他返回了一个对象,我们暂且将它标为obj,
  • obj还继承了Person的方法
  • 还有一个大家发现没:obj也有了name和num。

如果我们来手搓一个new 怎么实现呢? 想想:

  1. 首先我们要创造一个空的对象obj{}。

  2. 我们还要让obj继承Person的方法。

  3. 我们知道js会隐式地声明未有的属性,复制的话太费力,所以可能某种方法使用了Person的构造函数,但是那个this却指向了obj。然后查到了确实有这么几种方法。到这儿来

    • Function 实例的 apply()  方法会以给定的 this 值和作为数组(或类数组对象)提供的 arguments 调用该函数
    • Function 实例的 bind()  方法创建一个新函数,当调用该新函数时,它会调用原始函数并将其 this 关键字设置为给定的值,同时,还可以传入一系列指定的参数,这些参数会插入到调用新函数时传入的参数的前面。
    • Function 实例的 call()  方法会以给定的 this 值和逐个提供的参数调用该函数。
  4. 最后呢,我们还要返回这个obj。

好了 可以搓了

  • 我们创建一个ourNew(){}方法,然后有一个我们需要new的对象Fun,然后它会有其它的参数name、num等,所以我们用动态参数来传值

  • 同时创建一个空对象obj

outNew(Fun,...args){
    
}
  • 设置obj的原型链指向Fun的原型对象,以此来达到继承Fun的方法。(有诸多方法,为提升性能)

    • obj.__proto__=Object.create(Func.prototype)便于代码的可读性。
    • let newObj = Object.create(Func.prototype) 与上一步结合起来,据说可以兼容IE。
    • obj.__proto__=Fun.prototype(Object.create(Func.prototype)会创建一个新的对象来赋给obj.proto)这样省去了不必要的中间对象。
  • 改变this指向,在obj上挂载Fun属性。此过程会调用构造函数。将Fun中的this指向obj 并将args作为参数一一传进去。

    • Fun.apply(obj,args)适用于动态传递参数数量的情况(例如,当参数存储在数组中时)。
    • Fun.call(obj,args)适用于参数数量已知且固定的情况。
    • Fun.bind(obj,args)适用于需要在稍后执行或在不同上下文中多次调用的情况。
  • 返回对象obj

    • return obj
ourNew(Fun,...args){
    let obj = Object.create(Fun);
    Fun.apply(obj,args)
    return obj
}

你不会以为就这么简单吧

其实呢 就是这么简单,原理就是如此,但是还有很多我们可优化的地方。

  • 构造函数会不会有返回值呢?返回的是一个String、number值呢?

  • Fun一定会是个方法体吗?

  • . . .
/**
 * 模拟 new 操作符的行为,创建一个构造函数的实例
 * @param {Function} Constructor - 要实例化的构造函数
 * @param  {...any} args - 传递给构造函数的参数
 * @returns {Object} - 返回构造函数的实例
 */
function ourNew(Constructor, ...args) {
  //  检查第一个参数是否是函数
  if (typeof Constructor !== 'function') {
    throw new Error('第一个参数必须是构造函数');
  }

  // 创建一个新的空对象
  const obj = {};

  // 将新对象的原型设置为构造函数的原型
  // 这样新对象可以访问构造函数原型链上的属性和方法
  obj.__proto__ = Object.create(Constructor.prototype);

  // 调用构造函数,并将 this 绑定到新创建的对象上
  const result = Constructor.apply(obj, args);

  //  如果构造函数返回的是对象或函数,则返回该结果
  // 否则返回新创建的对象
  const isObject = typeof result === 'object' && result !== null;
  const isFunction = typeof result === 'function';
  return isObject || isFunction ? result : obj;
}

// 测试用的构造函数
function Constructor(name) {
  this.name = name;

  // 返回一个函数用于测试
  return function() { 
    console.log('返回引用数据类型');
  };
}

// 在构造函数的原型上添加一个方法
Constructor.prototype.sayName = function() {
  console.log(`My name is ${this.name}`);
}

// 创建一个 Constructor 实例
const me = ourNew(Constructor, 'kailin'); 

// 调用实例方法
me.sayName(); 

// 输出实例对象
console.log(me);  

不出意外的话、应该要出意外了

大家可以找找什么原因,思考一下

总结

我们理解了 new 操作符的实现原理,通过一个简单的示例说明了在 JavaScript 中使用 new 操作符创建对象的过程,并且展示了如何手动实现 new 操作符的功能,自此相信各位能够更好地理解js底层中的逻辑,也因此有所体会甚至升华,指尖弹出更多的创意。

本文仅个人理解,若有纰漏之处还请指正,一同学习。若有同仁见此句,厚脸求个免费的赞 (^▽^)