当前位置: 首页 > news >正文

【手写 new 操作符实现 - 深入理解 JavaScript 对象创建机制】

手写 new 操作符实现 - 深入理解 JavaScript 对象创建机制

技术栈: JavaScript, 原型链, 构造函数
难度等级: ⭐⭐⭐⭐
适用场景: 前端面试, JavaScript 底层原理学习


📋 引言

new 操作符是 JavaScript 中最基础也是最重要的概念之一,它是面向对象编程的核心机制。理解 new 的工作原理不仅能帮助我们写出更好的代码,也是高级前端开发者必须掌握的底层知识。

本文将从零开始,逐步实现一个完整的 new 操作符,深入探讨其内部机制和相关概念。

🎯 什么是 new 操作符?

基本用法

function Person(name, age) {this.name = name;this.age = age;
}Person.prototype.sayHello = function () {console.log(`Hello, I'm ${this.name}`);
};// 使用 new 创建实例
const person = new Person('Alice', 25);
person.sayHello(); // Hello, I'm Alice

new 操作符的作用

当我们使用 new 操作符调用函数时,JavaScript 引擎会执行以下操作:

  1. 创建新对象:创建一个全新的空对象
  2. 设置原型:将新对象的 __proto__ 指向构造函数的 prototype
  3. 绑定 this:将构造函数的 this 绑定到新对象
  4. 执行构造函数:执行构造函数内部的代码
  5. 返回对象:如果构造函数没有显式返回对象,则返回新创建的对象

🔍 new 操作符的内部机制分析

执行流程可视化

📦 new Person('Alice', 25)↓
🆕 1. 创建空对象 {}↓
🔗 2. 设置原型链 {}.__proto__ = Person.prototype↓
🎯 3. 绑定 this 上下文↓
⚡ 4. 执行构造函数 Person.call(newObj, 'Alice', 25)↓
📤 5. 返回新对象或构造函数返回值

详细步骤分析

步骤 1:创建新对象
const newObj = {};
// 或者使用 Object.create(null)
步骤 2:设置原型链
// 将新对象的原型指向构造函数的 prototype
newObj.__proto__ = Constructor.prototype;
// 或者使用 Object.setPrototypeOf(newObj, Constructor.prototype)
步骤 3:绑定 this 并执行构造函数
// 将构造函数的 this 绑定到新对象上执行
const result = Constructor.apply(newObj, arguments);
步骤 4:返回适当的值
// 如果构造函数返回了对象,则返回该对象
// 否则返回新创建的对象
return typeof result === 'object' && result !== null ? result : newObj;

🛠️ 手写 new 操作符实现

第一版:基础实现

function myNew(Constructor, ...args) {// 1. 创建一个新对象const newObj = {};// 2. 设置原型链newObj.__proto__ = Constructor.prototype;// 3. 绑定 this 并执行构造函数const result = Constructor.apply(newObj, args);// 4. 返回新对象或构造函数的返回值return typeof result === 'object' && result !== null ? result : newObj;
}

测试基础版本

function Person(name, age) {this.name = name;this.age = age;
}Person.prototype.sayHello = function () {console.log(`Hello, I'm ${this.name}`);
};// 测试我们的实现
const person1 = myNew(Person, 'Alice', 25);
const person2 = new Person('Bob', 30);console.log(person1.name); // Alice
console.log(person1.age); // 25
person1.sayHello(); // Hello, I'm Alice// 验证原型链
console.log(person1 instanceof Person); // true
console.log(person1.__proto__ === Person.prototype); // true

第二版:优化实现

function myNew(Constructor, ...args) {// 参数验证if (typeof Constructor !== 'function') {throw new TypeError('Constructor must be a function');}// 1. 创建新对象,并设置原型链const newObj = Object.create(Constructor.prototype);// 2. 绑定 this 并执行构造函数const result = Constructor.apply(newObj, args);// 3. 判断构造函数的返回值类型const isObject = typeof result === 'object' && result !== null;const isFunction = typeof result === 'function';// 4. 返回新对象或构造函数的返回值return isObject || isFunction ? result : newObj;
}

第三版:完整实现

function myNew(Constructor, ...args) {// 1. 参数验证if (typeof Constructor !== 'function') {throw new TypeError(`${Constructor} is not a constructor`);}// 2. 创建新对象并设置原型链const newObj = Object.create(Constructor.prototype);// 3. 绑定 this 并执行构造函数const result = Constructor.apply(newObj, args);// 4. 判断返回值类型// 如果构造函数返回了对象或函数,则返回该值// 否则返回新创建的对象return result !== null && (typeof result === 'object' || typeof result === 'function')? result: newObj;
}

🧪 深入测试和验证

测试用例 1:基本功能

function Car(brand, model) {this.brand = brand;this.model = model;
}Car.prototype.start = function () {console.log(`${this.brand} ${this.model} is starting...`);
};const car1 = myNew(Car, 'Toyota', 'Camry');
const car2 = new Car('Honda', 'Civic');// 测试属性
console.log(car1.brand); // Toyota
console.log(car1.model); // Camry// 测试方法
car1.start(); // Toyota Camry is starting...// 测试原型链
console.log(car1 instanceof Car); // true
console.log(car1.__proto__ === Car.prototype); // true
console.log(car1.constructor === Car); // true

测试用例 2:构造函数返回对象

function Person(name) {this.name = name;// 显式返回对象return {name: name,type: 'custom'};
}const person1 = myNew(Person, 'Alice');
const person2 = new Person('Bob');console.log(person1); // { name: 'Alice', type: 'custom' }
console.log(person2); // { name: 'Bob', type: 'custom' }// 注意:返回的对象不是 Person 的实例
console.log(person1 instanceof Person); // false
console.log(person2 instanceof Person); // false

测试用例 3:构造函数返回原始值

function Number2(value) {this.value = value;// 返回原始值(会被忽略)return 42;
}const num1 = myNew(Number2, 100);
const num2 = new Number2(200);console.log(num1.value); // 100
console.log(num2.value); // 200// 原始值被忽略,返回新创建的对象
console.log(num1 instanceof Number2); // true
console.log(num2 instanceof Number2); // true

