JavaScript 对象系列:
第四篇
在前面的一篇文章中,面向对象(二):认识JavaScript中对象的原型,可以很清楚的知道:
- 对象基本上都具有隐式原型(
__proto__)。 - 函数都具有显式原型(
prototype)。 - 隐式原型与显式原型之间的联系(内存图的表现形式)
理解了上面的三点之后,就可以进入今天的主题:原型链。
原型链
所谓原型链,就是多个原型之间组成了一个用于查找规则的顺序,像链条一般。
原型链,也被称为隐式原型链,为什么呢?
var obj = {}
obj.__proto__.name = 'copyer'
console.log(obj.name)
- 定义一个对象
obj。 - 对象都具有隐式原型属性(
__proto__),在该对象上添加一个name属性。
在代码块的第三行中,拿取对象属性(name)的属性值,就会触发对象的[[get]]操作。
[[get]]实现大致逻辑: 先找自身对象上是否有这个属性,找到了就返回;如果没有找到,就会去对象的原型(__proto__)上去找,依次类推。
找到 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
这下知道原型链为什么叫做隐式原型链了吧,原型链是按照 __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] 就是最顶层的原型了。
总结
原型链还是很好理解的,就是一套优先级的查找规则。理解下面两点就行了:
- 原型链是按照对象属性
__proro__一直查找下去的(被称为隐式原型链)。 - 原型链的出口是顶层原型(
Object.prototype.__proto__)