什么是原型?
在 JavaScript 中,每个对象都有一个内部链接指向另一个对象,我们称之为“原型”(Prototype)。该原型对象也是另一个对象,而这个链接最终会指向 null
。通过这个机制,一个对象可以继承另一个对象的属性和方法。
当我们访问一个对象的属性时,JavaScript 引擎首先会检查该对象自身是否有这个属性,如果没有,它会沿着原型链查找该属性,直到找到为止,或直到链的末尾(null
)。
原型的核心概念
- 原型对象(Prototype Object): 每个 JavaScript 对象都有一个与之关联的原型对象。这个原型对象可以包含该对象可以访问的属性和方法。
__proto__
属性: 虽然 JavaScript 并不推荐直接使用__proto__
,但它是访问对象原型的方式之一。它指向了对象的原型,即对象的内部原型链接。
const obj = {};
console.log(obj.__proto__); // 输出对象的原型
constructor
属性: 每个函数都有一个prototype
属性,这个prototype
指向的是一个对象,作为所有由该构造函数创建的实例的原型。原型对象又有一个constructor
属性,指向该构造函数。
function Person(name) {
this.name = name;
}
const person1 = new Person('Alice');
console.log(person1.constructor === Person); // true
console.log(Person.prototype.constructor === Person); // true
什么是原型链?
原型链是多个对象通过原型关联在一起形成的链式结构。JavaScript 中的对象通过原型链可以继承其他对象的属性和方法。
当我们访问一个对象的属性时,JavaScript 引擎首先会在对象自身的属性中查找,如果没有找到,它会沿着该对象的原型链依次查找原型对象,直到找到该属性或到达原型链的终点——null
。
原型链的结构
原型链的终点是 null
,即 Object.prototype.__proto__
是 null
。
const obj = {};
console.log(Object.getPrototypeOf(obj)); // 输出 Object.prototype
console.log(Object.getPrototypeOf(Object.prototype)); // 输出 null
原型链:例子
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function() {
console.log(this.name);
};
function Dog(name, breed) {
Animal.call(this, name); // 继承属性
this.breed = breed;
}
// 继承方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.sayName(); // 输出 'Buddy'
// 原型链结构
console.log(myDog.__proto__); // Dog.prototype
console.log(myDog.__proto__.__proto__); // Animal.prototype
console.log(myDog.__proto__.__proto__.__proto__); // Object.prototype
console.log(myDog.__proto__.__proto__.__proto__.__proto__); // null
在上面的例子中,myDog
是 Dog
的实例,Dog.prototype
是 Animal.prototype
的一个子对象,继承了 Animal
的方法。这就是原型链继承的典型例子。
原型链中的属性查找机制
当访问对象属性时,JavaScript 引擎按照以下顺序查找:
- 先检查对象自身是否拥有该属性。
- 如果对象没有这个属性,则沿着
__proto__
查找它的原型。 - 继续沿着原型链查找,直到找到该属性或到达原型链的终点(
null
)。
属性查找:例子说明
function Car(brand) {
this.brand = brand;
}
Car.prototype.getBrand = function() {
return this.brand;
};
const myCar = new Car('Tesla');
console.log(myCar.brand); // 'Tesla',找到实例自身的属性
console.log(myCar.getBrand()); // 'Tesla',找到原型链上的方法
如果我们尝试访问一个不存在的属性,例如:
console.log(myCar.color); // undefined
此时,JavaScript 引擎会沿着原型链查找 color
属性,但最终没有找到,因此返回 undefined
。
原型链的继承机制
JavaScript 的继承是基于原型链实现的。通过构造函数和原型的结合,可以实现对象之间的继承。
-
构造函数继承属性: 子类构造函数调用父类构造函数,继承父类的属性。
function Parent() { this.name = 'Parent'; } function Child() { Parent.call(this); // 继承父类属性 } const child = new Child(); console.log(child.name); // 'Parent'
-
原型链继承方法: 子类通过
Object.create
或直接设置其prototype
指向父类的原型,实现对父类方法的继承。function Parent() {} Parent.prototype.sayHello = function() { console.log('Hello'); }; function Child() {} Child.prototype = Object.create(Parent.prototype); // 继承父类方法 Child.prototype.constructor = Child; // 修正 constructor 指向 const child = new Child(); child.sayHello(); // 输出 'Hello'
原型链的常见问题
-
原型污染: 修改原型对象会影响所有共享该原型的实例。如果不小心在原型上修改了属性或方法,所有实例都会受到影响。
function Person(name) { this.name = name; } Person.prototype.sayName = function() { console.log(this.name); }; const person1 = new Person('Alice'); const person2 = new Person('Bob'); person1.sayName(); // 'Alice' Person.prototype.sayName = function() { console.log('Modified'); }; person2.sayName(); // 'Modified',所有实例都受到了影响
-
无法枚举原型链上的属性: 原型链上的属性默认是不可枚举的,即
for...in
和Object.keys()
只会遍历对象自身的属性,不包括从原型链继承的属性。const obj = Object.create({ a: 1 }); obj.b = 2; console.log(Object.keys(obj)); // ['b'],原型链上的 'a' 不会被列出
应用场景
-
继承: 原型链的最大应用场景是继承,通过构造函数和原型链可以创建继承链,实现父类和子类的关系,复用代码。
-
属性查找: 原型链机制保证了对象在查找属性时可以自动向上查找,实现了简单的属性继承和扩展。