什么是原型链?
我们先看一下下面的这个小例子:
let data = {
name:'merit'
}
alert(data)
这时候,弹出的窗口里面显示着 [object Object],相信大家应该也都见过这个,可是为什么会是这个呢?
其实,再用alert显示对象的时候,会默认调用对象的toString函数,可是,我们明明没有给data设置这个函数啊,那这个函数又是哪里来的呢,这时候我们就把整个data给打印一下看一看:
我们看到,data不止有我们设置的name属性,还有一个我们没设置的__proto__属性,那这属性里面又是些什么东西呢,让我们再打开看一下:
这时候我们就在里卖弄看到了我们用到的toString函数。
上面这个是直接创建了一个Object实例对象,下面让我们用构造函数创建一个对象,看看是什么效果:
function MyObj(name){
this.name = name
}
MyObj.prototype.myToString = function(){
console.log(this.name)
}
let bar = new MyObj('merit')
alert(bar)
结果显然是和上面的一样为 [object Object],我们来看一看这个的toString方法是怎么得到的:
我们可以看到,bar这个对象本身有一个__proto__属性,这个属性里面竟然还有我们通过MyObj.prototype.myToString定义的函数,这个我们一会儿再说。然后在这个__proto__属性中,还有一个__proto__属性,里面有着我们所需要的toString函数。
当我们调用对象的某个属性的时候,就会先去看它本身有没有这个属性,没有的话,就会去看他的__proto__中有没有这个属性,有的话就调用,没有的话就继续看这个__proto__中的__proto__里面有没有,只到最后一层,如果都没有的话,才会报错。
上面这个例子中,如果把myToString函数名换成toString,那么alert(bar)再调用bar.toString就是我们自己写的内容了。
这种通过__proto__属性一层层的连接形成了一个链的样子就叫做原型链。
prototype与_proto_的关系
当我们创建一个对象的实例的时候,实例本身就会自带一个__proto__属性,该属性指向其构造函数的prototype属性,而所有构造函数的prototype属性都是一个Object的一个实例。而这个实例就会有一个指向Object.prototype的_proto__,所有对象类型无论是Array,String还是Function,最终的__proto__指向都是这个位置。
明白了原型链,其实就能够理解很多的东西了,比如我们经常使用的instanceof判断类型和实现继承的一些方式。
instanceof
我们经常使用instanceof去判断一个数据的数据类型,其实A instanceof B的本质就是去判断A这个对象的原型链上是否有B.prototype。这样你就能很好的明白下面的一些结果了:
let bar = 'merit'
let bar_s = new String('merit')
let bar_a = [1,2,3]
let bar_f = function(){console.log(1)}
bar instanceof Object //false
bar_s instanceof String //true
bar_a instanceof Array//true
bar_f instanceof Function//true
//任何对象类型的原型链终点都指向到Object的prototype
String instanceof Object//true
Array instanceof Object//true
Function instanceof Object //true
Object instanceof Object //true
Object instanceof Function //true
String instanceof Function//true
Array instanceof Function //true
Function instanceof Function //true
//对于最后一组,我们要知道{},[]创建对象或数组都是new Object()/new Array()的语法糖,而Object,Array本身这些构造器,又都是Function的一个实例。
既然对instanceof的原理理解了,我们也可以简单的写一个简易版的instanceof
//myInstanceof
function myInstanceof(A,B){
while(A.__proto__){
if(A.__proto__===B.prototype){
return true
}
A=A.__proto__
}
return false
}
继承
通过对原型链的理解,我们就可以很好的实现继承了。
function Parent(value){
this.tag='我是父对象'
this.val = value
}
Parent.prototype.showVal = function(){console.log(this.val)}
组合继承
function Child(value,name){
Parent.apply(this,value) //通过这一步可以保证子对象里面有父对象的值
this.tag = '我是子对象' //会覆盖掉父对象的赋值
this.name = name;
}
//接下来我们要保证子对象能调用父对象的方法
Child.prototype = new parent()
Child.prototype.constructor = Child
let bar = new Child('111','merit')
虽然我不知道为什么这个名字叫做组合对象,但是原理还是很明显的,就是让子对象的prototype指向父对象的一个实例,然后利用这个实例的__proto__去调用父对象的方法,下面一张图应该能很详细的说明这一点
通过上面这个图,我们可以看出子对象的一个实例bar是如何调用父对象的方法的,但是我们也能够看出来一个问题,就是通过这种构建一个父对象的实例在中间进行过渡的方式,会造成内存的浪费,因为我们并不会使用到父对象里面的值,这时候我们就可以通过下面的继承方式来改善
寄生组合继承
function Child(value,name){
Parent.call(this,value)
this.tag='子对象'
this.name = name
}
//这个会创造一个__proto__指向Parent.prototype的空对象
let childPrototype = Object.creat(Parent.prototype)
childPrototype.constructor = Child
Child.prototype = childPrototype
通过创建父对象实例而是通过Object创建空对象的方式继承,就很好的解决了上面的问题,可以说是一种比较完美的继承方案了。