js 原型链

作者: 木木心丶 | 来源:发表于2020-07-04 22:15 被阅读0次

    什么是原型链?

    我们先看一下下面的这个小例子:

    let data = {
        name:'merit'
    }
    alert(data)
    

    这时候,弹出的窗口里面显示着 [object Object],相信大家应该也都见过这个,可是为什么会是这个呢?

    其实,再用alert显示对象的时候,会默认调用对象的toString函数,可是,我们明明没有给data设置这个函数啊,那这个函数又是哪里来的呢,这时候我们就把整个data给打印一下看一看:

    我们看到,data不止有我们设置的name属性,还有一个我们没设置的__proto__属性,那这属性里面又是些什么东西呢,让我们再打开看一下:

    1593426483110.png

    这时候我们就在里卖弄看到了我们用到的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创建空对象的方式继承,就很好的解决了上面的问题,可以说是一种比较完美的继承方案了。

    相关文章

      网友评论

        本文标题:js 原型链

        本文链接:https://www.haomeiwen.com/subject/akmntktx.html