!!!想要搞懂原型链,你一定要打开Chrome,跟着笔者一步一步往下点,你即刻可以掌握
切入正题前,笔者先来定义两个名词,可能会与其他地方叫法不同,但笔者觉得这样更容易理解。
prototype
原形对象__proto__
实例对象
prototype和proto的概念
prototype
prototype是函数的一个属性,指向其构造函数的原型,它是一个对象,我们称之为原形对象。
只有函数有这一个对象,不过不止包括Function,包括自定义函数和内置函数。
所有函数都是Function的实例,Object、Array、Date、Number、String等都是Function构造出来的函数,因此它们都有prototype属性
__proto__
__proto__
是实例对象,指向其构造函数的原形对象。所以实例化的对象都有这一属性,而js中一切皆对象,可以理解为除了null,都有__proto__
属性
注:__proto__
是一个隐形的属性,只能在浏览器内部使用,外部不可见这一属性,但可以通过Object.getPrototypeOf(o)
来获取
prototype和proto的详解
秘籍:想要搞懂原型链,打开Chrome调试,一直点点点进去就明白了
- prototype
function Foo(){}
// 只有函数才有prototype属性
console.log(Foo.prototype)
空函数的prototype只有一个constructor属性,constructor指向构造函数Foo
Foo.prototype.constructor === Foo // true
- proto
proto是经过new的实例所拥有的属性,其指向构造函数的原形对象,即Foo.prototype
var foo = new Foo()
console.log(foo.__proto__)
console.log(foo.__proto__ === Foo.prototype) // true
- 从原型链添加属性
由于proto是一个指针,因此当prototype的值改变的话,proto也能获取改变后的值
Foo.prototype.name = 'cxm'
console.log(Foo.prototype)
console.log(foo.__proto__)
console.log(foo.name)
解释:我们并没有给foo赋予对象,但为什么还是能拿到name的值呢?是因为js访问对象的值的时候会首先去访问当前对象有没有name的属性,如果没有,则通过原型链去找,找proto下的值,找到了即停止,直到null
但实例的foo也添加属性name会怎么样呢?
foo.name = 'mao'
console.log(foo.name)
可以看到name变成了foo的属性,上面我们说过,js去找对象的属性的时候会默认去寻找自身的属性,找不到才会向原型链上找,并且不会改变原型链上的属性。判断是否是自身是属性可以使用obj.hasOwnProperty()
- 从原型链删除属性
当我们把foo上name的属性删除了会怎么样呢?
foo.name = 'mao'
console.log(foo.name) // mao,自身添加的属性
console.log(foo.hasOwnProperty('name')) // true
delete foo.name // 删除自身的属性
console.log(foo.name) // 使用了原型链上的属性
console.log(foo.__proto__)
foo删除自身的属性后,放name为null,则通过proto向原型链找,原型链找到name=‘cxm’,则返回。但这个用obj.hasOwnProperty()
去检测的时候就检测到不是自身的属性了
若在Foo.prototype删除了会怎样呢,毫无疑问,肯定为undefined了
Foo.__proto__
Foo为js对象,是由Function实例化的。那么其必然也有__proto__
属性,那么它的proto属性是指向谁呢?
这里有个诀窍来找,即谁构造它,它就指向谁
那Foo是有Function构造的,其必然指向Function.prototype
,下面来验证一下
console.log(Foo.__proto__ === Function.prototype)
Foo.prototype.__proto__
那Foo.prototype
指向谁呢,还是上面那句话,谁构造它,它就指向谁。
Foo.prototype
是一个对象,对象的构造函数那就是Object,所以下面就有答案了,它肯定指向Object.prototype
从上图我们确实可以看到Foo.prototype
是有proto属性的
console.log(Foo.prototype.__proto__ === Object.prototype) // true
那么Object又是谁构造的呢,Object作为顶级的原型链,它已经是老大哥,没有指向了,它最终指向null
console.log(Object.prototype.__proto__) // null
至此,先总结一波
- 除了
Object.__proro__
指向null外,其他对象的prototype
都指向Object.prototype
。Why?因为prototype
是一个Object
,Object
自然是由Object.prototype
来构造的- 对象的
__proto__
指向其构造函数,即谁生成它,它就指向谁。即谁构造它,它就指向谁。
下面列举一波,大家也可以多到Chrome去调试,更容易掌握原型链
console.log(Number.prototype.__proto__ === Object.prototype) // true
console.log(Function.prototype.__proto__ === Object.prototype) // true
var num = 1
console.log(num.__proto__.__proto__ === Object.prototype) // true
- 给Object.prototype添加属性
给顶级的原型链Object添加属性会怎么样呢?我们知道js去获取对象的属性时是递归向上遍历的
,如果在自身没有找到会向上查找,那么给Object添加一个属性后,即所有js对象都可以访问地到。
Object.prototype.topName = 'cxm'
console.log(Foo.topName) // 'cxm'
console.log(num.topName) // 'cxm'
console.log(foo.topName) // 'cxm'
console.log(String.topName) // 'cxm'
console.log(Date.topName) // 'cxm'
Object.prototype
对象的值
- 给Object.prototype添加方法
跟属性一样,它也是一个属性,只不过这个属性是一个方法,那么在所有的原型链都可以调用到这个方法。
下面给Object.prototype
添加一个叫whoAmI
的方法,用来判断自身是哪一个哪一个对象,非常实用。
Object.prototype.whoAmI = function() {
// this指向调用者,谁调用它,它就指向谁
var str = Object.prototype.toString.call(this)
// [object String],按这个格式截取
var type = str.substring(8, str.length - 1)
console.log(`I am ${type}`)
return type
}
测试
var str = 'cxm'
var num = 123
var bool = true
var obj = {}
var date = new Date()
var promise = new Promise(() => {})
结果如下,所有的类型都可以调用whoAmI
函数,
总结
!!!想要弄懂原型链,打开Chrome,一直点点点即可
prototype
原形对象,可以简化理解为指向本身,比如Foo.prototype的指向就是跟本身有关的(其实是指向构造函数的原形)__proto__
实例对象,可以简化理解为指向它爹,谁构造它,它就指向谁,永远指向上一级。- 除
Object.prototype.__proto__
指向null外,其他的prototype.__proto__
都指向Object.prototype
- 原型链上定义的属性都会被其自身和孩子继承,其孩子也可以定义相同的熟悉,只会覆盖并不会替换
完整的原型链图
终极原型链,最后执行null
console.log(foo.__proto__.__proto__.__proto__) // null
The End
笔者是阅读了网上的许多材料和根据自己的理解来整理的,其中可能会有一些不正确或描述模糊地地方,请大家见谅,也欢迎大家指出。
有任何问题的,欢迎骚扰
微信 | 公众号 | |
---|---|---|
![]() | ![]() | ![]() |