JavaScript 原型与原型链详解:从 prototype 到 __proto__
Introduction
JavaScript 不像 Java/C++ 那样基于类(class-based),而是基于原型(prototype-based)的面向对象语言。每个对象都有一个内部属性 [[Prototype]](浏览器中表现为 __proto__),指向另一个对象,这条引用链条就是原型链。理解原型链是理解继承机制、instanceof 原理、以及为什么修改 prototype 会影响所有实例的关键。
prototype 与 __proto__
/* 构造函数 */
function Person(name, age) {
this.name = name;
this.age = age;
}
/* 在 prototype 上定义方法(所有实例共享)*/
Person.prototype.greet = function() {
return '你好,我是' + this.name + ',今年' + this.age + '岁';
};
/* 通过 new 创建实例 */
var p1 = new Person('小马', 25);
var p2 = new Person('小腾', 30);
/* p1.greet === p2.greet 为 true(共享同一个函数对象)*/
/* 原型链:p1 → Person.prototype → Object.prototype → null */
原型链继承
/* 原型链继承 */
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return 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 上添加方法(优先级高于 Animal)*/
Dog.prototype.bark = function() {
return this.name + '汪汪叫';
};
var dog = new Dog('旺财', '金毛');
dog.speak(); // '旺财发出声音'(继承自 Animal)
dog.bark(); // '旺财汪汪叫'(Dog 自有)
ES6 class 语法糖
/* ES6 class 语法糖:本质还是原型继承 */
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return this.name + '发出声音';
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父构造函数
this.breed = breed;
}
bark() {
return this.name + '汪汪叫';
}
}
var dog = new Dog('旺财', '金毛');
dog instanceof Animal; // true
dog instanceof Dog; // true
Object.getPrototypeOf(dog) === Dog.prototype; // true
instanceof 与 isPrototypeOf
/* instanceof:检查原型链中是否存在某个构造函数的 prototype */
dog instanceof Dog; // true
dog instanceof Animal; // true(原型链上有 Animal)
dog instanceof Object; // true(原型链顶端是 Object)
/* isPrototypeOf:检查某个对象是否在另一对象的原型链上 */
Animal.prototype.isPrototypeOf(dog); // true
Object.prototype.isPrototypeOf(dog); // true
/* 获取对象的原型 */
Object.getPrototypeOf(dog); // Dog.prototype
dog.__proto__; // 同上(已废弃但不常用)
常见问题
- prototype 污染:直接修改 Object.prototype 会影响所有对象,ES5 开始推荐使用 Object.create() 而非 new Object()。
- class 中的方法:class 中的方法定义在 prototype 上,所以所有实例共享同一个函数引用,不会重复创建。
- __proto__ vs Object.getPrototypeOf:__proto__ 是历史遗留的 getter/setter,Object.getPrototypeOf() 是标准方法,推荐使用后者。
- constructor 不可靠:可以通过 prototype 手动修改 constructor,所以 instanceof 更可靠。
- MDN 继承与原型链:MDN 原型链
- You Don't Know JS:this 与对象原型