Appearance
JavaScript 原型链
什么是原型链
原型链(Prototype Chain)是 JavaScript 中实现继承的核心机制。在 JavaScript 中,每个对象都有一个原型对象(prototype),对象可以从其原型中继承属性和方法。当我们访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript 会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的末端(null)。
原型对象
1. prototype 属性
函数对象有一个 prototype 属性,指向一个对象,这个对象就是该函数的原型对象。当使用该函数作为构造函数创建实例时,实例的原型会指向这个原型对象。
javascript
// 示例
function Person(name) {
this.name = name;
}
// 向原型对象添加方法
Person.prototype.greet = function () {
return `Hello, my name is ${this.name}`;
};
// 创建实例
const person = new Person("John");
console.log(person.greet()); // 'Hello, my name is John'
// 检查原型链
console.log(person.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true2. __proto__ 属性
每个对象都有一个 __proto__ 属性(也称为 [[Prototype]]),指向其原型对象。这是一个非标准但广泛支持的属性,在现代 JavaScript 中,推荐使用 Object.getPrototypeOf() 和 Object.setPrototypeOf() 来操作原型。
javascript
// 示例
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true
const arr = [];
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true
const func = function () {};
console.log(func.__proto__ === Function.prototype); // true
console.log(func.__proto__.__proto__ === Object.prototype); // true3. Object.getPrototypeOf() 和 Object.setPrototypeOf()
现代 JavaScript 提供了标准方法来操作对象的原型。
javascript
// 示例
const obj = {};
const proto = { greet: () => "Hello" };
// 设置原型
Object.setPrototypeOf(obj, proto);
console.log(obj.greet()); // 'Hello'
// 获取原型
const objProto = Object.getPrototypeOf(obj);
console.log(objProto === proto); // true原型链的工作原理
1. 属性查找
当访问对象的属性或方法时,JavaScript 会按照以下步骤查找:
- 首先在对象本身查找该属性或方法
- 如果找不到,就沿着
__proto__指向的原型对象查找 - 如果还找不到,就沿着原型对象的
__proto__继续向上查找 - 直到找到该属性或方法,或者到达原型链的末端(
null)
javascript
// 示例
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
return `Hello, my name is ${this.name}`;
};
const person = new Person("John");
// 访问对象本身的属性
console.log(person.name); // 'John'
// 访问原型链上的方法
console.log(person.greet()); // 'Hello, my name is John'
// 访问原型链上的属性
console.log(person.toString()); // '[object Object]'(继承自 Object.prototype)
// 访问不存在的属性
console.log(person.age); // undefined2. 属性赋值
当给对象赋值时,JavaScript 会直接在对象本身创建属性,不会影响原型链。
javascript
// 示例
function Person(name) {
this.name = name;
}
Person.prototype.age = 30;
const person1 = new Person("John");
const person2 = new Person("Jane");
// 访问原型链上的属性
console.log(person1.age); // 30
console.log(person2.age); // 30
// 给对象赋值,会在对象本身创建属性
person1.age = 25;
console.log(person1.age); // 25(对象本身的属性)
console.log(person2.age); // 30(原型链上的属性)
// 检查属性是否在对象本身
console.log(person1.hasOwnProperty("age")); // true
console.log(person2.hasOwnProperty("age")); // false3. 原型链的末端
原型链的末端是 Object.prototype,其 __proto__ 指向 null。
javascript
// 示例
console.log(Object.prototype.__proto__ === null); // true
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true
console.log(obj.__proto__.__proto__ === null); // true构造函数和原型
1. 构造函数
构造函数是用于创建对象的函数,使用 new 关键字调用。
javascript
// 示例
function Person(name, age) {
this.name = name;
this.age = age;
}
// 创建实例
const person = new Person("John", 30);
console.log(person.name); // 'John'
console.log(person.age); // 302. 构造函数的原型
构造函数的 prototype 属性指向一个对象,这个对象是所有通过该构造函数创建的实例的原型。
javascript
// 示例
function Person(name) {
this.name = name;
}
// 向原型添加方法
Person.prototype.greet = function () {
return `Hello, my name is ${this.name}`;
};
Person.prototype.sayAge = function () {
return `I am ${this.age} years old`;
};
// 创建实例
const person = new Person("John");
person.age = 30;
console.log(person.greet()); // 'Hello, my name is John'
console.log(person.sayAge()); // 'I am 30 years old'
// 检查原型
console.log(person.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true3. 修改原型
可以通过修改构造函数的 prototype 来为所有实例添加或修改方法。
javascript
// 示例
function Person(name) {
this.name = name;
}
const person1 = new Person("John");
// 修改原型
Person.prototype.greet = function () {
return `Hello, my name is ${this.name}`;
};
const person2 = new Person("Jane");
// 所有实例都能访问新添加的方法
console.log(person1.greet()); // 'Hello, my name is John'
console.log(person2.greet()); // 'Hello, my name is Jane'继承
1. 原型继承
通过原型链实现继承。
javascript
// 示例
// 父构造函数
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
return `Hello, my name is ${this.name}`;
};
// 子构造函数
function Student(name, grade) {
// 调用父构造函数
Person.call(this, name);
this.grade = grade;
}
// 设置原型链,实现继承
Student.prototype = Object.create(Person.prototype);
// 修复 constructor 指向
Student.prototype.constructor = Student;
// 添加子构造函数特有的方法
Student.prototype.study = function () {
return `${this.name} is studying`;
};
// 创建实例
const student = new Student("John", 9);
console.log(student.name); // 'John'
console.log(student.grade); // 9
console.log(student.greet()); // 'Hello, my name is John'(继承自 Person)
console.log(student.study()); // 'John is studying'(Student 特有的方法)
// 检查原型链
console.log(student.__proto__ === Student.prototype); // true
console.log(Student.prototype.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true2. 多重继承
JavaScript 不直接支持多重继承,但可以通过混合(mixin)的方式实现类似的功能。
javascript
// 示例
// 第一个混入对象
const canEat = {
eat: function (food) {
return `${this.name} is eating ${food}`;
},
};
// 第二个混入对象
const canSleep = {
sleep: function () {
return `${this.name} is sleeping`;
},
};
// 构造函数
function Person(name) {
this.name = name;
}
// 混合混入对象
Object.assign(Person.prototype, canEat, canSleep);
// 创建实例
const person = new Person("John");
console.log(person.eat("apple")); // 'John is eating apple'
console.log(person.sleep()); // 'John is sleeping'原型链的方法
1. hasOwnProperty()
检查对象本身是否具有指定的属性(不包括原型链上的属性)。
javascript
// 示例
function Person(name) {
this.name = name;
}
Person.prototype.age = 30;
const person = new Person("John");
console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("age")); // false
console.log("age" in person); // true(包括原型链上的属性)2. isPrototypeOf()
检查一个对象是否是另一个对象的原型。
javascript
// 示例
function Person(name) {
this.name = name;
}
const person = new Person("John");
console.log(Person.prototype.isPrototypeOf(person)); // true
console.log(Object.prototype.isPrototypeOf(person)); // true
console.log(Array.prototype.isPrototypeOf(person)); // false3. Object.getPrototypeOf()
获取对象的原型。
javascript
// 示例
function Person(name) {
this.name = name;
}
const person = new Person("John");
const proto = Object.getPrototypeOf(person);
console.log(proto === Person.prototype); // true4. Object.setPrototypeOf()
设置对象的原型。
javascript
// 示例
const proto = { greet: () => "Hello" };
const obj = {};
Object.setPrototypeOf(obj, proto);
console.log(obj.greet()); // 'Hello'5. Object.create()
创建一个新对象,使用指定的对象作为其原型。
javascript
// 示例
const proto = {
greet: function () {
return `Hello, my name is ${this.name}`;
},
};
const person = Object.create(proto);
person.name = "John";
console.log(person.greet()); // 'Hello, my name is John'
console.log(Object.getPrototypeOf(person) === proto); // true常见的原型链
1. 数组的原型链
javascript
// 示例
const arr = [];
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
// 数组继承的方法
console.log(arr.push); // 继承自 Array.prototype
console.log(arr.toString); // 继承自 Object.prototype2. 函数的原型链
javascript
// 示例
function func() {}
console.log(func.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
// 函数继承的方法
console.log(func.apply); // 继承自 Function.prototype
console.log(func.toString); // 继承自 Object.prototype3. 日期的原型链
javascript
// 示例
const date = new Date();
console.log(date.__proto__ === Date.prototype); // true
console.log(Date.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
// 日期继承的方法
console.log(date.getFullYear); // 继承自 Date.prototype
console.log(date.toString); // 继承自 Object.prototype原型链的应用场景
1. 共享方法
通过原型链共享方法,可以减少内存使用,提高性能。
javascript
// 示例
// 不好的做法:每个实例都有自己的方法
function Person(name) {
this.name = name;
this.greet = function () {
return `Hello, my name is ${this.name}`;
};
}
// 好的做法:通过原型链共享方法
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
return `Hello, my name is ${this.name}`;
};
const person1 = new Person("John");
const person2 = new Person("Jane");
console.log(person1.greet === person2.greet); // true(共享同一个方法)2. 实现继承
通过原型链实现对象之间的继承关系。
javascript
// 示例
// 基础类
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function () {
return `${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 () {
return `${this.name} is barking`;
};
const dog = new Dog("Rex", "German Shepherd");
console.log(dog.name); // 'Rex'
console.log(dog.breed); // 'German Shepherd'
console.log(dog.eat()); // 'Rex is eating'(继承自 Animal)
console.log(dog.bark()); // 'Rex is barking'(Dog 特有的方法)3. 扩展内置对象
通过修改内置对象的原型,可以扩展内置对象的功能。
javascript
// 示例
// 扩展 Array 原型
Array.prototype.last = function () {
return this[this.length - 1];
};
const arr = [1, 2, 3];
console.log(arr.last()); // 3
// 扩展 String 原型
String.prototype.capitalize = function () {
return this.charAt(0).toUpperCase() + this.slice(1);
};
const str = "hello";
console.log(str.capitalize()); // 'Hello'
// 注意:扩展内置对象可能会与未来的 JavaScript 版本冲突,谨慎使用4. 原型模式
使用原型模式创建对象,可以避免重复初始化对象的开销。
javascript
// 示例
const personPrototype = {
greet: function () {
return `Hello, my name is ${this.name}`;
},
init: function (name, age) {
this.name = name;
this.age = age;
return this;
},
};
// 创建实例
const person1 = Object.create(personPrototype).init("John", 30);
const person2 = Object.create(personPrototype).init("Jane", 25);
console.log(person1.greet()); // 'Hello, my name is John'
console.log(person2.greet()); // 'Hello, my name is Jane'原型链的注意事项
1. 原型链的性能
属性查找的性能与原型链的长度有关,原型链越长,查找速度越慢。因此,应该尽量保持原型链简短。
javascript
// 示例
// 避免深层原型链
function Level1() {}
Level1.prototype.method1 = function () {};
function Level2() {}
Level2.prototype = Object.create(Level1.prototype);
Level2.prototype.method2 = function () {};
function Level3() {}
Level3.prototype = Object.create(Level2.prototype);
Level3.prototype.method3 = function () {};
// 深层原型链会影响性能
const obj = new Level3();
// 查找 method1 需要遍历三层原型链2. 原型链的修改
修改原型链会影响所有基于该原型的实例,包括修改前创建的实例。
javascript
// 示例
function Person(name) {
this.name = name;
}
const person1 = new Person("John");
// 修改原型
Person.prototype.greet = function () {
return `Hello, my name is ${this.name}`;
};
const person2 = new Person("Jane");
// 所有实例都能访问新添加的方法
console.log(person1.greet()); // 'Hello, my name is John'
console.log(person2.greet()); // 'Hello, my name is Jane'3. 原型链的循环引用
应避免创建循环引用的原型链,这会导致无限递归。
javascript
// 示例
// 避免循环引用
const obj1 = {};
const obj2 = {};
// 循环引用会导致问题
// Object.setPrototypeOf(obj1, obj2);
// Object.setPrototypeOf(obj2, obj1);
// 这样会导致无限递归
// console.log(obj1.someProperty);4. 原型链与 this
在原型链的方法中,this 指向调用该方法的对象。
javascript
// 示例
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
return `Hello, my name is ${this.name}`;
};
const person = new Person("John");
console.log(person.greet()); // 'Hello, my name is John'(this 指向 person)
// 注意:箭头函数的 this 不会绑定到调用对象
Person.prototype.greetArrow = () => {
return `Hello, my name is ${this.name}`;
};
console.log(person.greetArrow()); // 'Hello, my name is undefined'(箭头函数的 this 指向全局对象)原型链的最佳实践
1. 使用 Object.create() 创建原型
使用 Object.create() 创建原型,可以更清晰地设置原型链。
javascript
// 示例
// 好的做法
function Parent() {}
function Child() {
Parent.call(this);
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
// 不好的做法
function Child() {
Parent.call(this);
}
Child.prototype = new Parent(); // 会执行 Parent 构造函数,可能产生副作用2. 避免修改内置对象的原型
修改内置对象的原型可能会与未来的 JavaScript 版本冲突,谨慎使用。
javascript
// 示例
// 避免这样做
Array.prototype.myMethod = function () {
// 自定义方法
};
// 推荐的做法:使用工具函数
function myMethod(arr) {
// 操作数组
}3. 使用 hasOwnProperty() 检查属性
在遍历对象属性时,使用 hasOwnProperty() 检查属性是否属于对象本身,避免遍历原型链上的属性。
javascript
// 示例
function Person(name) {
this.name = name;
}
Person.prototype.age = 30;
const person = new Person("John");
// 好的做法:使用 hasOwnProperty
for (const key in person) {
if (person.hasOwnProperty(key)) {
console.log(key, person[key]); // 只遍历对象本身的属性
}
}
// 或者使用 Object.keys()
Object.keys(person).forEach((key) => {
console.log(key, person[key]); // 只遍历对象本身的属性
});4. 合理使用原型链长度
保持原型链简短,避免深层继承,以提高属性查找性能。
javascript
// 示例
// 好的做法:保持原型链简短
function Animal() {}
function Dog() {
Animal.call(this);
}
Dog.prototype = Object.create(Animal.prototype);
// 不好的做法:深层继承
function Animal() {}
function Mammal() {
Animal.call(this);
}
Mammal.prototype = Object.create(Animal.prototype);
function Carnivore() {
Mammal.call(this);
}
Carnivore.prototype = Object.create(Mammal.prototype);
function Dog() {
Carnivore.call(this);
}
Dog.prototype = Object.create(Carnivore.prototype);5. 使用 ES6 类
ES6 引入了 class 语法,提供了更清晰的方式来定义类和实现继承,底层仍然使用原型链。
javascript
// 示例
// 使用 ES6 类
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, my name is ${this.name}`;
}
}
class Student extends Person {
constructor(name, grade) {
super(name);
this.grade = grade;
}
study() {
return `${this.name} is studying`;
}
}
const student = new Student("John", 9);
console.log(student.greet()); // 'Hello, my name is John'
console.log(student.study()); // 'John is studying'
// 底层仍然使用原型链
console.log(student.__proto__ === Student.prototype); // true
console.log(Student.prototype.__proto__ === Person.prototype); // true原型链的兼容性
1. 浏览器兼容性
原型链是 JavaScript 的核心特性,所有现代浏览器都支持。在旧版本浏览器中,可能存在一些差异:
- IE8 及以下版本不支持
Object.getPrototypeOf()和Object.setPrototypeOf() - IE8 及以下版本的
__proto__属性实现不一致
2. 兼容性解决方案
对于不支持现代方法的浏览器,可以使用 polyfill 或替代方案。
javascript
// 示例
// 兼容 Object.getPrototypeOf()
if (!Object.getPrototypeOf) {
Object.getPrototypeOf = function (obj) {
return (
obj.__proto__ ||
(obj.constructor ? obj.constructor.prototype : Object.prototype)
);
};
}
// 兼容 Object.create()
if (!Object.create) {
Object.create = function (proto) {
function F() {}
F.prototype = proto;
return new F();
};
}总结
原型链是 JavaScript 中实现继承的核心机制,它允许对象从其原型中继承属性和方法。原型链的主要特点包括:
- 属性查找:当访问对象的属性或方法时,JavaScript 会沿着原型链向上查找
- 原型对象:每个对象都有一个原型对象,通过
__proto__属性指向 - 构造函数的 prototype:函数对象的
prototype属性指向其原型对象 - 继承:通过设置原型链,可以实现对象之间的继承关系
- 原型链的末端:原型链的末端是
Object.prototype,其__proto__指向null
原型链的应用场景非常广泛,包括:
- 共享方法,减少内存使用
- 实现继承,构建对象层次结构
- 扩展内置对象的功能
- 使用原型模式创建对象
通过理解原型链的工作原理,我们可以更好地掌握 JavaScript 的面向对象编程特性,编写更加高效、可维护的代码。在现代 JavaScript 中,虽然引入了 class 语法,但底层仍然使用原型链实现继承,因此理解原型链对于深入掌握 JavaScript 仍然非常重要。