跳到主要内容

in 运算符的作用是什么?

下文默认基于 TypeScript 5.x。in 既有运行时用法,也有类型层面的用法,面试里最好分开答。

面试速答(30 秒版 TL;DR)

  • 在运行时,in 用来判断“某个属性名是否存在于对象中”,包括原型链上的属性。
  • 在 TypeScript 里,in 还常出现在:
    • 联合对象类型收窄;
    • 映射类型(mapped types)。
  • 所以这道题别只答一半。比较完整的答法是:
    • 值层面:属性存在性判断;
    • 类型层面:遍历键名生成新类型。

1. 运行时里的 in

const user = {
name: "Tom",
};

console.log("name" in user); // true
console.log("toString" in user); // true
console.log("age" in user); // false

这里最容易丢分的一点是:

  • in 判断的不只是对象“自有属性”;
  • 它连原型链上的属性也会算进去。

所以:

"toString" in user; // true

2. inhasOwnProperty 有什么区别

很多面试官会追问这个。

  • in:看对象本身 + 原型链;
  • Object.hasOwn(obj, key)hasOwnProperty:只看对象自身属性。
const obj = Object.create({ inherited: true });
obj.self = true;

console.log("inherited" in obj); // true
console.log(Object.hasOwn(obj, "inherited")); // false

所以如果你的语义是“这个字段是不是对象自己真的定义了”,那就不要只用 in

3. TypeScript 里常用它做联合类型收窄

type Cat = {
meow: () => void;
};

type Dog = {
bark: () => void;
};

function speak(animal: Cat | Dog) {
if ("meow" in animal) {
animal.meow();
} else {
animal.bark();
}
}

这里 in 的意义是:

  • 运行时判断这个属性是否存在;
  • 编译期顺带帮助 TypeScript 把联合类型缩小。

这是前端业务代码里最常见的用法之一。

4. 用 in 判断 unknown 时要先保护对象分支

function readId(value: unknown) {
if (
typeof value === "object" &&
value !== null &&
"id" in value
) {
return value.id;
}

return undefined;
}

原因是:

  • in 左边是属性名;
  • 右边必须是对象类型;
  • null 和原始值都不能直接拿来做这个判断。

5. 类型层面的 in:映射类型

这是 TypeScript 面试里另一个高频点。

type Keys = "name" | "age";

type User = {
[K in Keys]: string;
};

等价于:

type User = {
name: string;
age: string;
};

所以这里的 in 不是运行时运算符语义,而是:

  • 遍历一个联合键集合;
  • 为每个键生成一份属性定义。

常见例子:

type ReadonlyUser<T> = {
readonly [K in keyof T]: T[K];
};

这就是很多工具类型的基础。

6. 高频面试题标准答法

6.1 in 能不能判断属性值是不是 undefined

不能直接这么理解。

in 判断的是“键是否存在”,不是“值是否有意义”。

const obj = { name: undefined };

console.log("name" in obj); // true

6.2 为什么 in 可以帮助联合类型收窄

因为如果某个分支独有某个属性,那么一旦该属性存在,编译器就能推断当前值更接近哪个分支。

6.3 映射类型里的 in 和运行时 in 是一个东西吗

不是一个层级:

  • 运行时 in 是 JavaScript 运算符;
  • 映射类型里的 in 是 TypeScript 类型系统语法。

7. 常见误区

  • 误区 1:以为 in 只检查对象自有属性。
    • 它会沿原型链查。
  • 误区 2:以为 in 是判断值是否不为 undefined
    • 它判断的是键是否存在。
  • 误区 3:只记住运行时用法,不知道它还能用于映射类型。

速记要点

  • 值层面的 in:判断属性名是否存在于对象或其原型链上。
  • 类型层面的 in:遍历键集合生成新类型。
  • 联合对象分支判断时,in 是很常见的收窄手段。