最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript原型链系列3: 继承和属性屏蔽

    正文概述 掘金(争霸爱好者)   2021-03-06   507

    《JavaScript高级程序设计》中提到过多种继承方法:原型链继承,借用构造函数,组合式继承,原型式继承等等,方式很多,让人傻傻 分不清楚。那么,这篇文章将从宏观角度对这些继承方式做一个梳理。

    我个人认为,JS继承从继承手段上可以分为三类:

    • 拷贝式继承
    • 修改prototype
    • 修改__proto__指针

    拷贝式继承

    在我的这篇文章中提到过类似的概念,通过将父类的属性和方法拷贝到子类实例上来实现继承,在js中的实现方式就是借用构造函数。可以看下面的例子

    function Father(a, b) {
      this.a = a
      this.b = b
      this.fn = function() {}
    }
    
    const f = new Father(1, 2)
    
    function Son(aa, bb) {
      Father.call(this, 10, 20)
      this.aa = aa
      this.bb = bb
    }
    
    const s1 = new Son(11, 22)
    const s2 = new Son(33, 44)
    

    红宝书中也提到过,这种方法的问题在于每个子类实例上都有父类属性的一份独立拷贝,浪费空间,此外,各个子类上的属性和方法是独立的,不能实现函数的重用

    // s1和s2两个实例的方法不是同一个引用
    s1.fn === s2.fn // false
    

    这种借用构造函数的继承方式实质上是一种对父类属性的拷贝,可以理解为一种拷贝式继承。

    修改prototype

    第二种继承方式是修改构造函数的prototype属性,在js中,这种方式有两种实现方法

    1. Son.prototype = new Father()

    这种方式被称为原型链继承

    function Father() {
      this.a = 'father'
    }
    
    Father.prototype.b = 'father prototype'
    
    function Son() {
      this.c = 'son'
    }
    
    Son.prototype = new Father() // 修改了Son.prototype
    
    const son = new Son()
    

    可以看出,子类构造函数的prototype属性被重写了,该属性被重写为一个Father实例,这样就实现了继承。

    JavaScript原型链系列3: 继承和属性屏蔽

    我们要注意一点,由于用父类的实例重写了子类构造函数的prototype属性,因此子类构造函数的prototype是有父类的实例属性的,因此一个子类实例能够访问子类的实例属性,父类的实例属性和父类的原型属性。此外,由于子类构造函数的prototype被重写,因此contructor属性会失真。

    2. Object.create()

    function Father() {
      this.a = 'father'
    }
    
    Father.prototype.b = 'father prototype'
    
    function Son() {
      this.c = 'son'
    }
    
    Son.prototype = Object.create(Father.prototype)
    
    const son = new Son()
    

    从上面的代码可以看出,Object.create()和传统的原型链方式都通过修改prototype来实现继承,但是区别在于,Object.create(Father.prototype)创造的对象是一个空对象,没有任何属于自身的属性,这个空对象继承自Father.prototype,并用这个空对象重写了Son.prototype

    JavaScript原型链系列3: 继承和属性屏蔽

    这种方式与原型链继承的区别在于,子类的prototype不再是父类的实例,因此子类实例不再能访问父类的实例方法。此外,由于prototype被重写,因此子类的contructor也会失真。

    如果直接使用Object.create来创建一个对象,其实也有一个重写prototype属性的过程,这个方法的pollyfill如下

    if (!Object.create) {
      Object.create = function(proto) {
        function F(){}
        F.prototype = proto // 对中间函数prototype的重写
        return new F()
      }
    }
    

    修改__proto__

    ES6后,我们可以使用Object.setPrototypeOf来修改__proto__属性,通过该方法,我们可以直接让子类原型继承自父类原型,不需要重写子类原型对象,因此constructor也不会失真。

    JavaScript原型链系列3: 继承和属性屏蔽

    ES6 extends

    看完了前面几种基础的继承方式,我们来看看ES6中的extends关键字

    class Father {
      constructor() {
        this.name = 'Father'
      }
      static log() {
        console.log('static', this.name)
      }
      say() {
        console.log('method', this.name)
      }
    }
    
    class Son extends Father {
      constructor() {
        super()
        this.name = 'Son'
      }
    }
    
    const son = new Son()
    

    一个简单的extends,里面有三种继承关系

    1. 实例属性继承:首先是在子类中调用super(),这与拷贝式继承对应(不完全相同)
    2. 原型方法的继承:我们在子类实例中能够调用父类中定义的原型方法,是两个prototype之间的继承,Son.prototype = Object.create(Father.prototype)
    3. 静态方法的继承:可以通过子类直接调用父类中的静态方法,是两个函数之间的继承:Object.setPrototypeOf(Son, Father)

    属性屏蔽

    其实说到继承,就不得不提一下属性屏蔽的问题,但是很多博客上并没有提到过这部分内容。

    我们都知道,在查找一个对象的某个属性时,如果源对象没有目标属性,就会沿着__proto__链,一直向上查找。在读属性时,属性屏蔽很容易理解:对象上的属性会屏蔽原型上的同名属性,但是在写属性时情况就比较复杂了。先来看一个问题吧:

    let proto = {
      num: 1
    }
    
    let obj = Object.create(proto)
    
    obj.num++
    
    console.log(obj)
    console.log(proto)
    

    答案是obj: { num: 2 }, proto: { num: 1 },你答对了吗?

    如果没有专门了解过这部分的知识的话,这道题很容易出错。很多人可能会认为,读取的是原型上的属性,那么赋值是也就会修改原型上的属性,其实没有这么简单,属性屏蔽符合下面的规则(以下源对象指obj,原型对象指proto):

    1. 如果源对象有目标属性,无论该属性是否可写,直接修改该属性
    2. 如果源对象没有目标属性
      1. 如果原型上也没有该属性,直接在源对象上新增该属性
      2. 如果原型上有该属性
        1. 如果该属性为数据属性(不是gettersetter)
          1. 如果该属性可写,就在源对象上添加该属性
          2. 如果该属性不可写,赋值语句静默失败
        2. 如果该属性为访问器属性,直接调用setter

    在来分析一下上面的题目,源对象obj上没有num属性,但是原型对象proto上有num属性,并且该属性可写,因此就会在源对象obj上添加该属性(注意,num++相当于num = num + 1),在读取num时会查找proto.name,之后加1,因此obj.num变为2


    起源地下载网 » JavaScript原型链系列3: 继承和属性屏蔽

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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