测试用例 4:错误处理

// 测试非函数参数
try {myNew('not a function');
} catch (error) {console.log(error.message); // not a function is not a constructor
}try {myNew(null);
} catch (error) {console.log(error.message); // null is not a constructor
}

🔬 深入理解相关概念

原型链深度解析

什么是原型链?

**原型链(Prototype Chain)**是 JavaScript 实现继承的核心机制。它是一个由对象组成的链式结构,每个对象都有一个指向其原型对象的内部链接。

// 原型链的基本结构示意
实例对象↓ __proto__
构造函数的原型对象 (Constructor.prototype)↓ __proto__
Object.prototype↓ __proto__
null (原型链的终点)
核心概念解释

1. __proto__ 属性

每个 JavaScript 对象都有一个 __proto__ 属性,它指向该对象的构造函数的原型对象(Constructor.prototype)。

// 对象字面量的构造函数是 Object
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true// 数组的构造函数是 Array
const arr = [];
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true// 自定义构造函数创建的对象
function Person(name) {this.name = name;
}const person = new Person('Alice');
console.log(person.__proto__ === Person.prototype); // true
console.log(person.constructor === Person); // true

2. prototype 属性

每个函数都有一个 prototype 属性,它是一个对象,包含应该被该函数作为构造函数创建的实例共享的属性和方法。

function Person(name) {this.name = name;
}// Person.prototype 是一个对象,用于存放共享的属性和方法
console.log(typeof Person.prototype); // "object"// 添加共享方法
Person.prototype.sayHello = function () {console.log(`Hello, I'm ${this.name}`);
};const person = new Person('Alice');
// 实例的 __proto__ 指向构造函数的 prototype
console.log(person.__proto__ === Person.prototype); // true

3. constructor 属性

每个原型对象都有一个 constructor 属性,指向与之关联的构造函数。

function Person() {}console.log(Person.prototype.constructor === Person); // trueconst person = new Person();
console.log(person.constructor === Person); // true
console.log(person.__proto__.constructor === Person); // true
为什么需要原型链?

1. 内存优化

通过原型链,多个实例可以共享相同的方法,避免重复创建:

