关于模拟 new操作符的实现

259 阅读4分钟

很简单的一个分享~


首先看了一下new操作符的使用:

  function Person(name, age) {
    this.name = name;
    this.age = age;
  }
  
  Person.prototype.job = 'engineer';
  Person.prototype.sayAge = function() {
    return this.age;
  }
  
  const person1 = new Person('wang', 24);
  console.log(typeof person1); // "object"
  console.log(person1.name); // wang
  console.log(person1.sayAge()); // 24
  console.log(person1.job); // engineer

Person是一个构造函数,实例person1能访问到构造函数的属性,以及Person的原型对象Person.prototype的属性和方法。

其实我做的更像是已知new的过程,来求证的。new调用函数的过程在 ES5官方文档函数定义 一节中做了定义:

  1. 创建一个ECMAScript的原生对象obj.
  2. 给obj设置原生对象的内部属性;(和原型不同,内部属性表示为[[PropertyName]],两个方括号包裹属性名,并且属性名大写,比如[[Prototype]][[Constructor]])
  3. 设置obj的[[Class]]Object.
  4. 设置obj的[[Extensible]]为true.([[Extensible]]决定是否可以向对象添加属性)
  5. proto的值设置为Fprototype属性值.
  6. 如果proto是对象类型,则obj的内部属性[[Prototype]]的值为proto;(进行原型链关联,实现继承的关键)
  7. 如果proto不是对象类型,则设置obj的内部属性[[Prototype]]的值为内建构造函数Object[[prototype]]的值.(函数的prototype属性可以被改写,如果改成非对象类型,obj的[[prototype]]就指向Object的原型对象)
  8. 调用函数F,将其返回值赋给result;其中,F执行时的实参传递给[[Construct]](即F的本身)的参数,F内部this指向obj.
  9. 如果resultObject类型,返回result.
  10. 如果F返回的不是对象类型(第9步不成立),则返回创建的对象obj.

很多地方都更简洁的归纳为4个步骤:

  1. 创建一个新的空对象{}.
  2. 将构造函数的作用域赋值给这个新的对.(将构造函数的_prototype_赋值给对象的_proto_属性)
  3. 执行构造函数的代码.
  4. 返回对象.

开始我的简单的测试

  function Person(name, age) {
    this.name = name;
    this.age = age;
  }
  
  Person.prototype.job = 'engineer';
  Person.prototype.sayAge = function() {
    return this.age;
  }
  
  function newOperator(fn) {
    const obj = {};
    // 或者 const obj = new Object();
    obj._proto_ = fn.prototype;
    fn.apply(obj, arguments);
    return obj;
  }
  
  const person1 = newOperator(Person, 'wang', 24);
  console.log(person1.name);
  console.log(person1.age);

打印结果:

查查查原因:发现arguments导致的。

arguments是一个类数组,sliceshift结合Function.call可以处理。

重新改一下newOperator函数

  // example 1
  function newOperator(fn, ...res) {
    const obj = new Object();
    obj._proto_ = fn.prototype;
    fn.apply(obj, res);
    return obj;
  }
  
  // example 2
  function newOperator(fn) {
    const obj = new Object();
    obj._proto_ = fn.prototype;
    fn.apply(obj, Array.slice.call(arguments, 1));
    return obj;
  }
  
  // example 3
  function newOperator() {
    const obj = new Object();
    const Construct = [].shift.call(arguments)
    Construct.apply(obj, arguments);
    return obj;
  }
  
  // example 4
  function newOperator(fn) {
    newOperator.target = fn; // ES6 new.target 是指向构造函数
    const obj = Object.create(fn.prototype);
    fn.apply(obj, [].slice.call(arguments, 1));
    return obj;
  }

修正后的打印结果 -- 正确了:

如果我们在构造函数有处理返回值的情况:

  function Person(name, age) {
    this.name = name;
    this.age = age;
    return { name: 'yun' }
  }
  // newOperator 函数直接返回新的对象
  const person1 = newOperator(Person, 'wang', 24);
  console.log(person1.name); // wang

上面所说的newOperator是最简单的情况,没有考虑到之前说的构造函数显示返回的情况,所以在稍微考虑一下返回结果的情况:

如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。

  function newOperator() {
    const obj = new Object();
    const Construct = [].shift.call(arguments)
    const result = Construct.apply(obj, arguments);
    const isObject = typeof result === 'object' && result !== null;
    const isFunction = typeof result === 'function';
    if (isObject || isFunction) {
      return result;
    }
    return obj;
  }

简单测试一下:

  function Person(name, age) {
    this.name = name;
    this.age = age;
    return { name: 'yun' }
  }
  // newOperator 有判断构造函数的返回值的情况
  const person1 = newOperator(Person, 'wang', 24);
  console.log(person1.name); // yun

一点点说明

关于Object类型的检测,可以用typeOf,也可以用instanceof

  function newOperator() {
    const obj = new Object();
    const Construct = [].shift.call(arguments)
    const result = Construct.apply(obj, arguments);
    return (result instanceof Object) ? result : obj;
  }

但是instanceof的工作原理是:在表达式x instanceof Foo 中,如果 Foo 的原型(即 Foo.prototype)出现在x的原型链中,则返回 true,不然,返回false

实例创建之后重写构造函数原型,实例指向的原型已经不是构造函数的新的原型了。

  const Foo = function() {};
  const o = new Foo();
  o instanceof Foo; // true

  // 重写 Foo 原型
  Foo.prototype = {};
  o instanceof Foo; // false

关于创建的新的对象:

  //方式多种
  const obj = {};
  const obj = new Object();
  const obj = Object.create();

但是我还没有理解他们是有什么区别的吗,在最后创建出来实例当中的引用。

参考资料:

JS中new调用函数原理

深入之new的模拟实现