一、原型(Prototype)基础概念
1.1 什么是原型
在JavaScript中,每个函数都拥有一个特殊的prototype属性(称为显式原型),该属性指向一个特定的对象——即原型对象。当使用new操作符创建实例时,这个实例会自动继承其构造函数prototype对象上定义的所有属性和方法。
function Person(name) {
this.name = name;
}
// 向Person函数的prototype添加方法
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
const person1 = new Person('Alice');
person1.sayHello(); // 输出:"Hello, I'm Alice"
1.2 实例的隐式原型 proto
除了null以外,每个JavaScript对象都包含一个内部属性[[Prototype]](在浏览器环境中可通过__proto__访问),这就是所谓的隐式原型。实例的__proto__直接指向其构造函数的prototype对象。
console.log(person1.__proto__ === Person.prototype); // 输出:true
核心关系:实例.proto === 构造函数.prototype
二、原型链(Prototype Chain)机制
2.1 原型链的形成
当访问对象的某个属性或方法时,JavaScript引擎遵循以下查找顺序:
首先在对象自身的属性中搜索
若未找到,则通过__proto__进入其原型对象继续查找
若原型对象中仍未找到,则继续沿__proto__向上追溯,直至遇到null
这种通过原型对象层层连接的查找路径构成了原型链。
function Animal(type) {
this.type = type;
}
Animal.prototype.breathe = function() {
console.log('Breathing...');
};
function Dog(name) {
this.name = name;
}
// 构建原型链:Dog.prototype → Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log('Woof!');
};
const myDog = new Dog('Buddy'); // 原型链查找过程演示:
myDog.bark(); // 1. 在自身找到 → 立即执行
myDog.breathe(); // 2. 自身无 → Dog.prototype无 → 在Animal.prototype中找到 → 执行
myDog.toString(); // 3. 沿原型链追溯至Object.prototype → 执行
2.2 完整的原型链结构
myDog → proto (指向Dog.prototype) → proto (指向Animal.prototype) → proto (指向Object.prototype) → proto (指向null)
三、关键特性与内置对象
3.1 构造函数与原型的关系
function Car(brand) {
this.brand = brand;
}
console.log(Car.prototype.constructor === Car); // 输出:true
const myCar = new Car('Toyota');
console.log(myCar.constructor === Car); // 输出:true
3.2 内置构造函数的原型链
const arr = [1, 2, 3]; // 原型链:arr → Array.prototype → Object.prototype → null
const str = 'hello'; // 原型链:str → String.prototype → Object.prototype → null
const num = 123; // 原型链:num → Number.prototype → Object.prototype → null
四、实际应用场景
4.1 方法共享与内存优化
// 不推荐:每个实例都会创建独立的say方法
function BadPerson(name) {
this.name = name;
this.say = function() {
console.log(this.name);
};
}
// 推荐:将方法定义于原型上,实现所有实例共享
function GoodPerson(name) {
this.name = name;
}
GoodPerson.prototype.say = function() {
console.log(this.name);
};
4.2 继承实现
// 父类定义
function Shape(color) {
this.color = color;
}
Shape.prototype.getArea = function() {
throw new Error('必须在子类中实现此方法');
};
// 子类定义
function Circle(radius, color) {
Shape.call(this, color); // 调用父类构造函数
this.radius = radius;
}
// 实现原型继承
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;
// 子类特有方法
Circle.prototype.getArea = function() {
return Math.PI * this.radius * this.radius;
};
五、常用方法与属性
5.1 原型相关方法
function Parent() {}
function Child() {}
// 设置原型链关系
Child.prototype = Object.create(Parent.prototype);
// 验证原型关系
console.log(Child.prototype.isPrototypeOf(new Child())); // 输出:true
console.log(Parent.prototype.isPrototypeOf(new Child())); // 输出:true
// 属性检查
const obj = new Child();
console.log(obj.hasOwnProperty('xxx')); // 检查对象自身属性
console.log('xxx' in obj); // 检查整个原型链
5.2 现代JavaScript中的原型
ES6引入的class语法实质上是原型机制的语法糖:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
// 等效于传统原型写法:
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise.`);
};
六、常见误区与最佳实践
6.1 避免的陷阱
// 错误做法:直接替换整个
prototype function MyClass() {}
const instance1 = new MyClass();
MyClass.prototype = {
newMethod: function() {}
}; // 这会破坏已创建实例的原型链
// 正确做法:在现有prototype上添加方法
MyClass.prototype.newMethod = function() {};
6.2 性能考量
过长的原型链会增加属性查找时间 对频繁访问的属性,建议直接定义为实例属性 使用Object.create(null)创建无原型对象可提升特定场景下的性能
七、核心总结
所有函数都具备prototype属性
所有对象都包含__proto__属性
原型链的终点始终是null
实例从构造函数的prototype继承属性和方法
ES6 class本质是基于原型体系的语法封装