限制构造函数只能通过 new 调用
三种方法确保构造函数必须使用 new 调用,避免普通函数调用
问题
JavaScript 中的函数可以作为构造函数使用(new Func())或作为普通函数调用(Func()),但语言本身不会区分这两种调用方式。如何限制构造函数只能通过 new 调用?
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// 使用 new 调用
console.log(new Person("战场", "小包")); // Person {firstName: "战场", lastName: "小包"}
// 普通函数调用 - 不会报错,但行为异常
console.log(Person("战场", "小包")); // undefined
解答
方案一:使用 instanceof
利用 new 调用时 this 指向实例,普通调用时 this 指向 window(非严格模式)或 undefined(严格模式)的特性:
function Person(firstName, lastName) {
if (!(this instanceof Person)) {
throw new TypeError('Function constructor Person cannot be invoked without "new"');
}
this.firstName = firstName;
this.lastName = lastName;
this.fullName = this.firstName + this.lastName;
}
// 普通调用会抛出错误
console.log(Person("战场", "小包"));
// Uncaught TypeError: Function constructor Person cannot be invoked without "new"
// new 调用正常
console.log(new Person("战场", "小包")); // Person {...}
这种方案存在瑕疵,可以通过 call/apply 伪造实例绕过检测:
console.log(Person.call(new Person(), "战场", "小包")); // 可以执行
方案二:使用 new.target(推荐)
ES6 引入的 new.target 属性专门用于检测函数是否通过 new 调用:
function Person(firstName, lastName) {
if (!new.target) {
throw new TypeError('Function constructor Person cannot be invoked without "new"');
}
this.firstName = firstName;
this.lastName = lastName;
}
// 普通调用返回 undefined
console.log("not new:", Person("战场", "小包"));
// Uncaught TypeError: Function constructor Person cannot be invoked without "new"
// new 调用返回构造函数
console.log("new:", new Person("战场", "小包")); // Person {...}
方案三:使用 ES6 Class(最佳方案)
Class 语法天然限制必须使用 new 调用:
class Person {
constructor(name) {
this.name = name;
}
}
// 普通调用直接报错
Person("小包");
// Uncaught TypeError: Class constructor Person cannot be invoked without 'new'
// 必须使用 new
new Person("小包"); // Person {name: "小包"}
扩展:使用 new.target 实现抽象类
在继承场景中,new.target 返回实际调用的构造函数(父类或子类),可以实现抽象类:
class Animal {
constructor(type, name, age) {
// 禁止直接实例化 Animal
if (new.target === Animal) {
throw new TypeError("Abstract class Animal cannot be instantiated");
}
this.type = type;
this.name = name;
this.age = age;
}
}
class Dog extends Animal {
constructor(name, age) {
super("dog", name, age);
}
}
// 抽象类不能实例化
new Animal("dog", "baobao", 18);
// Uncaught TypeError: Abstract class Animal cannot be instantiated
// 子类可以正常实例化
new Dog("baobao", 18); // Dog {type: "dog", name: "baobao", age: 18}
关键点
instanceof方案利用this指向差异,但可被call/apply绕过,适用于低版本浏览器new.target是 ES6 专门为检测构造函数调用方式设计的属性,返回new作用的构造函数或undefined- ES6 Class 天然限制必须使用
new调用,是面向对象编程的最佳方案 new.target在继承中返回子类构造函数,可用于实现抽象类(父类不可实例化,只能通过子类使用)
目录