我们知道,在 JavaScript
中几乎所有数据都是对象(除 null
和 undefined
),而我们想要掌握掌握其面向对象编程模型,那就要从理解原型(Prototype)和原型链(Prototype Chain)开始。
1. 原型(prototype)
根据 ECMAScript 规范(ECMA-262),JavaScript 中的每个对象(Object)都有一个内部槽 [[Prototype]]
,它指向另一个对象或 null
。这个 [[Prototype]]
在 JavaScript 中通过 __proto__
或 Object.getPrototypeOf()
访问。
以下是es规范给的描述
4.4.8 prototype object that provides shared properties for other objects(给其它对象提供共享属性的对象)
1. 显式原型和隐式原型
在我们熟知的原型中除了 prototype
,还有__proto__
现代规范推荐使用 Object.getPrototypeOf()
暴露出来),我们通常称prototype
为显式原型,称__proto__
为隐式原型。
2. 原型的作用
- 共享属性和方法:多个对象实例可以共享同一个原型中的方法和属性,避免重复创建。
- 实现继承机制:JavaScript 通过原型链模拟面向对象语言中的继承。
- 属性查找机制的基础 :当访问一个对象的属性时,如果该对象自身没有,会沿着原型链向上查找,直到找到或为
null
(查找结束)。
3. 原型的重要性
- 是构建对象之间关联和继承的基础。
- 提高内存效率(方法不重复创建)。
- 是理解 JavaScript 面向对象模型的关键。
4. 如何创建和使用原型
4.1 使用构造函数与 prototype
javascript
// 构造函数
function Parent(name) {
this.name = name;
}
// 在 prototype 上添加方法(所有实例共享)
Parent.prototype.sayHello = function() {
console.log(`你好,我是 ${this.name}`);
};
// 创建实例
const p1 = new Parent("小明");
const p2 = new Parent("小红");
p1.sayHello(); // 输出:你好,我是 小明
p2.sayHello(); // 输出:你好,我是 小红
// 验证共享
console.log(p1.sayHello === p2.sayHello); // true(两个实例共享同一方法)
4.2 对象字面量中的原型
javascript
const parent = {
greet() {
console.log("Hello from parent");
}
};
const child = Object.create(parent); // 创建一个以 parent 为原型的对象
child.name = "Child";
child.greet(); // 输出:Hello from parent
console.log(child.__proto__ === parent); // true
4.3 使用 Object.setPrototypeOf()
设置原型
javascript
const proto = { sayHi() { console.log("Hi!"); } };
const obj = {};
Object.setPrototypeOf(obj, proto);
obj.sayHi(); // 输出:Hi!
2. 构造函数、原型对象与实例三者关系
javascript
function Parent(name) {
this.name = name;
}
const p1 = new Parent("小明");
console.log(p1.__proto__ === Parent.prototype); // true
console.log(Parent.prototype.constructor === Parent); // true
Parent.prototype
: 构造函数的原型对象。p1.__proto__
: 实例对象的内部[[Prototype]]
,也就是Object.getPrototypeOf(p1)
。Parent.prototype.constructor
: 指向构造函数本身,默认存在。
2. 什么是原型链
原型链是一种委托机制 ,多个对象通过 [[Prototype]]
层层相连,就形成了一条链式结构,这条链被称为 原型链(Prototype Chain) 。当我们访问对象的属性时,如果对象本身没有,会沿着 [[Prototype]]
一直向上查找,直到找到或到达 null
。
javascript
obj --> obj.__proto__ --> obj.__proto__.__proto__ --> ... --> null
1. 如何创建和使用原型链
1.1 默认的原型链结构(构造函数)
javascript
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello, 我是' + this.name);
};
const child = new Parent('小明');
child.sayHello(); // Hello, 我是小明
child
没有sayHello
方法;- 引擎会在
child.__proto__
(即Parent.prototype
)中查找; - 找到了并执行。
原型链结构展示
javascript
child --> child.__proto__(Parent.prototype) --> child.__proto__.__proto__(Object.prototype )--> null
1.2 使用 Object.create()
手动创建原型链
javascript
const parent = {
greet() {
console.log("我是parent");
}
};
const child = Object.create(parent);
child.name = "child";
child.greet(); // 我是parent
1.3 原型链的终点
原型链的尽头是 Object.prototype.__proto__ === null
。即:所有对象最终都继承自 Object.prototype
。
javascript
console.log(Object.prototype.__proto__); // null
2. 图解原型链
javascript
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello, 我是' + this.name);
};
const child = new Parent('小明');

