面向对象(四):掌握原型链

223 阅读3分钟

JavaScript 对象系列

面向对象(一):认识对象

面向对象(二):认识JavaScript中对象的原型

面向对象(三):创建多个对象的方案

面向对象(四):掌握原型链

面向对象(五):ES6 类的基本使用

面向对象(六):JavaScript中的7种继承方式

面向对象(七):ES6的class转ES5的源码阅读

第四篇

在前面的一篇文章中,面向对象(二):认识JavaScript中对象的原型,可以很清楚的知道:

  1. 对象基本上都具有隐式原型(__proto__)。
  2. 函数都具有显式原型(prototype)。
  3. 隐式原型与显式原型之间的联系(内存图的表现形式)

理解了上面的三点之后,就可以进入今天的主题:原型链

原型链

所谓原型链,就是多个原型之间组成了一个用于查找规则的顺序,像链条一般。

原型链,也被称为隐式原型链,为什么呢?

 var obj = {}
 obj.__proto__.name = 'copyer'
 console.log(obj.name)
  1. 定义一个对象 obj
  2. 对象都具有隐式原型属性(__proto__),在该对象上添加一个name属性。

在代码块的第三行中,拿取对象属性(name)的属性值,就会触发对象的[[get]]操作。

[[get]]实现大致逻辑: 先找自身对象上是否有这个属性,找到了就返回;如果没有找到,就会去对象的原型(__proto__)上去找,依次类推。

14_01.png

找到 obj的原型发现name属性,就返回属性值,停止查找。

那么考虑一种情况,对象都具有原型对象,即含有属性(__proto__),那么就会出现以下的情况:

obj对象具有原型属性

 obj.__proto__ = {}

obj的原型也是一个对象,那么它也是具有原型的

 obj.__proto__.__proto__ = {}

依次类推(原型都是对象,都有__proto__

 obj.__proto__.__proto__.__proto__ = {}
 ...

所以,当在原型对象上找不属性的时候,又会继续延着它自己原型对象去找。

大致过程是这样的:

 var obj = {}
 obj.__proto__ = {}  // 把obj内部的原型对象重新赋值一个新对象
 obj.__proto__.__proto__ = {}
 obj.__proto__.__proto__.__proto__ = {name: 'copyer'}
 console.log(obj.name) // copyer

14_02.png

这下知道原型链为什么叫做隐式原型链了吧,原型链是按照 __proto__ 属性一直往下找的,所以得名。

如果对象本身和原型上都是不存在该属性的,那么就会永远的一直找下去(就比如递归没有出口一样),那么就会造成内存崩溃。

所以,原型查找应该也是有一个出口,来终止查找。

顶层原型就是这个出口。

顶层原型

定义对象的两种方式:

 var obj = {}  // 字面量
 var obj2 = new Object()  // 构造函数

两种形式都是定义一个对象,从底层方面来说,它们是等价的,都是通过调用 Object() 来实现的。

通过 new 关键调用函数,内部就会实现:

 obj.__proto__ = Object.prototype
 // 把Object原型对象赋值给创建的对象的原型(内部操作)

Object.prototype也是一个对象,那么它也有隐式原型:

 console.log(Object.prototype.__proto__)  // null

打印出来是null,说明顶层对象就是Object原型,到了这里还找不到就会停止查找,从而返回undefined

也可以在 node 环境下打印:

 var obj = {}
 console.log(obj.__proto__) // [Object: null prototype] {}
 // obj.__proto__ = Object.prototype

在 node 环境下,[Object: null prototype] 就是最顶层的原型了。

总结

原型链还是很好理解的,就是一套优先级的查找规则。理解下面两点就行了:

  1. 原型链是按照对象属性__proro__一直查找下去的(被称为隐式原型链)。
  2. 原型链的出口是顶层原型(Object.prototype.__proto__