// ❌ 不使用原型 - 每个实例都有自己的方法副本
function Person(name) {this.name = name;this.sayHello = function () {// 每次创建新函数console.log(`Hello, I'm ${this.name}`);};
}const person1 = new Person('Alice');
const person2 = new Person('Bob');
console.log(person1.sayHello === person2.sayHello); // false - 不同的函数实例// ✅ 使用原型 - 所有实例共享同一个方法
function PersonOptimized(name) {this.name = name;
}PersonOptimized.prototype.sayHello = function () {console.log(`Hello, I'm ${this.name}`);
};const person3 = new PersonOptimized('Charlie');
const person4 = new PersonOptimized('Diana');
console.log(person3.sayHello === person4.sayHello); // true - 共享同一个函数

2. 实现继承

原型链是 JavaScript 实现继承的基础:

// 父类
function Animal(name) {this.name = name;
}Animal.prototype.eat = function () {console.log(`${this.name} is eating`);
};// 子类
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 () {console.log(`${this.name} is barking`);
};const dog = new Dog('Buddy', 'Golden Retriever');// 原型链查找过程
dog.bark(); // 1. 在 dog 实例上查找 bark 方法 -> 没找到
// 2. 在 Dog.prototype 上查找 -> 找到了dog.eat(); // 1. 在 dog 实例上查找 eat 方法 -> 没找到
// 2. 在 Dog.prototype 上查找 -> 没找到
// 3. 在 Animal.prototype 上查找 -> 找到了
原型链查找过程详解
function Person(name) {this.name = name;
}Person.prototype.getName = function () {return this.name;
};Person.prototype.species = 'Homo sapiens';const person = new Person('Alice');// 原型链查找演示
console.log('=== 原型链查找过程 ===');// 1. 查找实例自有属性
console.log('person.name:', person.name); // "Alice" - 在实例上找到// 2. 查找原型方法
console.log('person.getName():', person.getName()); // "Alice" - 在原型上找到// 3. 查找原型属性
console.log('person.species:', person.species); // "Homo sapiens" - 在原型上找到// 4. 查找更高层原型的方法
console.log('person.toString():', person.toString()); // "[object Object]" - 在 Object.prototype 上找到// 5. 查找不存在的属性
console.log('person.nonExistent:', person.nonExistent); // undefined - 整个原型链都没找到
原型链的完整结构图
function Person(name) {this.name = name;
}Person.prototype.sayHello = function () {console.log('Hello');
};const person = new Person('Alice');// 完整的原型链结构
console.log('=== 完整原型链结构 ===');console.log('1. person 实例:');
console.log('   person.name =', person.name);
console.log('   person.__proto__ ===', person.__proto__ === Person.prototype);console.log('2. Person.prototype:');
console.log('   Person.prototype.constructor ===', Person.prototype.constructor === Person);
console.log('   Person.prototype.__proto__ ===', Person.prototype.__proto__ === Object.prototype);console.log('3. Object.prototype:');
console.log('   Object.prototype.__proto__ ===', Object.prototype.__proto__ === null);// 可视化原型链
console.log('\n=== 原型链可视化 ===');
console.log('person');
console.log('  ↓ __proto__');
console.log('Person.prototype');
console.log('  ↓ __proto__');
console.log('Object.prototype');
console.log('  ↓ __proto__');
console.log('null');
原型链在 new 操作符中的作用
// 手写 new 中的原型设置步骤详解
function myNewDetailed(Constructor, ...args) {console.log('=== new 操作符中的原型链设置 ===');// 步骤1: 创建空对象const newObj = {};console.log('1. 创建空对象:', newObj);// 步骤2: 设置原型链 - 关键步骤!newObj.__proto__ = Constructor.prototype;console.log('2. 设置原型链:');console.log('   newObj.__proto__ === Constructor.prototype:',newObj.__proto__ === Constructor.prototype);// 此时 newObj 已经可以访问 Constructor.prototype 上的方法了console.log('3. 原型链设置完成,newObj 已具备继承能力');// 步骤3: 绑定 this 并执行构造函数const result = Constructor.apply(newObj, args);console.log('4. 执行构造函数,绑定实例属性');// 步骤4: 返回对象return typeof result === 'object' && result !== null ? result : newObj;
}// 测试详细过程
function Person(name) {this.name = name;console.log('   构造函数执行,设置 this.name =', name);
}Person.prototype.sayHello = function () {console.log(`Hello, I'm ${this.name}`);
};const person = myNewDetailed(Person, 'Alice');
console.log('5. 最终结果测试:');
person.sayHello(); // 可以调用原型方法
深入理解:原型对象、[[Prototype]] 和 prototype 的本质

1. 什么是原型对象?正确理解概念

重要澄清[[Prototype]] 才是真正的"原型对象"!prototype 只是构造函数的一个属性。

console.log('=== 正确理解"原型对象"概念 ===');// [[Prototype]] 指向的才是真正的"原型对象"
function Person(name) {this.name = name;
}const person = new Person('Alice');// person 的原型对象是什么?
console.log('person 的原型对象:', person.__proto__); // Person.prototype 对象
console.log('这个对象的类型:', typeof person.__proto__); // object// Person.prototype 是什么?
console.log('Person.prototype 是:', Person.prototype); // 一个对象
console.log('它是 person 的原型对象吗:', person.__proto__ === Person.prototype); // true// 关键理解:
// - person.__proto__ 指向的对象才是 person 的"原型对象"
// - Person.prototype 只是构造函数的一个属性,恰好被设置为 person 的原型对象
// - 原型对象可以是任意对象,不一定来自构造函数的 prototype

任何对象都可以作为另一个对象的原型对象:

// 原型对象就是普通对象
const animalPrototype = {eat() {console.log(`${this.name} is eating`);},species: 'Animal'
};// 任何对象都可以作为原型对象
const dog = Object.create(animalPrototype);
dog.name = 'Buddy';
dog.eat(); // Buddy is eatingconsole.log('animalPrototype 的类型:', typeof animalPrototype); // object// 为什么 constructor 是 Object?
console.log('animalPrototype.constructor:', animalPrototype.constructor); // Object// 原因分析:
console.log('=== constructor 属性来源分析 ===');
console.log('animalPrototype 自身有 constructor 吗:',animalPrototype.hasOwnProperty('constructor')
); // false
console.log('animalPrototype.__proto__:', animalPrototype.__proto__ === Object.prototype); // true
console.log('Object.prototype.constructor:', Object.prototype.constructor === Object); // true// 所以 animalPrototype.constructor 实际上是通过原型链从 Object.prototype 继承来的
// 继承链:animalPrototype -> Object.prototype (constructor: Object) -> null// 对比:构造函数的 prototype 对象
function Animal() {}
console.log('Animal.prototype.constructor:', Animal.prototype.constructor === Animal); // true
console.log('Animal.prototype 有自己的 constructor:',Animal.prototype.hasOwnProperty('constructor')
); // true

2. [[Prototype]] vs prototype:核心区别澄清

重要澄清[[Prototype]] 绝不是 constructor.prototype 的快捷缩写!它们是完全不同的概念。

function Person(name) {this.name = name;
}console.log('=== [[Prototype]] vs prototype 的本质区别 ===');// prototype 是函数对象的一个普通属性(只有函数才有)
console.log('Person.prototype 是什么:', typeof Person.prototype); // object
console.log('Person.prototype 是函数的普通属性');// [[Prototype]] 是每个对象都有的内部属性
const person = new Person('Alice');console.log('=== 关键概念理解 ===');// 1. [[Prototype]] 不是缩写,是独立的内部属性
console.log('person 的 [[Prototype]]:', person.__proto__);
console.log('Person 函数的 [[Prototype]]:', Person.__proto__); // Function.prototype// 2. prototype 是函数独有的属性
console.log('person 有 prototype 属性吗:', 'prototype' in person); // false
console.log('Person 有 prototype 属性吗:', 'prototype' in Person); // true// 3. 它们指向不同的对象(大多数情况下)
console.log('person.__proto__ === Person.prototype:', person.__proto__ === Person.prototype); // true (特殊情况)
console.log('Person.__proto__ === Person.prototype:', Person.__proto__ === Person.prototype); // false// 4. [[Prototype]] 的多种可能值
const obj1 = {}; // [[Prototype]] -> Object.prototype
const obj2 = Object.create(null); // [[Prototype]] -> null
const obj3 = Object.create(person); // [[Prototype]] -> person 对象console.log('obj1.__proto__:', obj1.__proto__ === Object.prototype); // true
console.log('obj2.__proto__:', obj2.__proto__); // undefined (null)
console.log('obj3.__proto__:', obj3.__proto__ === person); // true// 5. 函数的两个不同原型概念
console.log('=== 函数的双重原型身份 ===');
console.log('Person 作为函数的 [[Prototype]]:', Person.__proto__ === Function.prototype); // true
console.log('Person 作为构造函数的 prototype:', Person.prototype); // 用于创建实例的原型模板// Person.__proto__ 是 Person 函数对象自己的原型
// Person.prototype 是 Person 构造函数为实例准备的原型模板

3. prototype vs [[Prototype]] 的包含关系澄清

让我用更清晰的方式说明这个包含关系:

console.log('=== prototype vs [[Prototype]] 的范围关系 ===');// 1. prototype:只有函数(构造函数)才有
function Person() {}
function Animal() {}
const obj = {};
const arr = [];console.log('函数有 prototype 属性:');
console.log('Person.prototype:', typeof Person.prototype); // object
console.log('Animal.prototype:', typeof Animal.prototype); // objectconsole.log('普通对象没有 prototype 属性:');
console.log('obj.prototype:', obj.prototype); // undefined
console.log('arr.prototype:', arr.prototype); // undefined// 2. [[Prototype]]:所有对象都有(包括函数,因为函数也是对象)
console.log('所有对象都有 [[Prototype]]:');
console.log('Person.__proto__:', Person.__proto__ === Function.prototype); // true
console.log('obj.__proto__:', obj.__proto__ === Object.prototype); // true
console.log('arr.__proto__:', arr.__proto__ === Array.prototype); // true// 3. 关键理解:P 包含 p 的关系
console.log('=== P(所有对象的[[Prototype]]) 包含 p(构造函数的prototype) ===');// [[Prototype]] 的所有可能指向:
const example1 = new Person(); // [[Prototype]] -> Person.prototype
const example2 = {}; // [[Prototype]] -> Object.prototype
const example3 = []; // [[Prototype]] -> Array.prototype
const example4 = Object.create(null); // [[Prototype]] -> null
const example5 = Object.create(example1); // [[Prototype]] -> example1 对象
const example6 = Object.create({ custom: true }); // [[Prototype]] -> 自定义对象console.log('example1.__proto__ === Person.prototype:', example1.__proto__ === Person.prototype); // true
console.log('example2.__proto__ === Object.prototype:', example2.__proto__ === Object.prototype); // true
console.log('example3.__proto__ === Array.prototype:', example3.__proto__ === Array.prototype); // true
console.log('example4.__proto__:', example4.__proto__); // undefined (null)
console.log('example5.__proto__ === example1:', example5.__proto__ === example1); // true
console.log('example6.__proto__.custom:', example6.__proto__.custom); // true// 总结:
// - prototype 只存在于构造函数上(子集)
// - [[Prototype]] 存在于所有对象上(全集)
// - [[Prototype]] 不一定指向构造函数的 prototype
// - 关系:所有的 Constructor.prototype 都可能成为某个对象的 [[Prototype]]
//        但 [[Prototype]] 还可以指向其他任意对象

4. 原型对象的本质:[[Prototype]] 指向的对象

console.log('=== 原型对象的本质理解 ===');// 1. 什么是原型对象?
function Animal() {}
const animal = new Animal();console.log('animal 的原型对象是:', animal.__proto__); // Animal.prototype 对象
console.log('这个原型对象就是:', Animal.prototype); // 同一个对象// 2. 原型对象可以是任意对象
const customPrototype = {species: 'Unknown',describe() {return `This is a ${this.species}`;}
};const creature = Object.create(customPrototype);
console.log('creature 的原型对象是:', creature.__proto__); // customPrototype 对象
console.log('customPrototype 是原型对象吗:', creature.__proto__ === customPrototype); // true// 3. prototype 属性 vs 原型对象
console.log('=== prototype 属性 vs 原型对象 ===');// Animal.prototype 是 Animal 函数的一个属性
console.log('Animal.prototype 是属性:', 'prototype' in Animal); // true// 这个属性的值恰好是 animal 实例的原型对象
console.log('Animal.prototype 的值恰好是 animal 的原型对象:',animal.__proto__ === Animal.prototype
); // true// 但是我们也可以让其他对象作为原型对象
const anotherAnimal = Object.create(customPrototype);
console.log('anotherAnimal 的原型对象:', anotherAnimal.__proto__ === customPrototype); // true
console.log('这个原型对象不是任何构造函数的 prototype');// 4. 术语正确使用
console.log('=== 正确的术语 ===');
console.log('✅ 正确: animal 的原型对象是', animal.__proto__);
console.log('✅ 正确: Animal.prototype 是构造函数的属性');
console.log('✅ 正确: Animal.prototype 的值被设置为实例的原型对象');
console.log('❌ 错误: 说 "Animal.prototype 是原型对象" - 不准确');
console.log('❌ 错误: 说 "prototype 是原型对象" - 概念混淆');

5. 深入理解这种包含关系

console.log('=== 深入理解包含关系 ===');// 场景1:通过构造函数创建 - [[Prototype]] 指向构造函数的 prototype
function Car() {}
const car = new Car();
console.log('通过构造函数创建:', car.__proto__ === Car.prototype); // true// 场景2:通过 Object.create 创建 - [[Prototype]] 可以指向任意对象
const animal = { species: 'Animal' };
const dog = Object.create(animal);
console.log('通过 Object.create:', dog.__proto__ === animal); // true
console.log('不指向任何构造函数的 prototype:', dog.__proto__.constructor); // Object(继承来的)// 场景3:函数自身的 [[Prototype]]
function MyFunc() {}
console.log('函数的 [[Prototype]]:', MyFunc.__proto__ === Function.prototype); // true
console.log('函数的 prototype 属性:', typeof MyFunc.prototype); // object
console.log('两者完全不同:', MyFunc.__proto__ !== MyFunc.prototype); // true// 场景4:内置对象的情况
console.log('=== 内置对象的 [[Prototype]] 指向 ===');
const str = 'hello';
const num = 42;
const bool = true;// 基本类型的包装对象
console.log('字符串的 [[Prototype]]:', str.__proto__ === String.prototype); // true
console.log('数字的 [[Prototype]]:', num.__proto__ === Number.prototype); // true
console.log('布尔值的 [[Prototype]]:', bool.__proto__ === Boolean.prototype); // true// 验证:这些 prototype 也是对象,也有自己的 [[Prototype]]
console.log('String.prototype.__proto__:', String.prototype.__proto__ === Object.prototype); // true
console.log('Number.prototype.__proto__:', Number.prototype.__proto__ === Object.prototype); // true// 最终的包含关系图示:
console.log(`
=== 包含关系总结 ===大集合:所有可能的原型对象([[Prototype]] 指向的对象):
├── 指向构造函数的 prototype(子集 p)
│   ├── Person.prototype
│   ├── Array.prototype  
│   ├── Object.prototype
│   └── ...其他构造函数的 prototype
│
├── 指向普通对象
│   ├── 自定义对象 {custom: true}
│   ├── 其他实例对象
│   └── ...任意对象
│
└── 指向 null(原型链终点)小集合:构造函数的 prototype 属性值(可能成为原型对象的对象):
├── Function.prototype
├── Object.prototype
├── Array.prototype
└── ...各种构造函数的 prototype关系:p ⊆ P(Constructor.prototype 是原型对象的一个子集)=== 正确的术语理解 ===
- "原型对象" = [[Prototype]] 指向的对象(真正的原型)
- "prototype 属性" = 构造函数的 prototype 属性(只是一个属性)
- Constructor.prototype 只有在被设置为某对象的 [[Prototype]] 时,才成为"原型对象"
`);

3. new 操作中的原型绑定机制

function detailedNew(Constructor, ...args) {console.log('=== new 中的原型绑定详解 ===');// 步骤1: 创建新对象const newObj = {};console.log('1. 创建空对象');// 步骤2: 设置 [[Prototype]] - 这里是引用绑定!console.log('2. 绑定原型引用:');console.log('   Constructor.prototype 的内存地址:', Constructor.prototype);newObj.__proto__ = Constructor.prototype; // 这是引用赋值,不是值拷贝console.log('   newObj.__proto__ 的内存地址:', newObj.__proto__);console.log('   两者是同一个对象:', newObj.__proto__ === Constructor.prototype);// 步骤3: 执行构造函数const result = Constructor.apply(newObj, args);return typeof result === 'object' && result !== null ? result : newObj;
}function Person(name) {this.name = name;
}Person.prototype.sayHello = function () {console.log(`Hello, I'm ${this.name}`);
};const person1 = detailedNew(Person, 'Alice');// 证明是引用绑定
console.log('=== 引用绑定证明 ===');
const originalPrototype = Person.prototype;// 在原型上添加方法
Person.prototype.newMethod = function () {console.log('这是新添加的方法');
};// 已创建的实例也能访问新方法(因为引用同一个对象)
person1.newMethod(); // 这是新添加的方法// 但如果完全替换 prototype...
Person.prototype = {constructor: Person,differentMethod() {console.log('这是替换后的方法');}
};// 已创建的实例不受影响(因为它们引用的是旧的原型对象)
console.log('person1.__proto__ === originalPrototype:', person1.__proto__ === originalPrototype); // true
console.log('person1.__proto__ === Person.prototype:', person1.__proto__ === Person.prototype); // false// person1.differentMethod(); // 报错:不存在
person1.newMethod(); // 仍然可以调用(在旧原型对象上)// 新创建的实例会引用新的原型对象
const person2 = new Person('Bob');
// person2.newMethod(); // 报错:不存在
person2.differentMethod(); // 这是替换后的方法

4. 对象的 [[Prototype]] 并非总是指向构造函数的 prototype

console.log('=== [[Prototype]] 的多种可能指向 ===');// 1. 通过构造函数创建 - 指向构造函数的 prototype
function Person() {}
const person = new Person();
console.log('通过构造函数:', person.__proto__ === Person.prototype); // true// 2. 通过 Object.create 创建 - 指向指定的对象
const customPrototype = { custom: true };
const obj1 = Object.create(customPrototype);
console.log('通过 Object.create:', obj1.__proto__ === customPrototype); // true
console.log('不指向 Object.prototype:', obj1.__proto__ === Object.prototype); // false// 3. 通过 Object.create(null) 创建 - 没有原型
const obj2 = Object.create(null);
console.log('无原型对象:', obj2.__proto__); // undefined// 4. 通过 Object.setPrototypeOf 修改
const obj3 = {};
const newPrototype = { modified: true };
Object.setPrototypeOf(obj3, newPrototype);
console.log('修改后的原型:', obj3.__proto__ === newPrototype); // true// 5. 字面量对象 - 指向 Object.prototype
const literal = {};
console.log('字面量对象:', literal.__proto__ === Object.prototype); // true// 6. 数组 - 指向 Array.prototype
const arr = [];
console.log('数组对象:', arr.__proto__ === Array.prototype); // true

5. 原型对象的多层级结构

// 演示原型对象也有自己的原型
function Animal() {}
Animal.prototype.eat = function () {console.log('eating');
};function Dog() {}
Dog.prototype = Object.create(Animal.prototype); // Dog 的原型指向 Animal 的原型
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () {console.log('barking');
};const dog = new Dog();console.log('=== 多层级原型链 ===');
console.log('dog.__proto__ === Dog.prototype:', dog.__proto__ === Dog.prototype); // true
console.log('Dog.prototype.__proto__ === Animal.prototype:',Dog.prototype.__proto__ === Animal.prototype
); // true
console.log('Animal.prototype.__proto__ === Object.prototype:',Animal.prototype.__proto__ === Object.prototype
); // true
console.log('Object.prototype.__proto__ === null:', Object.prototype.__proto__ === null); // true// 原型链查找路径:dog -> Dog.prototype -> Animal.prototype -> Object.prototype -> null
原型链修改的危险性和安全考量

1. 修改构造函数的 prototype 对象

function Person(name) {this.name = name;
}// ✅ 可以修改 prototype 上的方法和属性
Person.prototype.sayHello = function () {console.log(`Hello, I'm ${this.name}`);
};Person.prototype.species = 'Homo sapiens';// ✅ 可以修改已存在的方法
Person.prototype.sayHello = function () {console.log(`Hi there! I'm ${this.name}`);
};const person1 = new Person('Alice');
person1.sayHello(); // Hi there! I'm Alice// ✅ 即使在实例创建后修改 prototype,也会影响所有实例
Person.prototype.sayGoodbye = function () {console.log(`Goodbye from ${this.name}`);
};person1.sayGoodbye(); // Goodbye from Aliceconst person2 = new Person('Bob');
person2.sayGoodbye(); // Goodbye from Bob

2. 直接修改实例的 proto (不推荐)

function Person(name) {this.name = name;
}function Animal(name) {this.name = name;
}Animal.prototype.eat = function () {console.log(`${this.name} is eating`);
};const person = new Person('Alice');// ❌ 不推荐:直接修改 __proto__ 性能较差
person.__proto__ = Animal.prototype;
person.eat(); // Alice is eating// ✅ 推荐:使用 Object.setPrototypeOf()
const person2 = new Person('Bob');
Object.setPrototypeOf(person2, Animal.prototype);
person2.eat(); // Bob is eating

3. 修改内置对象的原型 (需谨慎)

// ⚠️ 慎重:修改内置对象原型会影响全局
Array.prototype.last = function () {return this[this.length - 1];
};const arr = [1, 2, 3];
console.log(arr.last()); // 3// 所有数组都会受影响
const arr2 = ['a', 'b', 'c'];
console.log(arr2.last()); // 'c'// ✅ 更安全的方式:创建子类
class MyArray extends Array {last() {return this[this.length - 1];}
}const myArr = new MyArray(1, 2, 3);
console.log(myArr.last()); // 3

4. 原型链修改的时机影响

function Person(name) {this.name = name;
}// 先创建实例
const person1 = new Person('Alice');// 后添加方法 - 实例仍可访问
Person.prototype.sayHello = function () {console.log(`Hello, I'm ${this.name}`);
};person1.sayHello(); // Hello, I'm Alice// 完全替换 prototype - 已创建的实例不受影响
const oldPrototype = Person.prototype;
Person.prototype = {constructor: Person,sayHi: function () {console.log(`Hi, I'm ${this.name}`);}
};// 旧实例仍指向旧原型
console.log(person1.__proto__ === oldPrototype); // true
// person1.sayHi(); // 报错:person1.sayHi is not a function// 新实例指向新原型
const person2 = new Person('Bob');
person2.sayHi(); // Hi, I'm Bob
console.log(person2.__proto__ === Person.prototype); // true
原型链修改的真正危险性

1. 全局污染风险

console.log('=== 原型链修改的危险性演示 ===');// ❌ 极度危险:修改 Object.prototype
Object.prototype.isAdmin = false; // 这会影响所有对象!const user = { name: 'Alice' };
const config = {};
const emptyObj = {};console.log('所有对象都被污染了:');
console.log('user.isAdmin:', user.isAdmin); // false
console.log('config.isAdmin:', config.isAdmin); // false
console.log('emptyObj.isAdmin:', emptyObj.isAdmin); // false// 这在 for...in 循环中会出现
for (const key in user) {console.log('遍历到的属性:', key); // name, isAdmin (污染属性也会被遍历)
}// 清理污染(实际项目中很难做到)
delete Object.prototype.isAdmin;

2. 原型污染攻击

// 模拟原型污染攻击
function vulnerableFunction(userInput) {const config = {};// 危险的动态属性设置const keys = userInput.split('.');let current = config;for (let i = 0; i < keys.length - 1; i++) {if (!current[keys[i]]) {current[keys[i]] = {};}current = current[keys[i]];}current[keys[keys.length - 1]] = true;return config;
}// 攻击载荷
const maliciousInput = '__proto__.polluted';
vulnerableFunction(maliciousInput);// 检查是否成功污染
console.log('原型污染检测:', {}.polluted); // 可能为 true// 修复:安全的属性设置
function safeFunction(userInput) {if (userInput.includes('__proto__') ||userInput.includes('prototype') ||userInput.includes('constructor')) {throw new Error('Potential prototype pollution detected');}const config = Object.create(null); // 使用无原型对象// ... 安全的属性设置逻辑return config;
}

3. 内存泄漏风险

// 原型修改可能导致的内存泄漏
function demonstrateMemoryLeak() {const largeData = new Array(1000000).fill('large data');// 危险:将大对象添加到原型上SomeConstructor.prototype.cachedData = largeData;// 即使不再需要这些数据,由于在原型上,也无法被垃圾回收// 所有实例都会"继承"这个大对象的引用
}function SomeConstructor() {}// 更安全的方式:使用 WeakMap 或实例属性
const privateData = new WeakMap();function SafeConstructor() {privateData.set(this, {/* 私有数据 */});
}SafeConstructor.prototype.getData = function () {return privateData.get(this);
};

4. 意外的行为修改

// 修改原型可能导致意外行为
Array.prototype.contains = function (item) {return this.indexOf(item) !== -1;
};const arr = [1, 2, 3];
console.log(arr.contains(2)); // true,看起来没问题// 但是会影响所有数组遍历
for (const key in arr) {console.log(key); // 0, 1, 2, contains - 意外出现了 contains!
}// 更安全的方式:使用实用函数
function arrayContains(arr, item) {return arr.indexOf(item) !== -1;
}// 或者检查是否已存在该方法
if (!Array.prototype.includes) {// includes 是 ES2016 的标准方法Array.prototype.includes = function (item) {return this.indexOf(item) !== -1;};
}

5. 安全的原型扩展模式

// ✅ 安全模式1:命名空间
if (!window.MyLibrary) {window.MyLibrary = {};
}MyLibrary.ArrayUtils = {contains(arr, item) {return arr.indexOf(item) !== -1;}
};// ✅ 安全模式2:Symbol 避免冲突
const CONTAINS_SYMBOL = Symbol('contains');Array.prototype[CONTAINS_SYMBOL] = function (item) {return this.indexOf(item) !== -1;
};const arr2 = [1, 2, 3];
console.log(arr2[CONTAINS_SYMBOL](2)); // true// Symbol 属性不会在 for...in 中出现
for (const key in arr2) {console.log(key); // 只有 0, 1, 2
}// ✅ 安全模式3:条件扩展
function safeExtend(target, methodName, method) {if (!(methodName in target.prototype)) {Object.defineProperty(target.prototype, methodName, {value: method,writable: true,configurable: true,enumerable: false // 不在 for...in 中出现});}
}safeExtend(Array, 'safeContains', function (item) {return this.indexOf(item) !== -1;
});

6. 原型冻结和保护

// 保护关键原型不被修改
Object.freeze(Object.prototype);
Object.freeze(Array.prototype);
Object.freeze(Function.prototype);// 尝试修改会失败(严格模式下抛错)
try {Object.prototype.maliciousMethod = function () {};console.log('修改成功'); // 不会执行
} catch (error) {console.log('修改被阻止:', error.message);
}// 检测原型修改
const originalMethods = {objectToString: Object.prototype.toString,arrayPush: Array.prototype.push
};function detectPrototypeModification() {return (Object.prototype.toString === originalMethods.objectToString &&Array.prototype.push === originalMethods.arrayPush);
}console.log('原型完整性检查:', detectPrototypeModification());
原型相关的内置方法
// Object.getPrototypeOf() - 获取对象的原型
function Person(name) {this.name = name;
}const person = new Person('Alice');
console.log(Object.getPrototypeOf(person) === Person.prototype); // true// Object.setPrototypeOf() - 设置对象的原型
const obj = {};
Object.setPrototypeOf(obj, Person.prototype);
console.log(obj instanceof Person); // true// Object.isPrototypeOf() - 检查对象是否在另一个对象的原型链上
console.log(Person.prototype.isPrototypeOf(person)); // true
console.log(Object.prototype.isPrototypeOf(person)); // true// Object.hasOwnProperty() - 检查对象自身是否具有指定属性
person.age = 25;
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('age')); // true
console.log(person.hasOwnProperty('toString')); // false

原型链机制实践

function Animal(name) {this.name = name;
}Animal.prototype.eat = function () {console.log(`${this.name} is eating`);
};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 () {console.log(`${this.name} is barking`);
};const dog = myNew(Dog, 'Buddy', 'Golden Retriever');dog.eat(); // Buddy is eating (继承自 Animal)
dog.bark(); // Buddy is barking (Dog 自己的方法)// 原型链查找
console.log(dog.__proto__ === Dog.prototype); // true
console.log(dog.__proto__.__proto__ === Animal.prototype); // true

this 绑定机制

function Person(name) {this.name = name;// 普通函数中的 thisthis.getName = function () {return this.name;};// 箭头函数中的 thisthis.getNameArrow = () => {return this.name;};
}const person = myNew(Person, 'Alice');console.log(person.getName()); // Alice
console.log(person.getNameArrow()); // Alice// 改变 this 指向
const obj = { name: 'Bob' };
console.log(person.getName.call(obj)); // Bob
console.log(person.getNameArrow.call(obj)); // Alice (箭头函数不受影响)

🚀 高级特性和边界情况

处理 Symbol.species

function MyArray(...args) {this.length = args.length;for (let i = 0; i < args.length; i++) {this[i] = args[i];}
}MyArray.prototype = Object.create(Array.prototype);
MyArray.prototype.constructor = MyArray;// 支持 Symbol.species
MyArray[Symbol.species] = MyArray;const arr = myNew(MyArray, 1, 2, 3);
console.log(arr.length); // 3
console.log(arr[0]); // 1

处理异步构造函数

function AsyncPerson(name) {this.name = name;// 异步初始化this.init = async function () {await new Promise((resolve) => setTimeout(resolve, 100));this.initialized = true;};
}const person = myNew(AsyncPerson, 'Alice');// 需要等待异步初始化
person.init().then(() => {console.log(person.initialized); // true
});

处理 Proxy 构造函数

function createProxyConstructor(target) {return new Proxy(target, {construct(target, args, newTarget) {console.log('Proxy construct called');const instance = Reflect.construct(target, args, newTarget);return instance;}});
}function Person(name) {this.name = name;
}const ProxyPerson = createProxyConstructor(Person);
const person = myNew(ProxyPerson, 'Alice');
// 输出: Proxy construct called
console.log(person.name); // Alice

🎯 实际应用场景

工厂模式实现

function createInstance(Constructor, ...args) {return myNew(Constructor, ...args);
}// 动态创建实例
const constructors = {User: function (name) {this.name = name;},Product: function (name, price) {this.name = name;this.price = price;}
};const user = createInstance(constructors.User, 'Alice');
const product = createInstance(constructors.Product, 'iPhone', 999);

单例模式实现

function Singleton() {if (Singleton.instance) {return Singleton.instance;}this.timestamp = Date.now();Singleton.instance = this;
}const s1 = myNew(Singleton);
const s2 = myNew(Singleton);console.log(s1 === s2); // true
console.log(s1.timestamp === s2.timestamp); // true

依赖注入容器

class DIContainer {constructor() {this.dependencies = new Map();this.singletons = new Map();}register(name, Constructor, options = {}) {this.dependencies.set(name, {Constructor,singleton: options.singleton || false});}resolve(name) {const dependency = this.dependencies.get(name);if (!dependency) {throw new Error(`Dependency ${name} not found`);}if (dependency.singleton) {if (!this.singletons.has(name)) {this.singletons.set(name, myNew(dependency.Constructor));}return this.singletons.get(name);}return myNew(dependency.Constructor);}
}// 使用示例
const container = new DIContainer();container.register('Logger',function Logger() {this.log = (msg) => console.log(msg);},{ singleton: true }
);const logger1 = container.resolve('Logger');
const logger2 = container.resolve('Logger');console.log(logger1 === logger2); // true

🔧 性能优化和最佳实践

性能对比

// 性能测试
function TestConstructor(value) {this.value = value;
}// 测试原生 new
console.time('native new');
for (let i = 0; i < 100000; i++) {new TestConstructor(i);
}
console.timeEnd('native new');// 测试我们的实现
console.time('myNew');
for (let i = 0; i < 100000; i++) {myNew(TestConstructor, i);
}
console.timeEnd('myNew');

内存优化

function optimizedNew(Constructor, ...args) {// 使用 Object.create 更高效const newObj = Object.create(Constructor.prototype);// 避免不必要的类型检查const result = Constructor.apply(newObj, args);// 优化返回值判断return result && typeof result === 'object' ? result : newObj;
}

调试友好版本

function debugNew(Constructor, ...args) {console.log(`Creating instance of ${Constructor.name}`);console.log('Arguments:', args);if (typeof Constructor !== 'function') {const error = new TypeError(`${Constructor} is not a constructor`);console.error(error);throw error;}const newObj = Object.create(Constructor.prototype);console.log('Created object with prototype:', Constructor.prototype);const result = Constructor.apply(newObj, args);console.log('Constructor result:', result);const finalResult = result !== null && typeof result === 'object' ? result : newObj;console.log('Final result:', finalResult);return finalResult;
}

🎓 面试常见问题

Q1: new 和 Object.create 的区别?

// new 操作符
function Person(name) {this.name = name;
}
Person.prototype.sayHello = function () {console.log('Hello');
};const person1 = new Person('Alice');
// 相当于:
const person2 = Object.create(Person.prototype);
Person.call(person2, 'Alice');// Object.create 只设置原型,不执行构造函数
const person3 = Object.create(Person.prototype);
console.log(person3.name); // undefined

Q2: 如何判断函数是否通过 new 调用?

function Person(name) {// 方法1:使用 new.targetif (!new.target) {throw new Error('Person must be called with new');}// 方法2:检查 this 的原型if (!(this instanceof Person)) {throw new Error('Person must be called with new');}this.name = name;
}// 正确使用
const person1 = new Person('Alice'); // 正常// 错误使用
try {const person2 = Person('Bob'); // 抛出错误
} catch (error) {console.log(error.message);
}

Q3: 箭头函数可以作为构造函数吗?

const ArrowConstructor = (name) => {this.name = name;
};try {const obj = new ArrowConstructor('Alice');
} catch (error) {console.log(error.message); // ArrowConstructor is not a constructor
}// 箭头函数没有 prototype 属性
console.log(ArrowConstructor.prototype); // undefined

🌟 ES6+ 中的新特性

Class 构造函数

class Person {constructor(name, age) {this.name = name;this.age = age;}sayHello() {console.log(`Hello, I'm ${this.name}`);}
}// 我们的 myNew 同样适用于 class
const person = myNew(Person, 'Alice', 25);
person.sayHello(); // Hello, I'm Alice

Proxy 构造函数

function createSmartConstructor(Constructor) {return new Proxy(Constructor, {construct(target, args, newTarget) {console.log(`Creating ${target.name} with args:`, args);return Reflect.construct(target, args, newTarget);}});
}const SmartPerson = createSmartConstructor(Person);
const person = myNew(SmartPerson, 'Alice', 25);
// 输出: Creating Person with args: ['Alice', 25]

📊 总结

核心要点

  1. new 操作符的本质:创建对象、设置原型、绑定 this、执行构造函数、返回结果
  2. 原型链机制:理解 __proto__prototype 的关系
  3. this 绑定:构造函数中的 this 指向新创建的对象
  4. 返回值处理:构造函数返回对象时会覆盖默认返回值

实现要点

  1. 参数验证:确保传入的是函数
  2. 原型设置:使用 Object.create__proto__
  3. this 绑定:使用 applycall
  4. 返回值判断:区分对象和原始值

实际应用

  1. 工厂模式:动态创建不同类型的实例
  2. 单例模式:控制实例创建过程
  3. 依赖注入:容器化管理对象创建
  4. 框架开发:理解组件实例化过程

性能考虑

  1. 避免重复类型检查:在高频调用场景中优化
  2. 使用 Object.create:比手动设置 __proto__ 更高效
  3. 内存管理:注意闭包和引用的清理

通过深入理解和实现 new 操作符,我们不仅掌握了 JavaScript 对象创建的核心机制,也为理解更复杂的面向对象编程概念打下了坚实基础。这个知识点在高级前端面试中经常出现,也是编写高质量 JavaScript 代码的重要基础。


技术标签JavaScript new操作符 原型链 构造函数 面向对象编程
难度等级:⭐⭐⭐⭐
适用场景:前端面试、JavaScript 底层原理学习、框架源码理解

💡 理解 new 操作符的工作原理,是掌握 JavaScript 面向对象编程的关键一步。通过手写实现,我们能更深入地理解 JavaScript 的内部机制。

http://www.dtcms.com/a/270708.html

相关文章:

  • 【Linux】权限的概念及理解
  • VR/AR在HMI中的创新应用:远程协作与维修的沉浸式体验
  • 类和对象拓展——日期类
  • 【实习篇】之Http头部字段之Disposition介绍
  • 使用 Docker 搭建 Rust Web 应用开发环境——AI教你学Docker
  • VR重现红军过雪山:一场穿越时空的精神洗礼​
  • MySQL 09 普通索引和唯一索引
  • MySQL 间隙锁
  • pytorch 自动微分
  • 半导体晶圆检测的基本知识
  • EGARCH
  • Linux C 目录流基本操作
  • Alloy VS Promtail:基于 Loki 的日志采集架构对比与选型指南
  • ECS由浅入深第四节:ECS 与 Unity 传统开发模式的结合?混合架构的艺术
  • Using Spring for Apache Pulsar:Publishing and Consuming Partitioned Topics
  • vue2 echarts中国地图、在地图上标注经纬度及标注点
  • AI应用实践:制作一个支持超长计算公式的计算器,计算内容只包含加减乘除算法,保存在一个HTML文件中
  • 「macOS 系统字体收集器 (C++17 实现)」
  • Oracle存储过程导出数据到Excel:全面实现方案详解
  • Java零基础笔记08(Java编程核心:面向对象编程高级 {继承、多态})
  • 【macOS】【Swift】【RTF】黑色文字在macOS深色外观下看不清的解决方法
  • yolo8实现目标检测
  • springMVC05-异常处理器
  • HashMap源码分析:put与get方法详解
  • 【拓扑空间】示例及详解1
  • sqlplus表结构查询
  • 高效集成-C#全能打印报表设计器诞生记
  • Android-重学kotlin(协程源码第一阶段)新学习总结
  • mongodb: cannot import name ‘_check_name‘ from ‘pymongo.database‘
  • 池化思想-Mysql异步连接池