这一次,彻底搞懂 —— 构造函数和new

656 阅读4分钟

在此之前先问自己几个问题:

  • 什么是构造函数?和普通函数的区别?

  • 所有函数都可以被 new 吗?

  • new 操作符做了什么?

  • 如何自己实现一个 new

  • new.target 知道吗?

如果不太清楚,懵懵懂懂的话,不要慌乱,不妨往下阅读~

本篇文章就是着重解决这些问题的。

相信认真看完之后,你定会对以上问题有所收获~

构造函数

对于使用过基于类的语言 (如 Java 或 C++) 的开发人员来说,JavaScript 有点令人困惑,因为它是动态的,并且本身不提供一个 class 实现。(在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的)。即使我们模拟出的 “类”,也只是一个函数对象。在 Javascript 中的类和函数是无法区分的,我们似乎也只能定规范,比如构造函数模式,非严格规定:第一个字母大写的自定义函数就是构造函数。 从技术上讲,任何函数 (除了箭头函数) 都可以用作构造器。即:箭头函数外的任何函数都可以通过 new 来运行。

构造器的主要目的 —— 实现可重用的对象创建代码。

构造函数在技术上是常规函数。不过有两个约定

  1. 它们的命名以大写字母开头

  2. 它们只能由 "new" 操作符来执行。

    这样的调用意味着在开始时创建了空的 this,并在最后返回填充了值的 this

new

new 运算符 创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

一个简单的 new 的例子:

function Person(){
   this.name = 'Jack';
}
var p = new Person(); 

console.log(p) // Person {name: "Jack"}

如果不使用 new 关键词呢?

function Person(){
  this.name = 'Jack';
}
var p = Person();

console.log(p) // undefined
console.log(name) // Jack
console.log(p.name) // 'name' of undefined

我们发现,如果没有使用 new 关键字调用构造函数,返回的是 undefined ,这是因为在全局环境中调用函数 this 指向 window

那么当构造函数中 return 一个对象会是什么样呢?

function Person(){
   this.name = 'Jack'; 
   return {age: 18}
}
var p = new Person(); 

console.log(p)  // {age: 18}
console.log(p.name) // undefined
console.log(p.age) // 18

我们发现,当构造函数 return 的是一个和 this 无关的对象时,new 命令会直接返回这个新对象,而不是通过 new 执行步骤生成的 this 对象。

注意:构造函数返回的必须是一个对象,如果不是对象,还是会按照 new 的实现步骤,返回新生成的对象。例子:

function Person(){
   this.name = 'Jack'; 
   return 'tom';
}
var p = new Person(); 

console.log(p)  // {name: 'Jack'}
console.log(p.name) // Jack

因此我们总结一下:

new 关键词执行后总是会返回一个对象,要么是实例对象,要么是 return 语句指定的对象。

new 做了什么?

new 关键字会进行以下操作:

  1. 创建一个空对象(即{}
  2. 链接该对象(设置该对象的 constructor)到另一个对象
  3. 把步骤1新创建的对象作为 this 的上下文
  4. 如果该对象没有返回对象,则返回 this

高频面试题:手写 new

function _new(Ctor,...args){
  // ctor必须是个构造函数,而且不能是箭头函数 
  if(!Ctor.prototype){
  	throw new TypeError(`${Ctor} is not a constructor`)
  }
  // 创建一个新的空对象
  let obj = {};
  // 将这个对象的原型和构造函数的原型关联
  obj.__proto__ = Object.create(Ctor.prototype)
  // 将创建的对象作为上下文的this,执行构造函数
  let res = Ctor.apply(obj, args)
  let isObj = typeof res === 'object' && typeof res !==null
  let isFun = typeof res === 'function'
  // 返回这个新对象,即构造函数的 return 的结果;如果不是对象,就返回新生成的对象。
  return isObj || isFun ? res : obj;
}

new.target

在一个函数内部,我们可以使用 new.target 属性来检查它是否被使用 new 进行调用了。

对于常规调用,它为空,对于使用 new 的调用,则等于该函数:

function User() {
  alert(new.target);
}

// 不带 "new":
User(); // undefined

// 带 "new":
new User(); // function User { ... }

它可以被用在函数内部,来判断该函数是被通过 new 调用的“构造器模式”,还是没被通过 new 调用的“常规模式”。

我们也可以让 new 调用和常规调用做相同的工作,像这样:

function User(name) {
  if (!new.target) { // 如果你没有通过 new 运行我
    return new User(name); // ……我会给你添加 new
  }

  this.name = name;
}

let john = User("John"); // 将调用重定向到新用户
alert(john.name); // John

最后

如有问题,请大佬指正~

如有帮助,希望能够点赞收藏~

参考资料: