认识 new:

new 操作符做的事情是:

  1. 语句执行时,创建一个新的空对象并且将该对象的 proto 属性链接到构造函数的 prototype
  2. 将新创建的对象作为构造函数的 this 指向,然后调用该构造函数
  3. 如果构造函数内部 return 一个新的对象,则返回该对象,否则返回这个创建的新对象
  1. function fn() {
  2. this.a =123;
  3. }
  4. const a = new fn();
  5. 1. a.__proto__ === fn.prototype // true
  6. 2. fn this 指向 a
  7. 3. 不能返回一个对象,若返回一个对象,a.__proto__ === Object.prototype

知道了上述步骤,现在来验证一下:

  1. function demo1(){
  2. this.a = 1;
  3. let b={b:1};
  4. return b;
  5. }
  6. function demo2(){
  7. this.a = 1;
  8. }
  9. let d1 = new demo1();
  10. let d2 = new demo2();

运行上面的代码后,将得到如下的打印结果
image.png

可以看到,因为 demo1 函数中“return”了一个通过对象字面量创建的对象,所以最终得到的 d1 对象的 _proto _属性的 constructor 指向了 Object,而不是指向 demo1,并且这里 this.a 似乎也没有起效。

而观察对象 d2 可以看到,d2 对象的 _proto _属性的 constructor 指向了 demo2,并且可以看到 this.a 起效了。这就验证了前面说到的三段步骤。

你看,由于 demo1 内部有 return 一个新对象,new 操作符帮助我们创建的新对象没有用上,但是在执行构造函数的时候,又偏偏让函数执行时的上下文 this 指向了新的对象,所以 demo1 返回的结果上没有 a,而 demo2 没有 return 时是符合预期的。

因此作为构造函数的函数,返回值必定不是对象。

扩展new

我们知道,在函数里不写 return 相当于 return undefined ,所以又能够联想到 new 构造函数时,如果返回的结果不是一个对象会被忽略而返回 new 帮我们创建的变量,也正因此 String 这样的函数才既可以用来做构造函数,又可以用来作为类型转换函数。

有些时候我们定义的函数出于某种目的会强制要求必须通过 new 调用,比如像 Promise,那么怎么来做这样的检查呢?重新思考之前说的 new 的执行步骤。

new 在执行构造函数前,我们已经创建了一个新对象然后还让新对象的 _proto 属性指向了构造函数的 prototype(这是一个只有函数才拥有的属性,打印这个属性你就会发现它也是一个对象,其上有属性 constructor 和 _proto ),在这之后将构造函数的 this 指向了新对象然后才执行构造函数。这也就意味着,我们可以在构造函数中通过 this 拿到这个新对象,然后去判断这个对象上的 constructor 是不是当前在执行的构造函数即可,如下所示:

  1. function demo3(){
  2. if(this.constructor !== demo3){
  3. throw new Error('本函数必须作为构造函数使用');
  4. }
  5. this.a = 3;
  6. }

你可以试试。

模拟 new 的过程

前面讲了 new 的执行过程,由于在 JavaScript 中 new 是一个操作符,我们是没法通过模拟的方式来真正的实现一个操作符的,但是我们可以通过实现一个函数来模拟 操作符的表现,如下所示:

  1. // 模拟函数
  2. function newFunc(fn){
  3. let newObj = Object.create(null);
  4. newObj.__proto__ = fn.prototype;
  5. return function(){
  6. let rt = fn.apply(newObj, arguments);
  7. return !rt || typeof rt !== 'object' ? newObj: rt;
  8. }
  9. }
  10. let demo6 = function(v1,v2,v3){
  11. this.v1=v1;
  12. this.v2=v2;
  13. this.v3=v3;
  14. }
  15. let d6 = newFunc(demo6)(1,2,3)

注意,这里必须要说明的是,这只是在模拟操作,并没有说实现一个跟 new 一模一样的,这里 _proto _属性也是一个非标准化的属性,所以日常工作中不要使用 。

总结

  1. 创建对象的手法有:对象字面量“{}”;Object.create();new Constructor
  2. 在实现构造函数时,如果有预期需要通过 new 来调用,则不要在函数内部返回一个对象
  3. new 的执行过程上面说了,不再赘述,不清楚的再回头看一下