JavaScript原型链通过委托实现继承,对象查找属性时会沿原型链向上搜索。每个对象的[[Prototype]]指向其原型,如构造函数实例的原型指向构造函数的prototype属性,而prototype默认包含constructor属性指回构造函数。使用new创建实例时,实例的[[Prototype]]被设为构造函数的prototype,从而实现方法共享。ES6的class是原型继承的语法糖,本质仍是基于原型链的委托机制,不同于传统类继承的复制模式。直接覆盖prototype会丢失constructor连接,需手动修复或避免覆盖。原型链广泛用于共享方法、实现继承(如Object.create)和Polyfill,但需注意长链带来的查找开销及动态修改原型可能引发的性能与稳定性问题。遍历属性时应使用hasOwnProperty过滤继承属性,确保正确性。

JavaScript原型链是其实现继承的核心机制,它不像传统面向对象语言那样通过类来复制属性,而是通过一种链式查找的方式,让对象能够访问其原型上的属性和方法。本质上,每个JavaScript对象都有一个指向另一个对象的内部链接,这个被链接的对象就是它的原型。当试图访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript就会沿着这个原型链向上查找,直到找到该属性或到达原型链的顶端(
null
要真正掌握原型链,我们得从几个核心概念入手。在我看来,理解它首先要抛开一些传统OOP语言的固有思维。JavaScript对象,它们生来就不是“类”的实例,而是直接从其他对象那里“借用”或“委托”功能。
每个对象在内部都有一个
[[Prototype]]
[[Prototype]]
Object.getPrototypeOf()
__proto__
当你创建一个对象,比如
const obj = {};[[Prototype]]
Object.prototype
Object.prototype
null
toString()
hasOwnProperty()
立即学习“Java免费学习笔记(深入)”;
当你尝试访问
obj.method()
obj
method
obj
[[Prototype]]
Object.prototype
method
null
undefined
继承的实现,很大程度上就是通过设置对象的
[[Prototype]]
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
const alice = new Person('Alice');
alice.sayHello(); // 输出: Hello, my name is Alice这里
Person
new
alice
[[Prototype]]
Person.prototype
alice
Person.prototype
sayHello
Person
Person.prototype
这可能是许多从Java或C++背景转过来的开发者最容易感到困惑的地方。在我看来,最核心的区别在于“复制”与“委托”。
传统类继承,比如Java,当你声明一个类
B extends A
B
A
Dog
Animal
而JavaScript的原型链继承,它更像是一种“委托”或“共享”。当你创建一个对象,并让它的原型指向另一个对象时,它并没有复制那个原型上的属性和方法。相反,它只是在自己的属性查找失败时,把这个查找请求“委托”给它的原型去完成。所以,这更像是一种“behaves-like”(表现得像)或“delegates-to”(委托给)的关系。一个
Dog
Animal.prototype
eat()
Dog
eat()
这种委托机制带来了极大的灵活性。原型对象本身可以在运行时被修改,这意味着所有依赖这个原型的对象,其行为也会动态地改变。这在类继承中是不可想象的,因为类一旦定义,其结构通常是相对固定的。ES6引入的
class
constructor
constructor
prototype
prototype
constructor
constructor
举个例子:
function Car(make) {
this.make = make;
}
console.log(Car.prototype.constructor === Car); // true当我们使用
new Car('Honda')myCar
myCar
[[Prototype]]
Car.prototype
myCar.constructor
const myCar = new Car('Honda');
console.log(myCar.constructor === Car); // true这里发生了什么?
myCar
constructor
myCar
[[Prototype]]
constructor
myCar.[[Prototype]]
Car.prototype
Car.prototype.constructor
Car
myCar.constructor
Car
这个机制在判断一个实例是由哪个构造函数创建时非常有用。然而,这里有个常见的“坑”:如果你完全覆盖了一个构造函数的
prototype
constructor
function Bike(brand) {
this.brand = brand;
}
// 错误示范:直接覆盖prototype,没有保留constructor
Bike.prototype = {
getBrand: function() {
return this.brand;
}
};
const myBike = new Bike('Giant');
console.log(myBike.constructor === Bike); // false (通常会是 Object)
console.log(myBike.constructor); // ƒ Object() { [native code] }在这种情况下,
myBike.constructor
Object
myBike.[[Prototype]]
{ getBrand: ... }[[Prototype]]
Object.prototype
Object.prototype
constructor
Object
constructor
function Scooter(model) {
this.model = model;
}
Scooter.prototype = {
constructor: Scooter, // 手动修复constructor指向
getModel: function() {
return this.model;
}
};
const myScooter = new Scooter('Vespa');
console.log(myScooter.constructor === Scooter); // true或者更推荐的做法是,不要直接覆盖整个
prototype
function Skateboard(type) {
this.type = type;
}
Skateboard.prototype.getType = function() { // 直接在原型上添加方法
return this.type;
};
const mySkateboard = new Skateboard('Longboard');
console.log(mySkateboard.constructor === Skateboard); // true理解
constructor
在日常的JavaScript开发中,原型链并非只是一个理论概念,它无处不在,并且深刻影响着我们代码的结构和性能。
常见的应用场景:
共享方法与属性: 这是原型链最直接也是最重要的应用。将方法定义在构造函数的
prototype
this
// 假设我们有成千上万个User实例
function User(name, email) {
this.name = name;
this.email = email;
// 如果在这里定义 greet 方法,每个实例都会有一个自己的副本
// this.greet = function() { console.log(`Hello, ${this.name}`); };
}
// 将方法定义在原型上,所有User实例共享同一个greet方法
User.prototype.greet = function() {
console.log(`Hello, ${this.name}`);
};
const user1 = new User('Alice', 'alice@example.com');
const user2 = new User('Bob', 'bob@example.com');
user1.greet(); // Hello, Alice
user2.greet(); // Hello, Bob
// 实际上 user1.greet 和 user2.greet 指向的是同一个函数对象
console.log(user1.greet === user2.greet); // true这对于内置对象也一样,比如
Array.prototype.map
String.prototype.toUpperCase
实现继承: 无论是ES5时代的组合继承、寄生组合继承,还是ES6的
class extends
Object.create()
const animal = {
eats: true,
walk() {
console.log("Animal walks");
}
};
const rabbit = Object.create(animal); // rabbit 的原型是 animal
rabbit.jumps = true;
rabbit.walk(); // Animal walks (方法从 animal 原型继承)Polyfills和功能扩展: 虽然不推荐直接修改
Object.prototype
prototype
Array.prototype
String.prototype
Array.prototype.flat
性能考量:
属性查找开销: 原型链越长,查找一个不存在于当前对象上的属性所需的时间就越长,因为它需要遍历更多的原型对象。在现代JavaScript引擎中,这种开销通常可以忽略不计,因为引擎做了大量的优化。然而,如果你的原型链设计得异常复杂或过长,理论上确实会增加查找时间。
动态修改原型: JavaScript允许在运行时修改对象的原型。当你修改一个原型对象时,所有继承自该原型的实例都会立即反映出这些改变。这既是原型链的强大之处,也可能成为潜在的性能问题或bug源。频繁或无意地修改共享原型,可能会导致难以预测的行为,尤其是在大型应用中。
hasOwnProperty
Object.prototype.hasOwnProperty.call(obj, prop)
obj.hasOwnProperty(prop)
for...in
总的来说,原型链是JavaScript的基石,深入理解它不仅能帮助我们写出更高效、更优雅的代码,也能更好地理解各种库和框架的内部机制。它提醒我们,JavaScript的面向对象思维与传统语言有所不同,更侧重于对象间的委托和共享。
以上就是掌握JavaScript原型链的核心概念与继承机制的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号