Effective TypeScript

避免使用 JavaScript 对象包装类型

避免使用 JavaScript 的对象包装类型(如 `String`、`Number`),应始终优先使用基本类型。

基本类型与对象包装类型

JavaScript 中有七种基本类型值,包括 stringnumberbooleansymbolbigint 等[^1]。这些基本类型与对象(Object)的关键区别在于它们是不可变的,且不拥有方法[^2]。

然而,当你在一个基本类型值(例如字符串字面量 "hello")上调用方法时,JavaScript 会执行隐式转换,暂时将其转换为对应的对象包装类型(例如 String)以允许方法调用[^2]。一旦操作完成,该临时对象就会被丢弃[^3]。这种机制导致了一个令人困惑的运行时现象:如果你尝试给一个基本类型赋值属性,该属性会立即消失[^4]:

let x = "hello"; // x 是基本类型 string

x.language = 'English'; // 隐式转换为 String 对象,属性被设置
console.log(x.language); // x 再次隐式转换为 String 对象,但这次是新的临时对象
// 输出: undefined // 原始的 String 对象已被丢弃

TypeScript 中的类型区分与问题

TypeScript 明确区分了基本类型(小写)和它们的对象包装类型(大写):

  • stringString
  • numberNumber
  • booleanBoolean
  • symbolSymbol
  • bigintBigInt [^5]

Item 10 强烈建议开发者避免使用这些对象包装类型,始终坚持使用小写的基本类型[^6]。

当开发者不小心使用了对象包装类型时,最主要的问题出现在赋值操作和函数调用中。尽管基本类型可以赋值给它们的包装类型(因为结构兼容),但包装类型不能赋值给基本类型[^7]:

function isGreeting(phrase: string) { /* ... */ }

const wrapperString = new String("hello");
const primitiveString = "world";

// 1. 允许:基本类型可以赋值给包装类型
const s: String = primitiveString; // OK

// 2. 核心问题:包装类型不能赋值给基本类型
isGreeting(wrapperString);
// Argument of type 'String' is not assignable to parameter of type 'string'.
// 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible.

如果代码中不慎使用大写字母进行了类型注解,尽管运行时值仍为基本类型(因为 TypeScript 类型会被擦除,无法影响运行时行为[^8]),但这种标注是具有误导性和冗余的[^9]:

const s: String = "primitive"; // 运行时 s 仍然是基本类型 string

最佳实践总结

  • 坚持使用小写的基本类型:始终使用 stringnumberbooleansymbolbigint,避免使用大写的对象包装类型,以确保类型系统能够正确检查函数的参数和返回值[^6]。
  • 避免实例化:通常情况下,没有理由直接实例化对象包装类型(即使用 new String(...)new Number(...))。
  • Symbol 和 BigInt 的例外:在调用 Symbol()BigInt() 时,即使不使用 new 关键字,它们也会创建基本类型的值(symbolbigint),因此这些构造函数的使用是允许的[^10]。
在 GitHub 上编辑

上次更新于