当我们使用 new 关键字时,背后到底发生了什么?这个看似简单的操作,实际上完成了一系列复杂的步骤。理解 new 的工作原理,是掌握 JavaScript 面向对象编程的关键。
前言:从 new 的神秘面纱说起
javascript
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return `你好,我是${this.name},今年${this.age}岁`;
};
const person = new Person('张三', 25);
上述代码中 new 到底创建了什么?为什么 this 指向了新对象?原型链是怎么建立的?如果构造函数有返回值会怎样?我们将通过本篇文章揭开 new 的神秘面纱,从零实现一个自己的 new 操作符。
理解 new
new 的四个核心步骤:
- 创建一个空对象
- 将对象的原型设置为构造函数的 prototype 属性
- 将构造函数的 this 绑定到新对象,并执行构造函数
- 判断返回值类型:如果构造函数返回一个对象(包括函数),则返回该对象;否则返回新创建的对象
手写实现 new 操作符
基础版本实现
javascript
function myNew(constructor, ...args) {
// 1. 创建一个空对象
const obj = {};
// 2. 将对象的原型设置为构造函数的 prototype 属性
Object.setPrototypeOf(obj, constructor.prototype);
// 3. 将构造函数的 this 绑定到新对象,并执行构造函数
const result = constructor.apply(obj, args);
// 4. 判断返回值类型
// 如果构造函数返回一个对象(包括函数),则返回该对象
// 否则返回新创建的对象
const isObject = result !== null && (typeof result === 'object' || typeof result === 'function');
return isObject ? result : obj;
}
处理边界情况
javascript
function myNewEnhanced(constructor, ...args) {
// 边界情况1:constructor 不是函数
if (typeof constructor !== 'function') {
throw new TypeError(`${constructor} is not a constructor`);
}
// 边界情况2:箭头函数(没有 prototype)
if (!constructor.prototype) {
throw new TypeError(`${constructor.name || constructor} is not a constructor`);
}
// 1. 创建新对象(改进方法):使用 Object.create 更优雅地设置原型
const obj = Object.create(constructor.prototype);
// 2. 调用构造函数
let result;
try {
result = constructor.apply(obj, args);
} catch (error) {
// 如果构造函数抛出异常,直接传播
throw error;
}
// 3. 处理返回值
// 注意:null 也是 object 类型,但需要特殊处理
const resultType = typeof result;
const isObject = result !== null && (resultType === 'object' || resultType === 'function');
return isObject ? result : obj;
}
完整实现与原型链优化
javascript
function myNewComplete(constructor, ...args) {
// 1. 参数验证
if (typeof constructor !== 'function') {
throw new TypeError(`Constructor ${constructor} is not a function`);
}
// 2. 检查是否为可构造的函数:箭头函数和部分内置方法没有 prototype
if (!constructor.prototype && !isNativeConstructor(constructor)) {
throw new TypeError(`${getFunctionName(constructor)} is not a constructor`);
}
// 3. 创建新对象并设置原型链
const proto = constructor.prototype || Object.prototype;
const obj = Object.create(proto);
// 4. 绑定 constructor 属性
obj.constructor = constructor;
// 5. 执行构造函数
const result = Reflect.construct(constructor, args, constructor);
// 6. 处理返回值
// Reflect.construct 已经处理了返回值逻辑
// 但我们还是实现自己的逻辑以保持一致
return processConstructorResult(result, obj, constructor);
}
// 辅助函数:检查是否为原生构造函数
function isNativeConstructor(fn) {
// 一些内置构造函数如 Symbol、BigInt 没有 prototype
const nativeConstructors = [
'Number', 'String', 'Boolean', 'Symbol', 'BigInt',
'Date', 'RegExp', 'Error', 'Array', 'Object', 'Function'
];
return nativeConstructors.some(name =>
fn.name === name || fn === globalThis[name]
);
}
// 辅助函数:获取函数名
function getFunctionName(fn) {
if (fn.name) return fn.name;
const match = fn.toString().match(/^function\s*([^\s(]+)/);
return match ? match[1] : 'anonymous';
}
// 辅助函数:处理构造函数返回值
function processConstructorResult(result, defaultObj, constructor) {
// 如果 result 是 undefined 或 null,返回 defaultObj
if (result == null) {
return defaultObj;
}
// 检查 result 的类型
const type = typeof result;
// 如果是对象或函数,返回 result
if (type === 'object' || type === 'function') {
// 额外检查:如果 result 是构造函数本身的实例,确保原型链正确
if (result instanceof constructor) {
return result;
}
return result;
}
// 原始值,返回 defaultObj
return defaultObj;
}
深入原型链与继承
原型链的建立过程
javascript
// 父构造函数
function Animal(name) {
this.name = name;
}
// 父类方法
Animal.prototype.speak = function () {
return `${this.name} 叫了`;
};
// 子构造函数
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// 建立原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// 子类方法
Dog.prototype.bark = function () {
return `${this.name} 汪汪叫`;
};
// 创建实例
const myDog = new Dog('旺财', '金毛');
console.log(myDog.speak()); // 旺财 叫了
console.log(myDog.bark()); // 旺财 汪汪叫
ES6 类与 new 的关系
ES6 类的本质还是基于原型的语法糖:
ES6 基本写法
javascript
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `你好,我是${this.name}`;
}
}
const person = new Person('张三', 30);
对应 ES5 的写法
javascript
function PersonES5(name, age) {
// 类构造器中的代码
if (!(this instanceof PersonES5)) {
throw new TypeError("Class constructor Person cannot be invoked without 'new'");
}
this.name = name;
this.age = age;
}
// 实例方法(添加到原型)
PersonES5.prototype.greet = function () {
return `你好,我是${this.name}`;
};
const personES5 = new PersonES5('李四', 25);
类的重要特性
- 类必须用 new 调用
- 类方法不可枚举
- 类没有变量提升
ES6 实现继承的完整示例
javascript
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' 叫了');
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
speak() {
console.log(this.name + ' 汪汪叫');
}
}
ES6 继承的本质
ES6 通过 extends 关键字实现继承,就等价于 ES5 的寄生组合继承:
javascript
function AnimalES5(name) {
this.name = name;
}
AnimalES5.prototype.speak = function () {
console.log(this.name + ' 叫了');
};
function DogES5(name) {
AnimalES5.call(this, name);
}
// 设置原型链
DogES5.prototype = Object.create(AnimalES5.prototype);
DogES5.prototype.constructor = DogES5;
DogES5.prototype.speak = function () {
console.log(this.name + ' 汪汪叫');
};
特殊场景与高级应用
单例模式与 new
方法1:使用静态属性
javascript
class SingletonV1 {
static instance = null;
constructor(name) {
if (SingletonV1.instance) {
return SingletonV1.instance;
}
this.name = name;
SingletonV1.instance = this;
}
static getInstance(name) {
if (!this.instance) {
this.instance = new SingletonV1(name);
}
return this.instance;
}
}
方法2:使用闭包
javascript
const SingletonV2 = (function () {
let instance = null;
return class Singleton {
constructor(name) {
if (instance) {
return instance;
}
this.name = name;
instance = this;
}
};
})();
方法3:代理模式
javascript
function createSingletonProxy(Class) {
let instance = null;
return new Proxy(Class, {
construct(target, args) {
if (!instance) {
instance = Reflect.construct(target, args);
}
return instance;
}
});
}
实现 Object.create 的 polyfill
javascript
if (typeof Object.create !== 'function') {
Object.create = function (proto, propertiesObject) {
// 参数验证
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object or null');
}
// 核心实现:使用空函数作为中间构造函数
function F() { }
F.prototype = proto;
// 创建新对象,原型指向proto
const obj = new F();
// 处理第二个参数(属性描述符)
if (propertiesObject !== undefined) {
Object.defineProperties(obj, propertiesObject);
}
// 处理 null 原型
if (proto === null) {
obj.__proto__ = null;
}
// 返回新对象
return obj;
};
}
常见面试问题与解答
问题1:new 操作符做了什么?
- 创建一个新的空对象',
- 将这个空对象的原型设置为构造函数的 prototype 属性',
- 将构造函数的 this 绑定到这个新对象,并执行构造函数',
- 如果构造函数返回一个对象(包括函数),则返回该对象;否则返回新创建的对象
问题2:如果构造函数有返回值会怎样?
- 返回对象(包括函数):忽略 this 绑定的对象,返回该对象
- 返回原始值(number, string, boolean等):忽略返回值,返回 this 绑定的对象
- 没有 return 语句:隐式返回 undefined,返回 this 绑定的对象
问题3:如何判断函数是否被 new 调用?
- ES5:检查 this instanceof Constructor'
- ES6+:使用 new.target(更准确)
- 箭头函数:不能作为构造函数,没有 new.target
结语
通过深入理解 new 操作符的工作原理,我们不仅能在面试中脱颖而出,还能在实际开发中做出更明智的设计决策。对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!