最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 这一次,彻底理解js原型和继承

    正文概述 掘金(一拳小和尚)   2021-02-18   675

    先讲面向对象

    面向对象是一种编程思想,其重要特征就是多态和继承,通常涉及到类和类和类的实例(对象)、接口等概念。JavaScript中没有类,但是我们可以使用构造函数和原型模拟类的实现。

    js如何实现继承

    说到js的继承,那首先想到的肯定是原型链,实际上js中实现继承就是依赖原型链的机制,那么下面就让我们详细了解一下原型链。

    什么是原型链

    这里总结一下原型链的概念:每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针(__proto__),如果我们让类型A的原型对象prototype等于另一个类型B的实例,那么类型A的实例都有一个指向类型B的实例的指针,同样我们让类型B的原型对象prototype等于另一个类型C的实例,那么类型B的实例都有一个指向类型C的实例的指针,如此实现一个类似链表的结构。 下面我们使用一下原型,观察一下原型链的结构:

    function Dog(name){
       this.name=name;
    }
    Dog.prototype={
      age:13
    }
    let dog=new Dog('tom');
    console.log(dog)
    console.log(dog.age)
    

    输出如下图
    这一次,彻底理解js原型和继承
    我们发现dog对象上没有age属性,但是我们打印dog.age却输出了13,这个值就是我们为函数Dogprototype属性赋值的对象中age的值,同时我们发现dog对象中新增了一个__proto__属性,我们展开它。
    这一次,彻底理解js原型和继承

    没错,它就是我们为Dog.prototype赋值的对象{age:13},当我们访问dog中不存在的age属性时,js内部会查找这个对象的__proto__属性所指向的对象是否包含这个属性,如果存在就返回它;不存在的话呢,就继续访问__proto__所指向的对象上的__proto__属性,一直到最后访问到Object对象,仍然查找不到属性就返回undefined。 为了验证上面的说法,我们修改上面的构造函数如下:

    function Dog(name){
           this.name=name;
           this.age=0;
    }
    

    上面的代码我们直接给Dog构造函数中加上age的赋值操作,输出如下如图: 这一次,彻底理解js原型和继承

    我们发现返回的age变为0了。 那么问题来了,为什么我们新创建的对象上会有一个指向constructor.prototype__proto__属性呢?实际上这个属性是在我们使用new关键字时为对象添加的,因此我们不妨猜想一下new的实现,我们使用new关键字做了如下几件事:

    • 创建对象
    • 以新对象为this执行构造函数
    • 为对象添加__proto__属性
    • 返回创建的对象

    基于以上步骤我们实现一下new关键字,代码如下:

    function New(f,...args){
        //创建一个没有任何属性的对象
        let obj={};
        //在obj对象上执行f函数,即以obj为this执行构造函数
        f.apply(obj,args);
        //为__proto__赋值
        obj.__proto__=f.prototype;
        //返回处理过的对象
        return obj;
    }
    

    这里最后的return语句实际上并不严谨,因为我们的构造函数存在其本身返回一个对象的情况,这是我们不返回新创建的obj而是遵循构造函数的返回,但是这会破坏原型链,实际使用中如果需要使用继承应当避免此操作。 修改后的New函数为:

    function New(f,...args){
        //创建一个没有任何属性的对象
        let obj={};
        //在obj对象上执行f函数,即以obj为this执行构造函数
        let res = f.apply(obj,args);
        //为__proto__赋值
        obj.__proto__=f.prototype;
        //返回处理过的对象
        return typeof res === 'object'?res:obj;
    }
    

    理解了上面的知识,我们就可以依赖这个原型链机制去实现继承了,下面我们依次说一下利用原型链实现js继承的三种方式:

    • 原型继承
    • 组合继承
    • 寄生组合继承

    构造函数继承

    我们先将最简单的构造函数继承

    function SupperType(){
       this.data=[1,2,3];
       this.add=function(num){
           this.data.push(num)
       }
    }
    funciton SubType(name){
       this.name=name; 
       SupperType.apply(this)
    }
    

    构造函数的原理很简单,就是在子类的构造函数中直接调用父类的构造函数即可,但是其存在的问题也十分明显,就是存在函数无法复用的问题,我们每创建一个对象,都要新开辟一块内存来存放一个add函数,但是这个函数实际上可以借助原型来保存复用的,下面我们用原型继承解决这个问题。

    原型式继承

    原型继承就是利用原型链上的属性查找机制共享属性和方法,这里我们把需要共享的方法挂在原型上,私有变量写在构造函数中。

    function SupperType(){
       this.data=[1,2,3];
      
    }
    SupperType.prototype.add=function(num){
       this.data.push(num);
    }
    sub=Object.create(new SupperType());//继承父类
    sub.name='name';//子类属性
    sub.clear=function(){//子类方法
      this.data=[];
    }
    console.log(sub.data);
    

    这里Object.create实际做了如下操作:

    Object.create=function(proto){
       function F(){};
       F.prototype=proto;
       return new F();
    }
    

    原型式继仍然不完美,我们通过把函数都放到原型上,做到了函数复用,但是很明显它不适用与子类也有构造函数的情况。

    组合继承

    组合模式是使用原型对象复用函数,并在子类构造函数中调用父类的构造函数,以实现变量的本地化。

    function SupperType(){
       this.data=[1,2,3];
      
    }
    SupperType.prototype.add=function(num){
       this.data.push(num);
    }
    
    funciton SubType(name){
       this.name=name; 
       SupperType.call(this);
    }
    SubType.prototype=new SupperType();
    
    let sub1=new SubType('sub1');
    let sub2=new SubType('sub2');
    sub1.add(4);
    console.log(sub1.data);//[1,2,3,4]
    console.log(sub2.data);//[1,2,3]
    

    组合继承看起来已经比较完美了,但是还不够完美,我们发现这里我们调用两次父类构造函数,第一次是在在为SubType.prototype赋值时调用的,这次调用在子类的原型上会产生多余的data属性,如果能去掉这些父类中多余的私有属性就完美了,接下来我们使用寄生组合模式来实现。

    寄生组合继承

    寄生组合模式就是借助原型式继承中用到的Object.create函数创建一个只继承父类方法的对象作为子类的原型对象。

    function SupperType(){
       this.data=[1,2,3];
      
    }
    SupperType.prototype.add=function(num){
       this.data.push(num);
    }
    
    funciton SubType(name){
       this.name=name; 
       SupperType.call(this);
    }
    SubType.prototype=Object.create(SupperType.prototype);
    
    let sub1=new SubType('sub1');
    let sub2=new SubType('sub2');
    sub1.add(4);
    console.log(sub1.data);//[1,2,3,4]
    console.log(sub2.data);//[1,2,3]
    

    perfect???

    instanceof

    这里扩展一个与原型链有关的小知识点,instanceof是根据什么判断对象类型的?这也是一个面试问的比较频繁的问题。 我们仍然以?的代码为例:

    function Dog(name){
          this.name=name;
    }
    Dog.prototype={
        age:13
    }
    let dog=new Dog('tom');
    consle.log(dog instanceof Dog)//true
    

    上面的结果会返回true,直觉上我们可能会认为这里是判断了dog对象的构造函数是否是Dog函数,但是我们输出dog.constructor===Dog,结果为false。当然我们仔细思考?的话也确实如此,按照原型链的机制,dog对象上默认没有constructor对象,那么我们就去dog.__proto__上去查找,因为这个原型对象是字面量对象{age:13},我们知道字面量对象都是Object对象,因此不难得出结果dog.constructor===Object这一次,彻底理解js原型和继承

    因此我们判断instanceof并不是直接通过判断对象的构造函数是否等于目标构造函数来出来的,仔细观察我们上面给出的New的实现我们发现构造函数Dogdog对象真正的关联是__proto__prototype,因此我们猜测,instanceOf是通过判断对象的__proto__是否与构造函数的prototype相等来处理的,那么我们实验一下:

        function Dog(name) {
            this.name = name;
        }
        let proto= {
            age: 13
        }
        Dog.prototype = proto;
        let dog = new Dog('tom');
    
        function Cat() {
        }
        console.log(dog instanceof Dog); //true
        console.log(dog instanceof Cat); //false
        Cat.prototype = proto; //Cat的原型对象等于Dog的原型对象
        console.log(dog instanceof Cat); //true
    

    上面代码我们声明了一个Cat函数作为构造函数,并且把Catprototype指向Dog的原型对象,这时我们发现dog instanceof Cattrue,这验证了我们上面的结论:instanceof是通过判断对象的__proto__是否与构造函数的prototype相等来处理的,准确的说是对象的原型链上是否有与要比较的构造函数的prototype相等的原型。即形如obj.__proto__.__proto__.__proto__.......的链式结构上是否能找到与A.prototype相等的原型对象。 按照上面的原理我们可以实现一下instanof:

    function Instanceof(obj,F){
            let O = F.prototype; 
            let proto = obj.__proto__;
            while(proto){
              if(proto === O){
                 return true
              }
              proto = proto.__proto__;
            }
            return false;
    }
    

    总结

    通过这篇文章我们详细介绍了如下几点:

    • 原型链机制
    • 关键字new的实现
    • 原型式继承
    • 组合继承
    • 寄生组合继承
    • instanceof的原理

    如有错误,还望指正~
    如果对你有帮助,请不吝点赞? ~


    起源地下载网 » 这一次,彻底理解js原型和继承

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元