网站

原型与原型链 - JavaScript | MDN

JavaScript 原型 - W3School

引入

学了对象和构造函数之后,会发现一个问题:每次用 new 创建对象,如果方法写在构造函数里,每个对象都会有一份相同的方法,很浪费内存。

JS 用原型机制解决这个问题:把方法放到原型对象上,所有实例共享同一份。

正文

定义

定义

原型是 JS 实现对象继承和属性共享的机制。每个函数都有一个 prototype 属性,每个对象都有一个 __proto__ 指向它的构造函数的原型。

prototype 属性

每个函数都有一个 prototype 属性,它是一个对象。

function User(name) {
  this.name = name;
}
 
console.log(User.prototype);

用构造函数创建的对象,会自动链接到构造函数的 prototype

__proto__ 属性

每个对象都有一个 __proto__ 属性,指向它的构造函数的原型。

function User(name) {
  this.name = name;
}
 
let user = new User("Tom");
 
console.log(user.__proto__ === User.prototype);

这就是实例和原型之间的链接关系。

在原型上添加方法

把方法放到 prototype 上,所有实例都能共享。

function User(name) {
  this.name = name;
}
 
User.prototype.sayHello = function () {
  console.log("你好,我是" + this.name);
};
 
let user1 = new User("Tom");
let user2 = new User("Jack");
 
user1.sayHello();
user2.sayHello();

两个对象共享同一个 sayHello 方法,不会重复创建。

原型链

当访问对象的属性时,JS 会按照这个顺序查找:

  1. 先在对象自己身上找
  2. 找不到就去 __proto__ 指向的原型上找
  3. 还找不到就继续沿着原型的 __proto__ 往上找
  4. 直到找到 Object.prototype,它的 __proto__null

这条查找链就叫原型链

function User(name) {
  this.name = name;
}
 
User.prototype.sayHello = function () {
  console.log("你好,我是" + this.name);
};
 
let user = new User("Tom");
 
console.log(user.name);
user.sayHello();
console.log(user.toString());
  • user.nameuser 自己身上找到
  • user.sayHello()User.prototype 上找到
  • user.toString()Object.prototype 上找到

原型链的终点

所有对象的原型链最终都会指向 Object.prototype

Object.prototype.__proto__null,这就是原型链的终点。

console.log(Object.prototype.__proto__);

属性和方法的查找

对象自己的属性和原型上的属性都能访问到,但如果同名,对象自己的属性会优先。

function User(name) {
  this.name = name;
}
 
User.prototype.name = "默认名";
 
let user = new User("Tom");
 
console.log(user.name);

user 自己有 name 属性,所以不会去原型上找。

instanceof 运算符

instanceof 用来检查对象的原型链中有没有某个构造函数的原型。

function User(name) {
  this.name = name;
}
 
let user = new User("Tom");
 
console.log(user instanceof User);
console.log(user instanceof Object);

userUser 的实例,也是 Object 的实例,因为所有对象都继承自 Object

常见写法

写法作用
Func.prototype访问构造函数的原型对象
obj.__proto__访问对象的原型(学习用)
Object.getPrototypeOf(obj)获取对象的原型(推荐)
obj instanceof Func检查原型链中有没有某个构造函数的原型
Func.prototype.method = fn在原型上添加方法

特点

  • 每个函数都有 prototype 属性
  • 每个对象都有 __proto__,指向构造函数的原型
  • 属性查找沿着原型链一层层往上
  • 原型链终点是 Object.prototype.__proto__,值为 null
  • 对象自己的属性优先级高于原型上的属性
  • instanceof 检查的是原型链,不是构造函数本身

理解

可以把原型链理解为找爸爸

你自己没有的东西,去问你爸(__proto__)。

你爸也没有的,去问你爷爷(你爸的 __proto__)。

一直往上找,找到最老的祖先(Object.prototype)还没有,那就是真没有(undefined)。

prototype 是每个家庭的家产,家里所有孩子都能用。__proto__ 是每个孩子指回自己家的路。

实际开发中不需要太纠结原型链的底层细节,只要知道:构造函数用 prototype 共享方法,instanceof 检查继承关系就够了。

引出

理解了原型和原型链之后,可以继续学习 JS 自定义对象,看看用不同方式创建对象时原型关系是怎样的。

也可以学习 JS 类,ES6 的 class 语法本质上还是基于原型的,只是写法更清晰。