new.target 是 javascript 中检测构造函数调用上下文的机制,用于同步阶段防止抽象基类被直接实例化;但在异步图形引擎中需结合 init() 校验、注册表约束和模板白名单协同保障材质派生安全。

new.target 是 JavaScript 中用于检测构造函数调用上下文的原生机制,它在同步类继承中常用于防止直接实例化抽象基类。但在异步图形引擎(如基于 WebGPU、Unity DOTS 或自研管线的现代渲染系统)中,材质系统往往涉及延迟加载、运行时编译着色器、资源异步初始化等环节——此时单纯依赖 new.target 无法直接保障“基础材质安全派生”,需结合其语义与异步生命周期特征进行适配设计。
以下是从原理到落地的关键路径:
明确 new.target 在材质类中的原始作用
new.target 指向当前 new 表达式真正调用的构造函数。若在基类构造器中检查 new.target === BaseMaterial,即可阻止用户直接 new BaseMaterial(),强制要求子类继承并重写关键方法(如 createShaderModule()、bindResources())。
但注意:
- 它仅在同步构造阶段有效;
- 异步初始化(如
await material.loadTextures())不触发new.target; - WebGPU 的
GPUShaderModule创建、GPURenderPipeline编译均为 Promise 异步操作,不能靠new.target拦截。
将安全检查前移到异步准备入口
基础材质类应将“可派生性校验”从构造器移至首个异步初始化方法,例如:
class BaseMaterial {
protected constructor() {
if (new.target === BaseMaterial) {
throw new Error('BaseMaterial is abstract. Extend it instead.');
}
}
// 关键:异步初始化入口必须二次校验
async init(device: GPUDevice): Promise<void> {
// 防止子类忘记调用 super.init() 导致状态不全
if (new.target === BaseMaterial) {
throw new Error('init() called on abstract BaseMaterial');
}
// 确保子类已定义必要属性(如 shaderCode)
if (!this.shaderCode || typeof this.createPipeline !== 'function') {
throw new Error(`${new.target.name} must override shaderCode and createPipeline`);
}
await this.compileShader(device);
await this.createPipeline(device);
}
}</void>
这样既保留 new.target 的语义清晰性,又将其约束力延伸到异步执行起点。
结合材质注册表实现运行时派生保护
在引擎启动时,可维护一个 MaterialRegistry,只允许通过 registerMaterial(SubClass) 注册合法子类,禁止动态 eval() 或 Function() 构造材质类:
const MaterialRegistry = new Map<string typeof basematerial>();
export function registerMaterial<t extends typeof basematerial>(
name: string,
ctor: T
): asserts ctor is T & { readonly __isRegistered: true } {
if (ctor === BaseMaterial || new.target === BaseMaterial) {
throw new Error(`Cannot register abstract class ${name}`);
}
MaterialRegistry.set(name, ctor);
Object.defineProperty(ctor, '__isRegistered', { value: true });
}</t></string>
后续创建材质实例时(如 MaterialFactory.create('PBRMaterial')),先查注册表再 new ctor(),从源头杜绝未授权派生。
对接 Shader Graph 模板机制强化一致性
若引擎支持类似团结引擎的 Shader Graph Template(如通过 Create > Shader Graph > From Template),则基础材质类应与模板元数据对齐:
- 每个模板对应一个受信子类名(如
LitTemplateMaterial); -
BaseMaterial的init()方法可校验传入的templateId是否存在于预注册模板列表; - 拒绝加载未声明
@template标记的自定义着色器代码,避免绕过类型约束。
本质上,new.target 不是银弹,而是安全链条的第一环。在异步图形引擎中,它需与:
- 异步初始化守门逻辑(
init()校验) - 运行时注册机制(
registerMaterial) - 资源模板白名单(
templateId绑定)
三者协同,才能让基础材质既可扩展,又不失控。











