手写 new / instanceof / typeof
问题
请手写实现 new 操作符、instanceof 运算符和 typeof 运算符的核心逻辑。
这三个操作符是 JavaScript 类型系统和面向对象机制的基石,深入理解它们的底层原理不仅是面试高频考点,也有助于写出更健壮的代码。本文将逐一剖析原理并给出手写实现。
答案
一、手写 new 操作符
new 的执行过程
new 操作符用于创建一个构造函数的实例对象,其内部执行了以下 4 个步骤:
- 创建一个空对象 -- 作为即将返回的实例
- 设置原型链 -- 将空对象的
__proto__指向构造函数的prototype,使实例能访问原型上的方法 - 执行构造函数 -- 将
this绑定到新对象上,执行构造函数中的赋值逻辑 - 判断返回值 -- 如果构造函数显式返回一个对象,则使用该对象;否则返回步骤 1 创建的新对象
基础实现
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 Object为true typeof null === 'object',但null instanceof Object为false,这正好符合预期(返回null时应忽略,返回新对象)
返回值的三种情况
// 情况 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' -- 仍然返回新对象
完整测试用例
// 测试 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 相同的引用。
更多关于原型链的内容,参见 原型与继承。
基础实现
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;
}
__proto__ 是非标准属性(虽然浏览器都支持),Object.getPrototypeOf() 是 ES5 标准方法,更安全可靠。此外,通过 Object.create(null) 创建的对象没有 __proto__ 属性,但 Object.getPrototypeOf() 仍然能正常工作。
测试用例与边界情况
// 基础测试
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 的行为:
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 可以完全覆盖 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 年延续至今,因为修复它会破坏大量依赖此行为的代码。
这是 JavaScript 最著名的 bug 之一。TC39 曾在 ES6 早期草案中提出修复,但最终因向后兼容性问题放弃。在实际开发中,判断 null 应使用严格等于:value === null。
typeof 的返回值
// 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 种结果(number、string、boolean、undefined、symbol、bigint、object、function),无法区分 null、数组、日期等具体类型。Object.prototype.toString.call() 可以给出更精确的结果:
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.call(value) 时,会返回 [object Type] 格式的字符串,其中 Type 是值的内部 [[Class]] 属性。这个属性无法被用户代码直接修改(但可以通过 Symbol.toStringTag 来自定义),所以这是最可靠的类型判断方式。
增强版类型判断工具
/**
* 通用类型判断工具函数
* 返回小写的类型字符串
*/
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
// 跨 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 个步骤:创建空对象、设置原型链、执行构造函数、判断返回值。
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 引擎会沿着原型链查找:
- 先查找
obj自身属性 -- 没有method - 查找
obj.__proto__(即constructor.prototype)-- 找到method - 调用该方法,
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',但被忽略 |
// 返回对象 -- 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 相同的引用。
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():
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,Object.prototype.toString 的结果会被影响:
const obj = {
[Symbol.toStringTag]: 'MyCustomType',
};
console.log(Object.prototype.toString.call(obj)); // '[object MyCustomType]'
console.log(getType(obj)); // 'mycustomtype'
不过在实践中,只有少数内置对象(如 Map、Set、Promise)和刻意自定义的对象会使用此特性,一般情况下 Object.prototype.toString 足够可靠。
Q8: typeof 和 instanceof 的区别?各自适用场景?
答案:
| 比较维度 | typeof | instanceof |
|---|---|---|
| 操作对象 | 任何值 | 左侧必须是对象 |
| 返回值 | 字符串 | 布尔值 |
| 判断原理 | V8 类型标签 | 遍历原型链 |
| 能否判断原始类型 | ✔(除 null) | ✘ |
| 能否区分引用类型 | ✘(都返回 'object') | ✔ |
| 跨 iframe | ✔ | ✘ |
| 能否被篡改 | ✘ | ✔(Symbol.hasInstance) |
使用建议:
// 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) 创建的对象,不是通过自定义构造函数创建的实例。
// 方法 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 运算符时,会调用这个方法来确定结果。
// 基础用法
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.hasInstance,instanceof完全忽略原型链,仅调用此方法 null instanceof AnyClass始终返回false,即使Symbol.hasInstance返回true(规范规定)
Q11: new.target 是什么?有什么用?
答案:
new.target 是一个元属性(meta property),在函数内部使用:
- 通过
new调用时,new.target指向被new调用的构造函数 - 普通调用时,
new.target为undefined
// 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.target 和 this 绑定的关系,参见 this 指向。
Q12: Object.create(null) 和 有什么区别?
答案:
| 特性 | {} | Object.create(null) |
|---|---|---|
| 原型 | Object.prototype | null |
toString | ✔ 继承自原型 | ✘ 无此方法 |
hasOwnProperty | ✔ 继承自原型 | ✘ 无此方法 |
__proto__ 属性 | 指向 Object.prototype | 无此属性 |
| 用途 | 普通对象 | 纯净字典/缓存 |
// {} 相当于 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 源码中大量使用 Object.create(null) 来创建缓存对象和选项对象,避免原型链属性干扰键名查找,既更安全也有微小的性能优势(不需要沿原型链查找)。