跳到主要内容

手写 new / instanceof / typeof

问题

请手写实现 new 操作符、instanceof 运算符和 typeof 运算符的核心逻辑。

这三个操作符是 JavaScript 类型系统和面向对象机制的基石,深入理解它们的底层原理不仅是面试高频考点,也有助于写出更健壮的代码。本文将逐一剖析原理并给出手写实现。

前置知识

阅读本文前,建议先了解以下内容:

答案

一、手写 new 操作符

new 的执行过程

new 操作符用于创建一个构造函数的实例对象,其内部执行了以下 4 个步骤:

  1. 创建一个空对象 -- 作为即将返回的实例
  2. 设置原型链 -- 将空对象的 __proto__ 指向构造函数的 prototype,使实例能访问原型上的方法
  3. 执行构造函数 -- 将 this 绑定到新对象上,执行构造函数中的赋值逻辑
  4. 判断返回值 -- 如果构造函数显式返回一个对象,则使用该对象;否则返回步骤 1 创建的新对象

基础实现

myNew.ts
function myNew<T>(
constructor: new (...args: any[]) => T,
...args: any[]
): T {
// 1. 创建空对象,并将原型指向构造函数的 prototype
const obj = Object.create(constructor.prototype);

// 2. 执行构造函数,将 this 绑定到新对象
const result = constructor.apply(obj, args);

// 3. 判断返回值:如果构造函数返回了一个对象,则使用该对象
return result instanceof Object ? result : obj;
}
返回值判断的关键细节

第 3 步中 result instanceof Object 而不是 typeof result === 'object',是因为:

  • 构造函数可能返回函数(函数也是对象),此时 typeof 返回 'function',但 instanceof Objecttrue
  • typeof null === 'object',但 null instanceof Objectfalse,这正好符合预期(返回 null 时应忽略,返回新对象)

返回值的三种情况

new-return-cases.ts
// 情况 1:无返回值(最常见)
function Person(this: any, name: string): void {
this.name = name;
}

const p1 = myNew(Person as any, 'Alice');
console.log(p1.name); // 'Alice' -- 返回新对象

// 情况 2:返回一个对象 -- new 创建的对象被丢弃
function Weird(this: any, name: string): { custom: boolean } {
this.name = name;
return { custom: true }; // 显式返回对象
}

const p2 = myNew(Weird as any, 'Bob');
console.log((p2 as any).name); // undefined -- 新对象被丢弃
console.log((p2 as any).custom); // true -- 使用返回的对象

// 情况 3:返回原始值 -- 返回值被忽略
function Normal(this: any, name: string): number {
this.name = name;
return 42; // 返回原始值,被忽略
}

const p3 = myNew(Normal as any, 'Charlie');
console.log(p3.name); // 'Charlie' -- 仍然返回新对象

完整测试用例

myNew-test.ts
// 测试 1:基本功能
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak(): string {
return `${this.name} makes a sound`;
}
}

const animal = myNew(Animal, 'Dog');
console.log(animal.name); // 'Dog'
console.log(animal.speak()); // 'Dog makes a sound'
console.log(animal instanceof Animal); // true

// 测试 2:继承链
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
}

const dog = myNew(Dog, 'Buddy', 'Golden');
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true

// 测试 3:构造函数返回函数
function Factory(this: any): () => string {
this.value = 1;
return function(): string { return 'factory'; };
}

const f = myNew(Factory as any);
console.log(typeof f); // 'function' -- 返回了函数对象

二、手写 instanceof

instanceof 的原理

instanceof 运算符用于判断一个对象是否是某个构造函数的实例。它的核心原理是:沿着对象的原型链(__proto__)向上查找,看是否能找到与构造函数的 prototype 相同的引用

更多关于原型链的内容,参见 原型与继承

基础实现

