各位观众,掌声欢迎!今天咱们要聊聊 JavaScript 里一个有点神秘,但关键时刻能派上大用场的家伙:new.target
。
开场白:new.target
是啥?
你可能见过 this
,知道它指向谁。但 new.target
是什么鬼?简单来说,new.target
就像一个侦探,专门负责追踪你是怎么被“new”出来的。它只在函数或者类的构造函数里有意义。
如果一个函数或者类是用 new
关键字调用的,new.target
就会指向这个函数或类本身。如果不是用 new
调用的,new.target
就是 undefined
。
第一幕:函数构造器里的 new.target
在 ES5 及之前的年代,我们用函数来模拟类。那时候,防止函数被直接调用,确保只能通过 new
来创建实例,是个常见需求。new.target
出现之前,大家可能会用 this instanceof MyConstructor
这样的方式来判断。现在,有了 new.target
,就优雅多了。
function Person(name) {
if (!new.target) {
throw new Error("Person 必须用 'new' 调用");
}
this.name = name;
}
// 正确用法
const john = new Person("John");
console.log(john.name); // 输出 "John"
// 错误用法
try {
const jane = Person("Jane"); // 直接调用,会报错
} catch (error) {
console.error(error.message); // 输出 "Person 必须用 'new' 调用"
}
在这个例子里,如果 Person
函数不是通过 new
调用的,new.target
就是 undefined
,if (!new.target)
就会触发错误,阻止了函数的直接调用。
第二幕:Class 里的 new.target
ES6 引入了 class 语法,new.target
在 class 构造函数里也一样有效。但它还有更高级的用法,尤其是在抽象类和继承方面。
class Animal {
constructor() {
if (new.target === Animal) {
throw new Error("Animal 是一个抽象类,不能直接实例化");
}
}
speak() {
throw new Error("speak 方法必须在子类中实现");
}
}
class Dog extends Animal {
constructor(name) {
super(); // 必须先调用 super()
this.name = name;
}
speak() {
return "Woof!";
}
}
// 错误用法
try {
const animal = new Animal(); // 报错:Animal 是一个抽象类,不能直接实例化
} catch (error) {
console.error(error.message);
}
// 正确用法
const dog = new Dog("Buddy");
console.log(dog.speak()); // 输出 "Woof!"
这里,Animal
类被设计成一个抽象类,不能直接实例化。new.target === Animal
确保了这一点。只有子类(比如 Dog
)才能被 new
出来。
第三幕:new.target
与继承
new.target
在继承中扮演着重要的角色,尤其是在多层继承的情况下。它能准确地告诉你,最终是谁被 new
出来的。
class Base {
constructor() {
console.log("Base constructor, new.target:", new.target.name);
}
}
class Derived extends Base {
constructor() {
super();
console.log("Derived constructor, new.target:", new.target.name);
}
}
class FurtherDerived extends Derived {
constructor() {
super();
console.log("FurtherDerived constructor, new.target:", new.target.name);
}
}
const fd = new FurtherDerived();
// 输出:
// Base constructor, new.target: FurtherDerived
// Derived constructor, new.target: FurtherDerived
// FurtherDerived constructor, new.target: FurtherDerived
可以看到,即使在 Base
类的构造函数里,new.target
仍然指向最终被 new
出来的 FurtherDerived
类。这使得我们可以在基类中,根据最终的派生类执行不同的逻辑。
第四幕:new.target
的高级用法:工厂模式
new.target
还可以用于实现更灵活的工厂模式。
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error("Shape 是一个基类,不能直接实例化");
}
}
static create(type, ...args) {
switch (type) {
case "circle":
return new Circle(...args);
case "square":
return new Square(...args);
default:
throw new Error("不支持的形状类型");
}
}
draw() {
throw new Error("draw 方法必须在子类中实现");
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
draw() {
return `绘制一个半径为 ${this.radius} 的圆形`;
}
}
class Square extends Shape {
constructor(side) {
super();
this.side = side;
}
draw() {
return `绘制一个边长为 ${this.side} 的正方形`;
}
}
const circle = Shape.create("circle", 5);
console.log(circle.draw()); // 输出 "绘制一个半径为 5 的圆形"
const square = Shape.create("square", 10);
console.log(square.draw()); // 输出 "绘制一个边长为 10 的正方形"
在这个例子里,Shape
类提供了一个静态方法 create
,根据传入的类型创建不同的形状实例。虽然 Shape
本身不能直接实例化,但可以通过 create
方法间接创建子类的实例。
第五幕:new.target
与元编程
new.target
还可以结合 Proxy 等元编程特性,实现更高级的功能,比如控制类的实例化过程。
function classFactory(className, baseClass, properties) {
const handler = {
construct(target, args, newTarget) {
// 在构造函数调用前进行拦截
console.log(`正在创建 ${className} 的实例...`);
const instance = Reflect.construct(target, args, newTarget);
// 在构造函数调用后进行拦截
console.log(`${className} 的实例创建完成`);
return instance;
}
};
const proxyClass = new Proxy(baseClass, handler);
return proxyClass;
}
class MyClass {
constructor(name) {
this.name = name;
}
}
const ProxiedMyClass = classFactory("MyClass", MyClass);
const instance = new ProxiedMyClass("Alice"); // 输出拦截信息
console.log(instance.name); // 输出 Alice
这个例子中,classFactory
函数使用 Proxy 拦截了 MyClass
的构造过程,可以在实例创建前后执行自定义逻辑。
new.target
的总结:
咱们来总结一下 new.target
的作用:
- 防止函数或抽象类被直接调用: 确保函数只能通过
new
来创建实例,或者阻止抽象类被直接实例化。 - 在继承中确定最终的派生类: 即使在基类的构造函数里,也能知道最终是谁被
new
出来的。 - 实现更灵活的工厂模式: 可以根据不同的条件创建不同的实例,而无需直接使用
new
关键字。 - 结合元编程进行更高级的控制: 可以拦截类的实例化过程,实现更复杂的逻辑。
表格总结:
用途 | 说明 | 示例 |
---|---|---|
防止函数/抽象类直接调用 | 确保函数只能通过 new 调用,或者阻止抽象类被直接实例化。 |
javascript function Person(name) { if (!new.target) { throw new Error("Person 必须用 'new' 调用"); } this.name = name; } class Animal { constructor() { if (new.target === Animal) { throw new Error("Animal 是一个抽象类"); } } } |
在继承中确定最终派生类 | 即使在基类的构造函数中,也能知道最终哪个类被实例化。 | javascript class Base { constructor() { console.log("Base constructor, new.target:", new.target.name); } } class Derived extends Base {} const d = new Derived(); // Base constructor, new.target: Derived |
实现工厂模式 | 根据类型创建不同类的实例,无需直接使用 new 。 |
javascript class Shape { static create(type) { if (type === 'circle') return new Circle(); if (type === 'square') return new Square(); } } |
结合元编程进行高级控制 | 拦截类的实例化过程,执行自定义逻辑。 | javascript const handler = { construct(target, args) { console.log('Creating instance'); return new target(...args); } }; const MyClass = new Proxy(class {}, handler); new MyClass(); // 输出: Creating instance |
结尾:
new.target
虽然平时不显山不露水,但在某些场景下,却是解决问题的关键。掌握了它,你的 JavaScript 功力就能更上一层楼。希望今天的讲解对你有所帮助!下次再见!