JS原型以及原型链,一直以来都是重中之重,任何接触过JS这门语法的人,都知道,任何新建的对象之间都存在着继承关系,追根溯源之后都能走到原型链之上,那么什么是原型链,如何使用好原型链呢?
首先需要知道的一点就是:是不是所有的类型都有原型链?
从学习JS以来,我们就知道JS中有2大类型,一种是基础类型,一种是引用类型
基础类型分为:Number、Boolean、String、null、undefined、symbol(ES6中加入)
引用类型分为:Object、Function
首先,在这里先明确一个概念:
并不是所有的类型都有原型链,基础类型是没有原型链的,因为基础类型是存放在栈中的,比较的时候仅仅就是一个值类型的比较,根本无需追根朔源
而引用类型是有原型链的,因为引用类型的引用变量名称是存放在栈中(方便快速定位对象内存空间的地址),实际的对象存储空间其实是存储在堆中的。这里其实也可以涉及到浅拷贝和深拷贝的知识点,但由于这里主讲原型链,就先不进行扩展了(当你了解了原型链之后,浅拷贝和深拷贝其实就是被剥了壳的鸡蛋,一览无余)。
所以说,以后千万不要一说到原型链,就开始泛泛而谈,首先应该明确目标,只有引用类型才有原型链,基础类型是没有原型链的。
既然对象明确了,那么我们来进行初次接触:
1.当前对象不存在这个属性,但父类存在
从上图可以看到,打印person对象,会person对象中存在一个__propt__属性,这个属性的值是一个对象,那些或多或少的小伙伴们,如果接触过原型的操作,一定不会陌生如何往原型中注入属性age属性(Object.prototype.age = 18),对比persony原型前后的改变:
显然,我们并没有直接对person对象进行操作(如使用person.age=18)。在这里我们是对Object这个对象的原型进行操作,可能你会疑惑为什么对Object对象的原型注入的age属性为什么能在person对象上显示出来呢?其实这里就已经涉及到原型链的知识了,且看下图:
打开这个__proto__这个对象的constructor(构造函数),你就会发现在person对象构造函数的属性中已经添加了age:18,初次接触会感觉多么的神奇,为什么我在Object原型上注入的属性,竟然莫名其妙的跑到了person对象的构造函数上来了。
这里先给出结论:当新创建了对象(person)后,如果没有在对象上绑定相应属性(如这里的age属性,并没有直接绑定在新创建的person对象上),那么此时的对象person为了获取这个属性,就会去查找构造函数上的propotype属性,(也就是父类的propotype属性(Object.prototype)),请看下图:
首先,输出person.__proto__属性的内容
随后打开constructor函数的prototype属性
最后输出Object的prototype属性,会发现两者都有age这个属性
2.当前对象不存在这个属性,连父类也不存在
看到这里上面就可以明白了,如果我们去查找一个对象的属性,但是从当前这个对象无法直接获取的时候,这个时候当前的对象就会通过__proto__属性去查找父类的prototype属性,如果父类的prototype属性仍然找不到,那么这个父类就会继续查找它的父类,直到最顶层的Object对象,如果仍然找不到,那么就为null,此时就会停止查找,然后报错,如下:
可以看到,如果我们没有在person对象的原型以及父类(Object对象)上添加color属性时,会直接显示undefined;
3.当前对象存在这个属性,父类也存在这个属性
基于上面的基础,我们在person对象上添加属性age,如下:
可以看到,如果当前对象(person)上存在这个属性,那么会直接输出绑定在当前对象上的属性值,当然,还有一种情况就是当前对象存在这个属性,但是父类不存在,那么很好理解,既然当前对象都存在这个属性了,必然是不会去通过__proto__属性去查找父类的prototype属性的。
这里加个例子方便大家了解,如果你(当前对象son)现在没有钱(属性)了,那么就去找你爸爸(Father父类)要钱,如果你爸爸也没钱,就去找你爷爷(Grandpa爸爸的父类)要,如果仍然没有钱,那就哦豁,真的没了(null),图例如下:
最终总结:查找一个对象的属性值,如果当前对象存在这个属性值,就直接为当前对象的属性值,如果当前对象值不存在这个属性,那么当前对象就默认通过__proto__这个属性去查找父类的prototype属性,如果父类仍然查找不到这个属性,继续往上查找,直到连Object对象也不存在这个属性值,那么就会返回undefined
小细节:
为什么值类型没有原型链这个概念?因为值类型连父类都没有,就是表示单纯的值,所以没有__proto__这个属性去通过构造函数查找父类的prototype属性,所以肯定不存在原型链继承这个概念了。
此外,一定要注意ES6中的箭头函数,因为它没有继承Function函数,因此没有call、apply、bind这类的方法。