欢迎使用我的小程序👇👇👇👇
想象一下,你有一个工具库,每次需要新工具时,不是从零开始制造,而是基于现有工具改进——这就是JavaScript原型继承的核心思想。
从简单对象说起
在JavaScript中,万物皆对象。当我们创建一个对象时,它并不是孤立存在的:
// 创建一个简单的对象
let person = {
name: '小明',
age: 25,
greet() {
console.log(`你好,我是${this.name}`);
}
};
person.greet(); // 你好,我是小明
但如果有多个相似的对象,每个都这样创建会非常低效。这时候构造函数就派上用场了。
构造函数与原型初探
// 构造函数(首字母大写是约定)
function Person(name, age) {
this.name = name;
this.age = age;
}
// 通过构造函数的prototype属性添加共享方法
Person.prototype.greet = function() {
console.log(`你好,我是${this.name}`);
};
// 创建实例
let person1 = new Person('小明', 25);
let person2 = new Person('小红', 23);
person1.greet(); // 你好,我是小明
person2.greet(); // 你好,我是小红
console.log(person1.greet === person2.greet); // true - 同一个方法!
神奇的事情发生了:两个实例共享同一个greet方法!这是如何实现的?
原型对象:共享的工具箱
每个JavaScript函数都有一个特殊的prototype属性(箭头函数除外)。当我们使用new关键字调用函数时:
- 创建一个新对象
- 将这个对象的
__proto__指向构造函数的prototype - 将
this绑定到这个新对象 - 执行构造函数
- 返回这个新对象
// 查看原型关系
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
// 实例的原型链
console.log(person1.__proto__); // Person.prototype
console.log(person1.__proto__.__proto__); // Object.prototype
console.log(person1.__proto__.__proto__.__proto__); // null
原型链:逐级查找的机制
当访问对象的属性或方法时,JavaScript会:
- 先在对象自身查找
- 如果找不到,通过
__proto__到原型对象中查找 - 继续沿原型链向上查找,直到找到或到达
null
// 原型链查找示例
person1.greet(); // 1. person1自身没有greet方法
// 2. 去person1.__proto__(Person.prototype)找
// 3. 找到了!执行
// 添加自身属性会覆盖原型属性
person1.greet = function() {
console.log(`嗨,我是${this.name}`);
};
person1.greet(); // 嗨,我是小明 (使用自身方法)
person2.greet(); // 你好,我是小红 (仍使用原型方法)
// 删除自身属性后,又会使用原型的方法
delete person1.greet;
person1.greet(); // 你好,我是小明
完整的原型链图示
让我们通过一个更复杂的例子理解完整的原型链:
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name}在吃东西`);
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数
this.breed = breed;
}
// 关键步骤:建立原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复constructor指向
Dog.prototype.bark = function() {
console.log(`${this.name}在汪汪叫`);
};
let myDog = new Dog('旺财', '金毛');
// 完整的原型链查找
myDog.bark(); // 1. myDog自身查找 → 2. Dog.prototype找到
myDog.eat(); // 1. myDog自身没有 → 2. Dog.prototype没有 →
// 3. Animal.prototype找到
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true
console.log(myDog instanceof Object); // true
现代JavaScript中的原型继承
ES6引入了class语法糖,让原型继承更加直观:
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name}在吃东西`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
bark() {
console.log(`${this.name}在汪汪叫`);
}
}
// 底层仍然是原型继承
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
实用技巧与注意事项
- 检查原型关系
// 检查对象是否是构造函数的实例
console.log(myDog instanceof Dog); // true
// 检查属性是在自身还是原型上
console.log(myDog.hasOwnProperty('name')); // true
console.log(myDog.hasOwnProperty('eat')); // false
// 获取对象的原型
console.log(Object.getPrototypeOf(myDog) === Dog.prototype); // true
- 避免常见的原型陷阱
// 错误:直接修改内置对象的原型
Array.prototype.customMethod = function() { /* ... */ }; // 不推荐!
// 正确:通过继承扩展功能
class MyArray extends Array {
customMethod() { /* ... */ }
}
// 性能注意:原型链过长会影响查找速度
- 实现安全的原型继承
// 使用Object.create实现纯净的原型链
function createObject(proto) {
function F() {}
F.prototype = proto;
return new F();
}
// ES5标准方式
let child = Object.create(parent);
总结
JavaScript的原型继承机制就像一条工具传递链:
- 每个对象都有一个隐式的
__proto__链接指向它的原型 - 每个函数都有一个显式的
prototype属性,用于实例共享方法 - 查找属性时沿着原型链向上,形成了一种优雅的继承机制
理解原型链不仅有助于编写更好的JavaScript代码,还能帮助调试、理解第三方库,以及真正掌握JavaScript这门语言的核心。
记住:在JavaScript的世界里,对象不是孤岛,它们通过原型链相互连接,共享智慧与能力。