六维教程

JavaScript 原型是什么

原型(prototype)是 JavaScript 里最让新人头疼的概念之一,很多资料一上来就讲很深。这一篇用入门深度把它讲清楚,只需要记住三件事,每个函数都有 prototype,每个对象都有 proto,实例找属性会沿着原型链从下往上找。

每个函数都有 prototype

在 JavaScript 里函数也是对象,而且每个函数天生自带一个 prototype 属性,它指向一个普通对象。

function User(name) {
  this.name = name;
}

console.log(typeof User.prototype); // object
console.log(User.prototype);        // 一个普通对象,目前是空的

注意普通对象没有 prototype 属性,只有函数有。这个 prototype 属性的作用,就是为将来 new 出来的实例准备一个”共享仓库”,下一篇会用它共享方法。

每个对象都有 proto

每个对象身上还有一个隐藏的属性 proto(前后各两个下划线),用来指向自己的”原型”。浏览器里可以直接访问 proto,但规范更推荐用 Object.getPrototypeOf() 获取。

function User(name) {
  this.name = name;
}

const user1 = new User('张三');

console.log(user1.__proto__);              // 可以直接访问
console.log(Object.getPrototypeOf(user1)); // 推荐写法

最关键的规律是,实例的 proto 指向它的构造函数的 prototype,可以用全等比较验证。

function User(name) {
  this.name = name;
}

const user1 = new User('张三');

console.log(Object.getPrototypeOf(user1) === User.prototype); // true

一句话总结三者的关系,User.prototype 是挂在函数身上的对象,实例 user1 通过自己的 proto 指向它。构造函数、实例、原型三者靠这条线连在一起。

原型链是什么

访问对象的属性时,JavaScript 不是只在自己身上找,而是按下面的顺序找。

  1. 先在自己身上找,找到了就直接用。
  2. 找不到,就去 proto 指向的原型上找。
  3. 还找不到,就顺着原型的 proto 继续往上找。
  4. 一路找到 null 都没有,就返回 undefined。

这条从下往上一级一级找的链条,就是原型链。

function User(name) {
  this.name = name;
}
User.prototype.city = '北京';

const user1 = new User('张三');

console.log(user1.name); // 张三,自己身上的属性
console.log(user1.city); // 北京,自己身上没有,顺着 __proto__ 在 User.prototype 上找到
console.log(user1.age);  // undefined,一路找到 null 也没有

常用方法来自哪

平时用的数组和对象方法,其实都不是数组对象自己身上带的,而是沿原型链从原型上”借”来的。比如 push 方法住在 Array.prototype 上,每个数组实例的 proto 都指向 Array.prototype,所以每个数组都能用 push。

const arr = [];
console.log(arr.push);                                 // 一个函数,方法存在
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true

const obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
console.log(obj.toString); // 对象的方法来自 Object.prototype

这么看,JavaScript 数组基本操作 里用的 push、join 等方法,本质都是实例沿原型链找到的共享方法。这个特性很关键,下一篇会利用它自己给实例”发方法”。

给原型添加属性

直接给 User.prototype 添加属性,所有实例立刻都能访问到。

function User(name) {
  this.name = name;
}
User.prototype.city = '北京';

const user1 = new User('张三');
const user2 = new User('李四');

console.log(user1.city); // 北京
console.log(user2.city); // 北京

user1 和 user2 自己身上都没有 city,但都能通过 proto 找到 User.prototype 上的 city。这就是原型共享能力的来源,下一篇就讲怎么用这个能力共享方法。

小结

  • 每个函数都有 prototype 属性,它是一个普通对象。
  • 每个对象都有 proto,推荐用 Object.getPrototypeOf() 获取。
  • 实例的 proto 指向构造函数的 prototype。
  • 属性查找沿原型链从下往上,找不到就返回 undefined。
  • 数组和对象的常用方法都来自原型,比如 Array.prototype 上的 push。
  • 往 prototype 上添加属性,所有实例都能访问到。
上一篇
JavaScript 构造函数与 new
下一篇
JavaScript 原型共享方法