You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TypeScript中使用__proto__实现对象扩展?

问题描述

我写了下面这段无法编译的TypeScript代码:

export function extendObject<
  T extends Object,
  X extends Object,
>(x: T, a: X): T & X {
  const p = { __proto__: x }
  Object.assign(p, a)
  return p
}

在return p处出现了如下编译错误:

Type '{ __proto__: T; }' is not assignable to type 'T & X'.
  Type '{ __proto__: T; }' is not assignable to type 'T'.
    '{ __proto__: T; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Object'.ts(2322)

这段代码的JavaScript版本是可以正常运行的:

export function extendObject(x, a) {
  const p = { __proto__: x }
  Object.assign(p, a)
  return p
}

使用方式如下:

const a = { foo: 'bar' }
const b = extendObject(a, { one: 2 })
assert(b.foo === 'bar')
assert(b.one === 2)

我希望通过__proto__实现性能优化,请问怎么给这段代码添加正确的TypeScript类型注解来通过编译?如果不能用__proto__,还有哪些替代方案?

另外我还尝试了下面的写法,同样无法编译:

export function extendObject<
  T extends Object,
  X extends Object,
>(x: T, a: X): T & X {
  const p: T & X = {}
  Object.keys(x).forEach(key => (p[key] = x[key]))
  Object.keys(a).forEach(key => (p[key] = a[key]))
  // Object.assign(p, a)
  return p
}

解决方案

一、修复使用__proto__的TypeScript版本

TypeScript报错的核心原因是:{ __proto__: T }无法被TS识别为严格的T类型——T可以是Object的任意子类型,仅设置原型并不代表对象本身符合T的结构约束。

我们可以通过类型断言告知TS最终的p符合T & X类型,同时建议把泛型约束从大写Object改为小写object(TS中object专门表示非原始值类型,比Object更精准):

export function extendObject<
  T extends object,
  X extends object,
>(x: T, a: X): T & X {
  const p = { __proto__: x } as T & X;
  Object.assign(p, a);
  return p;
}

或者在return时做断言,逻辑更清晰:

export function extendObject<
  T extends object,
  X extends object,
>(x: T, a: X): T & X {
  const p = { __proto__: x };
  Object.assign(p, a);
  return p as T & X;
}

这样就能通过编译,同时保留原型链优化的逻辑——原型链继承无需复制所有属性,访问x的属性时通过原型查找,性能优于浅拷贝。

二、无法使用__proto__时的替代方案

1. 使用Object.create创建原型继承对象

这是__proto__的标准替代写法,TS对它的类型支持更友好,且是ES5标准API,兼容性更好:

export function extendObject<
  T extends object,
  X extends object,
>(x: T, a: X): T & X {
  const p = Object.create(x);
  Object.assign(p, a);
  return p as T & X;
}

它的作用和{ __proto__: x }完全一致,都是创建以x为原型的新对象。

2. 浅拷贝合并(无原型继承)

如果不需要原型链优化,仅需合并属性,可以直接用浅拷贝:

export function extendObject<
  T extends object,
  X extends object,
>(x: T, a: X): T & X {
  return { ...x, ...a };
}

或者用Object.assign:

export function extendObject<
  T extends object,
  X extends object,
>(x: T, a: X): T & X {
  return Object.assign({}, x, a);
}

这种方式会把x和a的所有可枚举属性复制到新对象,无原型链依赖,但如果x属性较多,性能不如原型继承。

3. 修复遍历拷贝写法

你之前尝试的遍历写法报错,是因为{}无法直接断言为T & X,且Object.keys返回string[],TS无法确定key属于T或X。可以修改为:

export function extendObject<
  T extends object,
  X extends object,
>(x: T, a: X): T & X {
  const p = {} as T & X;
  // 用类型断言处理x的key
  (Object.keys(x) as Array<keyof T>).forEach(key => {
    p[key] = x[key];
  });
  // 处理a的key
  (Object.keys(a) as Array<keyof X>).forEach(key => {
    p[key] = a[key];
  });
  return p;
}

不过这种写法本质还是浅拷贝,和Object.assign/扩展运算符效果一致,没有原型链优化的优势。


内容的提问来源于stack exchange,提问作者Lance Pollard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 16:32:23