如何确保构造函数只能被new调用?

1,177 阅读3分钟

大家都知道在JS中函数一般有两种用途,一种是当做构造函数,一种是当做普通函数。但是呢,在JS内部并没有区分这两者的方式,很多时候我们规定构造函数首字母大写,但这种规定方式毕竟人为的,JS是不认的,所以当我们调用构造函数的时候,一不小心少了个new,那会出现什么问题呢?不妨敲个例子:

function Student(name, score) {
    this.name = name;
    this.score = score;
    this.result = this.name + this.score;
}
console.log(new Student('小明', '98'));
console.log(Student('小明', '98'));

输出结果

image.png 发现了没,当我们把构造函数当成普通函数来调用之后,它竟然没报错,这可太不友好了,有什么方法能让它报错吗?让我们来一探究竟~

1、instanceof

instanceof是用来干嘛的勒? instanceof运算符用于检测构造函数的prototype属性是否是在某个实例对象的原型链上,通俗一点就是instanceof用于检测某个对象是否是另一个对象的实例。话不多说,上代码:

function Student(name, score) {
    if(!(this instanceof Student)) {
        throw new TypeError('cannot be invoked without "new"');
    }
    this.name = name;
    this.score = score;
    this.result = this.name + this.score;
}
console.log(new Student('小明', '98'));
console.log(Student('小明', '98'));

我们再来看一看现在的输出结果:

image.png

很好,报错了,达到我们的目的了,那我们再回过头来看看这段代码this instanceof Student,看到这个this,你不慌吗?反正我慌了。通过new来调用构造函数,会生成一个新对象,并且把这个新对象绑定为调用函数的this,哎呀,简单点吧,new调用后this指向的是实例,普通调用指向的是window(非严格模式,严格模式指向undefined)。 这种方式确实能够让构造函数无法被普通调用。

但是

我们忽略了JS中的call/apply方法是可以修改this指向的,如果我们能够强行改变this的指向,那上面这段代码就傻了,区分不出来了,来试一下~

image.png

成功骗过去了,JS以为我们是通过new调用的。 既然这种方法有漏洞,那还有其他方法吗?肯定是有的,毕竟我不是第一个发现这个漏洞的,继续往下看。。。

2、new.target

官方在发现这个问题之后,于ES6中给new引入了new.target属性,MDN上是这么解释的:new.target属性允许你检测函数或构造方法是否是通过new运算符被调用的。在通过new运算符被初始化的函数或构造方法中,new.target返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target的值是undefined。 可以,这很棒!再试一下~

image.png 有了一种方法之后,总想着再找一种方法,来,继续~

3、class

对嘛,ES6不是还有个class吗,面向对象编程怎么能少了它呢,class明确规定:类的构造器必须使用new来调用

image.png 这样写可太舒服了,好了,我知道你想问:“既然class必须使用new,那要new.target干啥呢?”,不要再好奇了,好奇就看下篇文章,再想下去要秃了~

你,学废了吗?