基本概念
1. 原型(Prototype)
在JavaScript中,每个函数都有一个特殊的属性叫做prototype(原型属性),这个属性指向一个对象,该对象包含了可以由该函数创建的所有实例共享的属性和方法。
js
function Person(name) {
this.name = name;
}
// 为Person的原型添加方法
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
const person1 = new Person('Alice');
person1.sayHello(); // "Hello, I'm Alice"
2. 原型链(Prototype Chain)
当访问一个对象的属性时,JavaScript引擎会:
- 首先在该对象自身属性中查找
- 如果没有找到,则沿着原型链向上查找
- 直到找到该属性或到达原型链的末端(null)
核心机制
1. __proto__ 属性
每个对象都有一个__proto__属性,指向创建该对象的构造函数的prototype。
js
function Animal(type) {
this.type = type;
}
const dog = new Animal('mammal');
console.log(dog.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
2. 构造函数、原型、实例的关系
rust
实例对象 --__proto__--> 构造函数的prototype --constructor--> 构造函数
深入理解
1. 原型继承
js
// 父类
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(`${this.name} is barking`);
};
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.eat(); // 继承自Animal
myDog.bark(); // Dog自己的方法
2. ES6 Class语法糖
js
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(`${this.name} is barking`);
}
}
// 本质上还是基于原型的继承
console.log(typeof Animal); // "function"
console.log(Animal.prototype.hasOwnProperty('eat')); // true
重要方法和属性
1. 原型相关方法
js
function Car(brand) {
this.brand = brand;
}
Car.prototype.drive = function() {
console.log(`${this.brand} is driving`);
};
const myCar = new Car('Toyota');
// 检查原型关系
console.log(myCar instanceof Car); // true
console.log(Car.prototype.isPrototypeOf(myCar)); // true
// 检查属性来源
console.log(myCar.hasOwnProperty('brand')); // true
console.log(myCar.hasOwnProperty('drive')); // false
// 获取原型
console.log(Object.getPrototypeOf(myCar) === Car.prototype); // true
2. 属性遍历
js
function List(items) {
this.items = items || [];
}
List.prototype.add = function(item) {
this.items.push(item);
};
const myList = new List([1, 2, 3]);
myList.customProp = 'custom';
// for...in 会遍历原型链上的可枚举属性
for (let key in myList) {
console.log(key); // items, customProp, add
}
// Object.keys 只返回自身可枚举属性
console.log(Object.keys(myList)); // ['items', 'customProp']
// 获取所有自身属性(包括不可枚举)
console.log(Object.getOwnPropertyNames(myList)); // ['items', 'customProp']
实际应用场景
1. 方法共享与内存优化
js
// 不好的做法:每个实例都创建新函数
function BadCircle(radius) {
this.radius = radius;
this.getArea = function() {
return Math.PI * this.radius * this.radius;
};
}
// 好的做法:方法放在原型上共享
function GoodCircle(radius) {
this.radius = radius;
}
GoodCircle.prototype.getArea = function() {
return Math.PI * this.radius * this.radius;
};
const circles = [];
for (let i = 0; i < 1000; i++) {
circles.push(new GoodCircle(i));
}
// 所有实例共享同一个getArea方法,节省内存
2. 扩展内置对象
js
// 为数组添加自定义方法(谨慎使用)
Array.prototype.last = function() {
return this[this.length - 1];
};
const arr = [1, 2, 3, 4, 5];
console.log(arr.last()); // 5
原型链示意图
javascript
实例对象
↓ __proto__
构造函数的prototype
↓ __proto__
Object.prototype
↓ __proto__
null
总结要点
- 每个函数都有prototype属性,指向原型对象
- 每个对象都有__proto__属性,指向创建它的构造函数的prototype
- 原型链的终点是null
- 原型继承是JavaScript的核心继承机制
- ES6的class语法本质上是原型继承的语法糖