1. 原型链的本质与运作机制
在JavaScript中,每个对象都有一个隐藏的[[Prototype]]属性,它指向另一个对象或null。当访问对象的属性时,如果对象自身没有该属性,JavaScript会沿着[[Prototype]]链向上查找,直到找到该属性或到达原型链末端(null)。这就是原型继承的核心机制。
1.1 原型对象与构造函数的关系
每个函数在创建时都会自动获得一个prototype属性(注意不是[[Prototype]])。当使用new操作符调用函数时:
- 创建一个新对象
- 将新对象的[[Prototype]]指向构造函数的prototype属性
- 将this绑定到新对象并执行构造函数
- 返回新对象(除非构造函数返回非原始值)
function Person(name) { this.name = name; } // 所有Person实例共享的方法 Person.prototype.sayHello = function() { console.log(`Hello, I'm ${this.name}`); }; const john = new Person('John'); john.sayHello(); // 通过原型链访问方法关键点:构造函数的prototype属性会成为实例的[[Prototype]],这就是原型继承的实现基础。
2. 原型链的构建方式
2.1 直接设置原型
ES6提供了Object.setPrototypeOf()方法直接设置对象的原型:
const parent = { familyName: 'Smith' }; const child = { firstName: 'John' }; Object.setPrototypeOf(child, parent); console.log(child.familyName); // 'Smith' 通过原型链访问2.2 构造函数模式
传统方式是通过构造函数和prototype属性构建原型链:
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('Woof!'); };2.3 类语法糖
ES6的class语法是构造函数模式的语法糖:
class Animal { constructor(name) { this.name = name; } eat() { console.log(`${this.name} is eating`); } } class Dog extends Animal { constructor(name, breed) { super(name); this.breed = breed; } bark() { console.log('Woof!'); } }3. 原型链的查找机制
3.1 属性访问的完整过程
当访问对象属性时,JavaScript引擎会:
- 检查对象自身是否有该属性
- 如果没有,检查对象的[[Prototype]]
- 递归这个过程直到找到属性或[[Prototype]]为null
const grandparent = { a: 1 }; const parent = { b: 2 }; const child = { c: 3 }; Object.setPrototypeOf(parent, grandparent); Object.setPrototypeOf(child, parent); console.log(child.a); // 1 (grandparent.a) console.log(child.b); // 2 (parent.b) console.log(child.c); // 3 (own property)3.2 属性遮蔽(Property Shadowing)
当对象和原型链上有同名属性时,对象自身的属性会"遮蔽"原型链上的属性:
const parent = { value: 1 }; const child = { value: 2 }; Object.setPrototypeOf(child, parent); console.log(child.value); // 2 (自身属性优先) delete child.value; console.log(child.value); // 1 (现在访问原型链)4. 原型链的实践应用与陷阱
4.1 方法共享的优势
原型链允许所有实例共享方法,节省内存:
function Person(name) { this.name = name; } // 所有实例共享同一个sayHello方法 Person.prototype.sayHello = function() { console.log(`Hello from ${this.name}`); }; const p1 = new Person('Alice'); const p2 = new Person('Bob'); console.log(p1.sayHello === p2.sayHello); // true4.2 原型污染问题
修改内置对象原型可能引发问题:
// 危险:修改Array原型会影响所有数组 Array.prototype.sayHello = function() { console.log("I'm an array!"); }; [].sayHello(); // 能工作但不推荐最佳实践:避免修改内置对象原型,除非是polyfill
4.3 原型链的性能考量
过长的原型链会影响属性查找性能:
// 创建深度原型链 let current = {}; for (let i = 0; i < 100; i++) { const newObj = {}; Object.setPrototypeOf(newObj, current); current = newObj; } // 属性查找需要遍历整个链 console.time('deep lookup'); current.foo = 'bar'; console.log(current.foo); console.timeEnd('deep lookup'); // 耗时明显增加5. 现代JavaScript中的原型继承
5.1 class语法与原型的关系
class语法更清晰但底层仍是原型继承:
class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a noise`); } } class Dog extends Animal { constructor(name) { super(name); } speak() { console.log(`${this.name} barks`); } } // 底层原型关系 console.log( Object.getPrototypeOf(Dog.prototype) === Animal.prototype ); // true5.2 使用Object.create()
创建具有特定原型的对象:
const personPrototype = { greet() { console.log(`Hello, I'm ${this.name}`); } }; const person = Object.create(personPrototype); person.name = 'Alice'; person.greet(); // Hello, I'm Alice5.3 原型链诊断方法
检查原型关系的工具方法:
const obj = {}; const arr = []; // 检查原型 console.log(Object.getPrototypeOf(obj) === Object.prototype); // true console.log(arr instanceof Array); // true // 检查属性来源 console.log(obj.hasOwnProperty('toString')); // false console.log('toString' in obj); // true6. 原型链的边界情况与特殊行为
6.1 原型为null的对象
创建没有原型的对象:
const bareObject = Object.create(null); console.log('toString' in bareObject); // false // 这类对象不继承任何方法 try { bareObject.hasOwnProperty('foo'); } catch (e) { console.log(e); // TypeError }6.2 构造函数返回对象的情况
当构造函数返回对象时,会改变默认行为:
function Foo() { this.a = 1; return { a: 2 }; // 返回对象会替换默认创建的对象 } const foo = new Foo(); console.log(foo.a); // 2 而不是16.3 循环原型链
JavaScript会阻止循环原型链:
const a = {}; const b = {}; Object.setPrototypeOf(a, b); try { Object.setPrototypeOf(b, a); // 抛出错误 } catch (e) { console.log(e); // TypeError }在实际项目中,理解原型链机制能帮助你更好地设计对象结构,避免常见的陷阱。虽然现代JavaScript中class语法更常用,但其底层仍然是基于原型继承实现的。掌握这些知识对于调试复杂问题和理解第三方库的工作原理都至关重要。