myInstanceof.ts
function myInstanceof(left: any, right: Function): boolean {
// 原始类型直接返回 false
if (left === null || (typeof left !== 'object' && typeof left !== 'function')) {
return false;
}

// 获取构造函数的 prototype
const prototype = right.prototype;
// 获取对象的原型
let proto = Object.getPrototypeOf(left);

// 沿原型链向上查找
while (proto !== null) {
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}

return false;
}
为什么用 Object.getPrototypeOf 而不是 __proto__

__proto__ 是非标准属性(虽然浏览器都支持),Object.getPrototypeOf() 是 ES5 标准方法,更安全可靠。此外,通过 Object.create(null) 创建的对象没有 __proto__ 属性,但 Object.getPrototypeOf() 仍然能正常工作。

测试用例与边界情况

myInstanceof-test.ts
// 基础测试
class Animal {}
class Dog extends Animal {}
const dog = new Dog();

console.log(myInstanceof(dog, Dog)); // true
console.log(myInstanceof(dog, Animal)); // true
console.log(myInstanceof(dog, Object)); // true
console.log(myInstanceof(dog, Array)); // false

// 边界情况 1:原始类型
console.log(myInstanceof(42, Number)); // false
console.log(myInstanceof('hello', String)); // false
console.log(myInstanceof(true, Boolean)); // false

// 边界情况 2:包装对象
console.log(myInstanceof(new Number(42), Number)); // true
console.log(myInstanceof(new String('hi'), String)); // true

// 边界情况 3:null 和 undefined
console.log(myInstanceof(null, Object)); // false
console.log(myInstanceof(undefined, Object)); // false

// 边界情况 4:函数
console.log(myInstanceof(function(){}, Function)); // true
console.log(myInstanceof(function(){}, Object)); // true

Symbol.hasInstance 自定义行为

ES6 引入了 Symbol.hasInstance,允许类自定义 instanceof 的行为:

symbol-hasinstance.ts
class EvenNumber {
static [Symbol.hasInstance](instance: any): boolean {
return typeof instance === 'number' && instance % 2 === 0;
}
}

console.log(2 instanceof EvenNumber); // true
console.log(3 instanceof EvenNumber); // false
console.log(4.0 instanceof EvenNumber); // true

// 实际应用:类型校验
class TypedArray {
static [Symbol.hasInstance](instance: any): boolean {
return Array.isArray(instance) &&
instance.every((item: any) => typeof item === 'number');
}
}

console.log([1, 2, 3] instanceof TypedArray); // true
console.log([1, 'a', 3] instanceof TypedArray); // false
console.log('not array' instanceof TypedArray); // false
Symbol.hasInstance 的潜在风险

Symbol.hasInstance 可以完全覆盖 instanceof 的默认行为,这意味着 instanceof 的结果可能不再反映真实的原型链关系。在编写库或框架时应谨慎使用,避免误导使用者。


三、typeof 实现原理

V8 中的类型标记

typeof 是一个一元运算符,在 V8 引擎中通过读取值的**内部类型标签(tag bits)**来判断类型。在 JavaScript 最初的实现中(Brendan Eich 1995 年编写),值以 32 位存储,低 3 位作为类型标签:

类型标签(低 3 位)类型
000对象 (object)
001整数 (integer)
010浮点数 (double)
100字符串 (string)
110布尔值 (boolean)

null 在机器层面用空指针(全 0)表示,低 3 位也是 000,所以被 typeof 误判为 'object'。这个 bug 从 1995 年延续至今,因为修复它会破坏大量依赖此行为的代码。

typeof null === 'object' 的历史

这是 JavaScript 最著名的 bug 之一。TC39 曾在 ES6 早期草案中提出修复,但最终因向后兼容性问题放弃。在实际开发中,判断 null 应使用严格等于:value === null

typeof 的返回值

typeof-results.ts
// typeof 对各类型的返回值
console.log(typeof 42); // 'number'
console.log(typeof 'hello'); // 'string'
console.log(typeof true); // 'boolean'
console.log(typeof undefined); // 'undefined'
console.log(typeof Symbol('id')); // 'symbol'
console.log(typeof 10n); // 'bigint'