3. 原型继承与类继承
1. 原型继承(Prototype Inheritance)
1.1 使用构造函数 + 原型对象
JavaScript
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = 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;
Dog.prototype.bark = function () {
console.log(this.name + '旺旺');
};
const dog = new Dog('小狗');
dog.sayName(); // 输出: 我是小狗
dog.bark(); // 输出: 小狗旺旺
1.2 使用 Object.create()
(纯粹的原型继承)
javascript
const animal = {
sayName() {
console.log('我是' + this.name);
}
};
const dog = Object.create(animal);
dog.name = '小狗';
dog.bark = function () {
console.log(this.name + '旺旺');
};
dog.sayName(); // 输出: 我是小狗
dog.bark(); // 输出: 小狗旺旺
2 .类继承(Class Inheritance)
javascript
class Animal {
constructor(name) {
this.name = name;
}
sayName() {
console.log(`我是${this.name}`);
}
}
class Dog extends Animal {
constructor(name, barking) {
super(name); // 调用父类构造函数
this.barking = '旺旺';
}
bark() {
console.log(`${this.name}${this.barking}`);
}
}
const dog = new Dog('小狗', '旺旺');
dog.sayName(); // 我是小狗
dog.bark(); // 小狗旺旺
// 虽然 ES6 引入了 `class`,但本质上仍然是基于原型实现的
console.log(dog.__proto__ === Dog.prototype); // true
console.log(dog.__proto__.__proto__ === Animal.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
console.log(Dog.__proto__ === Animal); // true
console.log(Animal.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
通过以上可以知道他们的异同
对比点 | 原型继承 | 类继承 |
---|---|---|
语法 | 基于函数和原型对象 | 使用 class 与 extends |
是否语法糖 | 否 | 是(本质仍基于原型链) |
构造函数调用方式 | 通过 new ,但无法传参到父构造函数 |
super() 直接调用父构造函数 |
继承内置对象 | 麻烦且容易出错 | 更简单且规范 |
可读性与维护性 | 差一些 | 好很多 |
原型链本质 | 显式设置原型 | 隐式通过 extends 设置原型链 |
4. 如何判断原型关系?
1. instanceof
运算符
javascript
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log('Hello, 我是' + this.name);
};
const child = new Parent('小明');
console.log(child instanceof Parent); // true
2. isPrototypeOf
方法
javascript
console.log(Parent.prototype.isPrototypeOf(child)); // true
5. 手写一个 instanceof
javascript
function myInstanceof(obj, constructor) {
let proto = Object.getPrototypeOf(obj);
const prototype = constructor.prototype;
while (proto) {
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
// myInstanceof(child,Parent) // true
// myInstanceof(Parent,Object) // true
6. 常见误区与陷阱
1. 原型污染与防范
javascript
// 危险:修改原型影响所有对象
Object.prototype.admin = true;
const user = {};
console.log(user.admin); // true
// 防范方案:
// 1. 冻结原型
Object.freeze(Object.prototype);
// 2. 使用无原型对象
const safeDict = Object.create(null);
2. 原型赋值的坑
javascript
function A() {}
A.prototype = {
say() {
console.log("hi");
},
};
const a = new A();
console.log(a.constructor); // Object, 不是 A
解决办法:
javascript
A.prototype.constructor = A; // 手动修复