在 JavaScript 中,原型(prototype) 和 原型链(prototype chain) 是核心的概念,它们是 JavaScript 实现继承的基础。下面将详细解释这些概念、它们的特点、使用场景及案例。
1. 原型(Prototype)
概念:
- 每个 JavaScript 对象(包括函数)都有一个隐藏的内部属性叫做
[[Prototype]]
,这个属性指向另一个对象,这个对象就是它的原型。 - 当你创建一个对象时,该对象会从它的原型对象继承属性和方法。构造函数创建的每个对象实例都有一个隐式的原型引用。
关键点:
- 所有的 JavaScript 对象都有原型,除了一些特殊情况(如
Object.prototype
的原型是null
)。 - 原型是用来实现对象属性和方法共享的机制。通过原型,多个对象可以共享相同的属性和方法,节省内存。
- 通过
constructor.prototype
可以访问构造函数的原型对象。
案例:
function Person(name) {
this.name = name;
}
// 在原型上添加一个方法
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
const john = new Person('John');
john.greet(); // 输出:Hello, my name is John
// 实例对象 john 的原型指向 Person.prototype
console.log(john.__proto__ === Person.prototype); // true
在这个例子中,Person
构造函数的所有实例(如 john
)都共享 greet
方法,因为它们的原型对象是 Person.prototype
。
2. 原型链(Prototype Chain)
概念:
- 原型链 是指对象通过其
[[Prototype]]
属性(也可以通过__proto__
访问)一层一层地向上查找属性和方法,直到找到或到达null
终止搜索的过程。 - 当访问一个对象的属性或方法时,如果该对象自身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找。
关键点:
- 原型链是实现继承的基础,通过原型链,JavaScript 的对象可以继承属性和方法。
- 原型链的终点是
Object.prototype
,它的[[Prototype]]
为null
,表示原型链的结束。 - 如果原型链中没有找到所需的属性或方法,JavaScript 会返回
undefined
。
案例:
function Animal() {
this.species = 'Animal';
}
Animal.prototype.move = function() {
console.log('Animal is moving');
};
function Dog(name) {
this.name = name;
}
// Dog 继承自 Animal
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const dog = new Dog('Rex');
console.log(dog.name); // Rex
dog.move(); // Animal is moving
// 原型链:dog --> Dog.prototype --> Animal.prototype --> Object.prototype --> null
console.log(dog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
在这个例子中,dog
实例没有 move
方法,但通过原型链,它从 Animal.prototype
中继承了 move
方法。
3. 特点与使用场景
3.1 原型(Prototype)的特点
-
共享属性和方法:通过原型对象,多个实例对象可以共享相同的方法和属性。这在节省内存方面很有效,尤其是在创建大量对象时。
-
动态修改:你可以在程序运行时动态修改原型对象,比如添加新的方法或属性,这些修改会立即影响到所有实例。
-
内置对象的原型:JavaScript 的内置对象(如
Array
、String
、Function
)也有原型,可以通过修改这些原型来影响内置对象的行为。
3.2 原型链(Prototype Chain)的特点
-
继承机制:原型链是 JavaScript 实现继承的核心机制。子对象可以通过原型链继承父对象的属性和方法。
-
原型链的查找过程:当访问对象的属性或方法时,JavaScript 会首先检查对象自身的属性和方法,如果没有找到,会沿着原型链逐层向上查找,直到找到或者到达
null
。 -
性能考虑:虽然原型链机制简化了继承,但过长的原型链可能导致性能问题,因为每次访问属性时都要逐层查找。
4. 原型与原型链的实际使用场景
4.1 使用场景:共享方法
通过将方法定义在构造函数的原型对象上,可以在所有实例之间共享这些方法,从而减少内存消耗。
function Car(make, model) {
this.make = make;
this.model = model;
}
// 在原型上定义方法
Car.prototype.start = function() {
console.log(`${this.make} ${this.model} is starting`);
};
const car1 = new Car('Toyota', 'Corolla');
const car2 = new Car('Honda', 'Civic');
car1.start(); // Toyota Corolla is starting
car2.start(); // Honda Civic is starting
console.log(car1.start === car2.start); // true
这里,car1
和 car2
实例共享了同一个 start
方法,避免了重复定义。
4.2 使用场景:继承和原型链
当创建复杂的类层次结构时,可以利用原型链来实现继承,子类继承父类的属性和方法。
function Mammal(name) {
this.name = name;
}
Mammal.prototype.speak = function() {
console.log(`${this.name} makes a sound`);
};
function Cat(name) {
Mammal.call(this, name); // 继承父类的属性
}
Cat.prototype = Object.create(Mammal.prototype); // 继承父类的方法
Cat.prototype.constructor = Cat;
const kitty = new Cat('Kitty');
kitty.speak(); // Kitty makes a sound
Cat
继承了 Mammal
的 speak
方法,同时通过 Mammal.call(this, name)
来继承 Mammal
的属性。
4.3 使用场景:扩展内置对象
通过扩展 JavaScript 内置对象的原型,可以给内置对象添加新的功能或方法。
// 给 Array 添加一个新方法
Array.prototype.last = function() {
return this[this.length - 1];
};
const arr = [1, 2, 3];
console.log(arr.last()); // 输出 3
在这个例子中,我们给 Array
原型添加了一个 last
方法,所有的数组对象都可以直接使用它。
5. 原型与原型链的总结
-
原型 是每个对象(特别是函数的实例对象)都具有的一个属性,指向另一个对象。通过这个属性,JavaScript 对象可以共享属性和方法。
-
原型链 是多个对象之间的层级关系,体现了对象从另一个对象继承属性和方法的机制。对象在访问某个属性时,会沿着原型链逐层查找,直到找到为止。
-
使用原型和原型链的场景 通常包括创建大量对象时为了减少内存开销而共享方法、实现继承机制以及扩展内置对象的功能。
掌握原型和原型链的原理,是理解 JavaScript 对象模型及其继承机制的关键。