// 特殊情况
console.log(typeof null); // 'object' -- 历史 bug
console.log(typeof {}); // 'object'
console.log(typeof []); // 'object' -- 数组也是对象
console.log(typeof function(){}); // 'function' -- 唯一的非基础类型区分
console.log(typeof class{}); // 'function' -- class 本质是函数

// 未声明变量不会报错
console.log(typeof undeclaredVar); // 'undefined'

更精确的类型判断:Object.prototype.toString

typeof 只能区分 7 种结果(numberstringbooleanundefinedsymbolbigintobjectfunction),无法区分 null、数组、日期等具体类型。Object.prototype.toString.call() 可以给出更精确的结果:

typeOf.ts
function typeOf(value: unknown): string {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

// 测试
console.log(typeOf(42)); // 'number'
console.log(typeOf('hello')); // 'string'
console.log(typeOf(true)); // 'boolean'
console.log(typeOf(undefined)); // 'undefined'
console.log(typeOf(null)); // 'null' -- 正确区分 null
console.log(typeOf([])); // 'array' -- 正确区分数组
console.log(typeOf({})); // 'object'
console.log(typeOf(new Date())); // 'date'
console.log(typeOf(/regex/)); // 'regexp'
console.log(typeOf(new Map())); // 'map'
console.log(typeOf(new Set())); // 'set'
console.log(typeOf(new WeakMap())); // 'weakmap'
console.log(typeOf(new Error())); // 'error'
console.log(typeOf(Promise.resolve())); // 'promise'
Object.prototype.toString 的原理

当调用 Object.prototype.toString.call(value) 时,会返回 [object Type] 格式的字符串,其中 Type 是值的内部 [[Class]] 属性。这个属性无法被用户代码直接修改(但可以通过 Symbol.toStringTag 来自定义),所以这是最可靠的类型判断方式。

增强版类型判断工具

getType.ts
/**
* 通用类型判断工具函数
* 返回小写的类型字符串
*/
function getType(value: unknown): string {
// null 和 undefined 特殊处理
if (value === null) return 'null';
if (value === undefined) return 'undefined';

// 基础类型用 typeof
const type = typeof value;
if (type !== 'object' && type !== 'function') {
return type;
}

// 引用类型用 Object.prototype.toString
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

// 类型守卫工具
function isPlainObject(value: unknown): value is Record<string, unknown> {
if (getType(value) !== 'object') return false;
const proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === null;
}

// 测试
console.log(isPlainObject({})); // true
console.log(isPlainObject(Object.create(null))); // true
console.log(isPlainObject(new Date())); // false
console.log(isPlainObject([])); // false

四、类型判断方法汇总对比

方法适用场景能判断 null能判断数组跨 iframe精确度
typeof原始类型、函数
instanceof引用类型、继承关系
Object.prototype.toString所有类型
Array.isArray数组-
constructor引用类型

各方法的局限性:

  • typeof -- 无法区分 null[]{}new Date() 等(都返回 'object'
  • instanceof -- 不能判断原始类型;跨 iframe 时不同全局执行环境的构造函数不同,导致判断失效
  • Object.prototype.toString -- 可以被 Symbol.toStringTag 修改,需要注意自定义类的情况
  • Array.isArray -- 只能判断数组,但在所有场景下都准确(包括跨 iframe)
  • constructor -- 可以被手动修改,且 null/undefined 没有 constructor
cross-iframe-issue.ts
// 跨 iframe 的 instanceof 问题
// 假设 iframe 中创建了一个数组
const iframeArray: unknown = []; // 实际来自 iframe

// 在主窗口中
// instanceof 使用主窗口的 Array 构造函数判断
// iframeArray instanceof Array // false(不同 iframe 的 Array 不同)

// Array.isArray 不受影响
Array.isArray(iframeArray); // true(推荐方式)

常见面试问题

Q1: 手写 new 操作符的实现

答案

new 操作符内部执行了 4 个步骤:创建空对象、设置原型链、执行构造函数、判断返回值。

q1-myNew.ts
function myNew<T>(
constructor: new (...args: any[]) => T,
...args: any[]
): T {
// 1. 创建空对象,原型指向构造函数的 prototype
const obj = Object.create(constructor.prototype);
// 2. 执行构造函数,this 绑定到新对象
const result = constructor.apply(obj, args);
// 3. 如果构造函数返回对象则使用,否则返回新对象
return result instanceof Object ? result : obj;
}

// 验证
class User {
name: string;
constructor(name: string) { this.name = name; }
greet(): string { return `Hi, ${this.name}`; }
}

const user = myNew(User, 'Alice');
console.log(user.greet()); // 'Hi, Alice'
console.log(user instanceof User); // true

关键点在于 Object.create(constructor.prototype) 一步完成了"创建空对象"和"设置原型链"两件事。关于 this 绑定的更多细节,参见 this 指向

Q2: 为什么 new 出来的对象能访问构造函数的原型方法?

答案

因为 new 在第 1 步中执行了 Object.create(constructor.prototype),这相当于:

const obj = {};
Object.setPrototypeOf(obj, constructor.prototype);
// 即 obj.__proto__ === constructor.prototype

当访问 obj.method() 时,JavaScript 引擎会沿着原型链查找:

  1. 先查找 obj 自身属性 -- 没有 method
  2. 查找 obj.__proto__(即 constructor.prototype)-- 找到 method
  3. 调用该方法,this 指向 obj
class Greeter {
greet(): string { return 'Hello!'; }
}

const g = new Greeter();

// 原型链验证
console.log(Object.getPrototypeOf(g) === Greeter.prototype); // true
console.log(g.hasOwnProperty('greet')); // false
console.log(Greeter.prototype.hasOwnProperty('greet')); // true

更多原型链机制的细节,参见 原型与继承

Q3: 构造函数返回一个对象会怎样?返回原始值呢?

答案

构造函数返回值new 的结果说明
无返回值 / undefined返回新对象最常见的情况
返回对象(含数组、函数)返回该对象新对象被丢弃
返回原始值(数字、字符串等)返回新对象返回值被忽略
返回 null返回新对象null 虽然 typeof'object',但被忽略
q3-return-value.ts
// 返回对象 -- new 创建的实例被丢弃
function ReturnObj(this: any): { x: number } {
this.name = 'will be lost';
return { x: 42 };
}
const a = new (ReturnObj as any)();
console.log(a.x); // 42
console.log(a.name); // undefined -- 新对象被丢弃

// 返回原始值 -- 被忽略
function ReturnNum(this: any): number {
this.name = 'kept';
return 123;
}
const b = new (ReturnNum as any)();
console.log(b.name); // 'kept' -- 返回值被忽略

// 返回 null -- 也被忽略(这是容易混淆的点)
function ReturnNull(this: any): null {
this.name = 'still kept';
return null;
}
const c = new (ReturnNull as any)();
console.log(c.name); // 'still kept'
面试延伸

这个特性在一些设计模式中有实际应用。例如工厂模式可以利用构造函数返回对象的特性,在 new 调用时返回一个缓存的单例:

const instances = new Map<string, any>();

function Singleton(this: any, key: string) {
if (instances.has(key)) {
return instances.get(key); // 返回已有对象,new 创建的对象被丢弃
}
this.key = key;
instances.set(key, this);
}

const a = new (Singleton as any)('app');
const b = new (Singleton as any)('app');
console.log(a === b); // true -- 单例

Q4: 手写 instanceof 的实现

答案

instanceof 的核心原理是沿着对象的 __proto__ 链逐级向上查找,看是否能找到与构造函数 prototype 相同的引用。

q4-myInstanceof.ts
function myInstanceof(left: any, right: Function): boolean {
// 原始类型直接返回 false(原始值没有原型链)
if (left === null || (typeof left !== 'object' && typeof left !== 'function')) {
return false;
}

const prototype = right.prototype;
let proto = Object.getPrototypeOf(left);

// 沿原型链向上遍历
while (proto !== null) {
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}

return false;
}

// 验证与原生 instanceof 结果一致
class A {}
class B extends A {}
const b = new B();

console.log(myInstanceof(b, B)); // true
console.log(myInstanceof(b, A)); // true
console.log(myInstanceof(b, Object)); // true
console.log(myInstanceof(b, Array)); // false

Q5: instanceof 的局限性有哪些?

答案

instanceof 有以下主要局限性:

1. 无法判断原始类型

console.log(42 instanceof Number);        // false
console.log('hello' instanceof String); // false
console.log(true instanceof Boolean); // false

// 原因:原始类型没有原型链
// 但包装对象可以
console.log(new Number(42) instanceof Number); // true

2. 跨 iframe / 跨 Realm 失效

// 不同 iframe 拥有独立的全局执行环境
// iframe 中的 Array !== 主窗口的 Array
// 所以 iframeArr instanceof Array 可能为 false

// 解决方案:使用 Array.isArray()
Array.isArray(someArray); // 跨 iframe 也准确

3. 可以被 Symbol.hasInstance 篡改

class Fake {
static [Symbol.hasInstance](): boolean {
return true; // 任何值都返回 true
}
}

console.log(42 instanceof Fake); // true(被篡改)
console.log('hello' instanceof Fake); // true(被篡改)
console.log(null instanceof Fake); // false(null 是特例,规范规定直接返回 false)

4. 原型链被修改后结果不可预期

function Foo() {}
const foo = new (Foo as any)();
console.log(foo instanceof Foo); // true

Foo.prototype = {}; // 修改了 prototype
console.log(foo instanceof Foo); // false -- 原型链断裂

Q6: typeof null 为什么是 'object'?

答案

这是 JavaScript 最早版本(1995 年)遗留的 bug。在 JS 的初始实现中,值在内存中以 32 位表示,低 3 位作为类型标签

  • 000:对象
  • 001:整数
  • 010:浮点数
  • 100:字符串
  • 110:布尔值

null 的值是机器空指针(C 语言中的 NULL),在大多数平台上表示为全零0x00000000)。因此它的低 3 位也是 000,被 typeof 的实现代码误判为对象。

// typeof null 的判断伪代码(V8 早期)
// if (value 的低 3 位 === 000) {
// if (value === NULL_POINTER) {
// // 本应在此返回 'null',但原始代码没有这个判断
// }
// return 'object';
// }

TC39 曾在 ES 2015 草案中提出将 typeof null 改为 'null',但因为太多现有代码依赖 typeof null === 'object' 的行为,最终放弃了这个修复。

正确判断 null 的方式

// 方式 1:严格等于(推荐)
value === null;

// 方式 2:Object.prototype.toString
Object.prototype.toString.call(null); // '[object Null]'

// 方式 3:与 undefined 一起判断
value == null; // null 或 undefined 都为 true

Q7: 如何准确判断一个变量的类型?

答案

最准确的方式是使用 Object.prototype.toString.call()

q7-accurate-type.ts
function getType(value: unknown): string {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

// 基础类型
console.log(getType(42)); // 'number'
console.log(getType('hello')); // 'string'
console.log(getType(true)); // 'boolean'
console.log(getType(undefined)); // 'undefined'
console.log(getType(null)); // 'null'
console.log(getType(Symbol())); // 'symbol'
console.log(getType(10n)); // 'bigint'

// 引用类型
console.log(getType([])); // 'array'
console.log(getType({})); // 'object'
console.log(getType(new Date())); // 'date'
console.log(getType(/regex/)); // 'regexp'
console.log(getType(new Map())); // 'map'
console.log(getType(new Set())); // 'set'
console.log(getType(new Error())); // 'error'
console.log(getType(function(){})); // 'function'
注意 Symbol.toStringTag

如果对象定义了 Symbol.toStringTagObject.prototype.toString 的结果会被影响:

const obj = {
[Symbol.toStringTag]: 'MyCustomType',
};

console.log(Object.prototype.toString.call(obj)); // '[object MyCustomType]'
console.log(getType(obj)); // 'mycustomtype'

不过在实践中,只有少数内置对象(如 MapSetPromise)和刻意自定义的对象会使用此特性,一般情况下 Object.prototype.toString 足够可靠。

Q8: typeof 和 instanceof 的区别?各自适用场景?

答案

比较维度typeofinstanceof
操作对象任何值左侧必须是对象
返回值字符串布尔值
判断原理V8 类型标签遍历原型链
能否判断原始类型✔(除 null)
能否区分引用类型✘(都返回 'object'
跨 iframe
能否被篡改✔(Symbol.hasInstance

使用建议

q8-when-to-use.ts
// 1. 判断原始类型 -- 用 typeof
if (typeof value === 'string') { /* ... */ }
if (typeof value === 'number') { /* ... */ }
if (typeof value === 'function') { /* ... */ }

// 2. 判断 null -- 用严格等于
if (value === null) { /* ... */ }

// 3. 判断数组 -- 用 Array.isArray
if (Array.isArray(value)) { /* ... */ }

// 4. 判断特定类的实例 -- 用 instanceof
if (value instanceof Date) { /* ... */ }
if (value instanceof RegExp) { /* ... */ }
if (value instanceof CustomClass) { /* ... */ }

// 5. 精确判断所有类型 -- 用 Object.prototype.toString
function getType(v: unknown): string {
return Object.prototype.toString.call(v).slice(8, -1).toLowerCase();
}

更多关于类型判断的内容,参见 数据类型与类型判断

Q9: 如何判断一个对象是不是纯对象(plain object)?

答案

纯对象(plain object)是指通过 {} 字面量或 new Object()Object.create(null) 创建的对象,不是通过自定义构造函数创建的实例。

q9-plain-object.ts
// 方法 1:检查原型
function isPlainObject(value: unknown): value is Record<string, unknown> {
if (typeof value !== 'object' || value === null) return false;

const proto = Object.getPrototypeOf(value);
// {} 的原型是 Object.prototype
// Object.create(null) 的原型是 null
return proto === Object.prototype || proto === null;
}

// 测试
console.log(isPlainObject({})); // true
console.log(isPlainObject({ a: 1 })); // true
console.log(isPlainObject(Object.create(null))); // true
console.log(isPlainObject(new Date())); // false
console.log(isPlainObject([])); // false
console.log(isPlainObject(null)); // false

// 方法 2:更严格的检查(参考 lodash 实现)
function isPlainObjectStrict(value: unknown): value is Record<string, unknown> {
if (Object.prototype.toString.call(value) !== '[object Object]') {
return false;
}

const proto = Object.getPrototypeOf(value);
if (proto === null) return true;

// 检查是否由 Object 构造函数创建
const Ctor = Object.hasOwnProperty.call(proto, 'constructor') && proto.constructor;
return typeof Ctor === 'function' && Ctor === Object;
}
实际应用场景

判断纯对象在以下场景中非常有用:

  • 深拷贝:只对纯对象递归拷贝,特殊对象(Date、RegExp 等)需要特殊处理
  • 数据合并:如 Object.assign 的深度版本,只对纯对象递归合并
  • 序列化:判断对象是否可以安全地 JSON.stringify

Q10: Symbol.hasInstance 是什么?如何自定义 instanceof 行为?

答案

Symbol.hasInstance 是一个内置的 Symbol 值,用于定义一个类的静态方法,当使用 instanceof 运算符时,会调用这个方法来确定结果。

q10-symbol-hasinstance.ts
// 基础用法
class Range {
min: number;
max: number;

constructor(min: number, max: number) {
this.min = min;
this.max = max;
}

// 自定义 instanceof 行为
static [Symbol.hasInstance](instance: any): boolean {
return typeof instance === 'number' &&
instance >= 0 && instance <= 100;
}
}

console.log(50 instanceof Range); // true
console.log(150 instanceof Range); // false
console.log('50' instanceof Range); // false

// 实际应用:类型校验框架
class Schema {
private validator: (value: any) => boolean;

constructor(validator: (value: any) => boolean) {
this.validator = validator;
}

// 让 instanceof 作为类型验证使用
[Symbol.hasInstance](instance: any): boolean {
return this.validator(instance);
}
}

const EmailSchema = new Schema(
(v: any) => typeof v === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)
);

console.log('user@example.com' instanceof EmailSchema); // true
console.log('invalid-email' instanceof EmailSchema); // false
注意事项
  • Symbol.hasInstance 定义为静态方法时(static [Symbol.hasInstance]),作用于类本身
  • 定义为实例方法时([Symbol.hasInstance]),作用于实例的 instanceof 检查
  • 当目标定义了 Symbol.hasInstanceinstanceof 完全忽略原型链,仅调用此方法
  • null instanceof AnyClass 始终返回 false,即使 Symbol.hasInstance 返回 true(规范规定)

Q11: new.target 是什么?有什么用?

答案

new.target 是一个元属性(meta property),在函数内部使用:

  • 通过 new 调用时,new.target 指向被 new 调用的构造函数
  • 普通调用时,new.targetundefined
q11-new-target.ts
// 1. 判断是否通过 new 调用
function Person(this: any, name: string): void {
if (!new.target) {
throw new TypeError('必须使用 new 调用 Person');
}
this.name = name;
}

new (Person as any)('Alice'); // 正常
// Person('Alice'); // TypeError

// 2. 在继承中,new.target 指向最终被 new 的类
class Parent {
constructor() {
console.log(new.target.name);
}
}

class Child extends Parent {
constructor() {
super();
}
}

new Parent(); // 输出: 'Parent'
new Child(); // 输出: 'Child' -- new.target 是 Child,不是 Parent

// 3. 实现抽象类(不能直接实例化,只能被继承)
class AbstractShape {
constructor() {
if (new.target === AbstractShape) {
throw new Error('AbstractShape 不能直接实例化');
}
}
}

class Circle extends AbstractShape {
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
}

// new AbstractShape(); // Error: 不能直接实例化
new Circle(5); // 正常

关于 new.targetthis 绑定的关系,参见 this 指向

Q12: Object.create(null) 和 有什么区别?

答案

特性{}Object.create(null)
原型Object.prototypenull
toString✔ 继承自原型✘ 无此方法
hasOwnProperty✔ 继承自原型✘ 无此方法
__proto__ 属性指向 Object.prototype无此属性
用途普通对象纯净字典/缓存
q12-create-null.ts
// {} 相当于 Object.create(Object.prototype)
const obj1 = {};
console.log(obj1.toString()); // '[object Object]'
console.log('toString' in obj1); // true
console.log(Object.getPrototypeOf(obj1) === Object.prototype); // true

// Object.create(null) 创建一个没有原型的纯净对象
const obj2 = Object.create(null);
// obj2.toString(); // TypeError: obj2.toString is not a function
console.log('toString' in obj2); // false
console.log(Object.getPrototypeOf(obj2)); // null

// 实际用途:安全的键值对存储
const cache = Object.create(null);
cache['__proto__'] = 'safe'; // 只是普通属性,不会影响原型链
cache['constructor'] = 'safe'; // 不会与 Object.prototype.constructor 冲突
Vue 2 源码中的应用

Vue 2 源码中大量使用 Object.create(null) 来创建缓存对象和选项对象,避免原型链属性干扰键名查找,既更安全也有微小的性能优势(不需要沿原型链查找)。

相关链接