前言

在JavaScript中,原型链是不可缺少的一部分,也是JavaScript经典的一部分之一,而构造函数又是原型链的基础。

构造函数之所以被称为构造函数,其实是为了实现类的概念。但是构造函数其实就是函数。那么、既然是函数就可以直接进行调用,问题来了,怎么防止被直接调用呢?

一、new.target属性

new.targetES6new运算符引入的一个新属性

先看一下MDN上对它的描述:

new.target属性允许你检测函数或构造方法是否是通过new运算符被调用的。在通过new运算符被初始化的函数或构造方法中,new.target返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target的值是undefined

普通函数中

function say() {  
    console.log(new.target) // undefined 
} 
say()

如上代码:输出undefined,那么检测可以这么写

function T() {
  if (new.target !== T) {
    throw new Error('该构造函数必须使用new调用');
  }
}
T(); // error
new T(); // 通过

但是需要注意的是,由于new.target是ES6引入的,所以IE并不支持,不然是最完美的解决方案

二、构造函数里面声明严格模式

在严格模式下,函数被直接调用时禁止this指向window,那么可以通过this来判断

function T() {
  'use strict';
  console.log(this) // undefined
}
T()

可以看到在严格模式下,函数被直接调用this是undefined,那么在使用new操作符下,this指向的是该对象,所以就可以通过this是否为undefined来判断

function T() {
  'use strict';
  if (this === undefined) {
    throw new Error('该构造函数必须使用new调用');
  }
}
T(); // error
new T(); // 通过

三、使用 instanceof 判断

instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上

instanceof 用法

function B() {
}
let b = new B();
console.log(b instanceof B); // true
console.log(Object.getPrototypeOf(b) === B.prototype); // true

结合用法可以做如下判断

function T() {
  if (!(this instanceof T)) {
   throw new Error('该构造函数必须使用new调用');
  }
}
T();    // error
new T() // 通过

上述代码中,由于直接通过T()调用时this指向window,而Object.getPrototypeOf(window)结果是Window,并非是构造函数本身

instanceof还可以防止直接被挂在到其他对象下面调用,因为构造函数里面的`this`此时指向`obj`,如下

function T() {
  if (!(this instanceof T)) {
    throw new Error('该构造函数必须使用new调用');
  }
}
let obj = {};
obj.a = T; // 将构造函数挂到对象下面
obj.a(); // error

虽然看上去instanceof已经很完美,但是仍然有一个缺陷,就是当该构造函数被作为原型方式继承时无法检测

function T() {
  if (!(this instanceof T)) {
    throw new Error('该构造函数必须使用new调用');
  }
}
function Y() {
}
Y.prototype = new T(); // 原型继承
Y.prototype.say = T; // 添加方法
Y.prototype.constructor = Y;
new Y().say(); // 通过