彻底理解js面向对象之继承


Posted in Javascript onFebruary 04, 2018

说道这个继承,了解object-oriented的朋友都知道,大多oo语言都有两种,一种是接口继承(只继承方法签名);一种是实现继承(继承实际的方法)

奈何js中没有签名,因而只有实现继承,而且靠的是原型链实现的。下面正式的说一说js中继承那点事儿

1、原型链

原型链:实现继承的主要方法,利用原型让一个引用类型继承另一个引用类型的属性和方法。

回顾:构造函数,原型,实例三者的关系

每一个构造函数都有一个原型对象(Person.prototype);原型对象都包含指向构造函数的指针(constructor);每个实例都包含指向原型对象的指针(看不见的_proto_指针)

原型链是怎么来的呢?

某个构造函数的原型对象是另一个构造函数的实例;这个构造函数的原型对象就会有个(看不见的_proto_指针)指向另一个构造函数的原型对象;

那么另一个原型对象又是其他的构造函数实例又会怎么样,就这样层层递进,形成原型链;来具体看一下吧

//第一个构造函数;有一个属性和一个原型方法
    function SuperType(){
        this.property=true;
    } 
    
    SuperType.prototype.getSuperValue=function(){
        return this.property
    }


    //第二个构造函数;目前有一个属性
    function SubType(){
        this.subproperty=false
    }
    
    //继承了SuperType;SubType原型成了SuperType的实例;实际就是重写SubType的原型对象;给SuperType原型对象继承了
    SubType.prototype=new SuperType()
    
    //现在这个构造函数有两个属性(一个本身的subproperty,一个继承的存在原型对象的property);两个方法(一个原型对象的getSubValue,一个原型对象的原型对象的getSuperValue)
    SubType.prototype.getSubValue=function(){
        return this.subproperty
    }
    
    var instance=new SubType()  //创建第二个构造函数的实例

    console.log(instance.getSuperValue())  //true 先查找instance这个实例有没有此方法;显然没有,再查找SubType原型对象有没有此方法;也没有,再查找SubType原型对象的原型对象;显然是存在的

注意:instance的constructor现在指向的是SuperType这个构造函数;因为原来的SubType.prototype被重写了,其内部的constructor也就随着SubType.prototype的原型对象的constructor指向构造函数SuperType;至于原型搜索机制是怎么样运行的,请仔细看上面的代码,相信你是可以的

1.1完整的原型

在原型那节已经提了些,还是再说一下。完整的原型包括Object。

所有函数的默认原型都是Object的实例;每个默认原型都有个_proto_指针指向Object.prototype;因此自定义类型都继承如toString,valueOf的方法

而Object.prototype的_proto_指针指向null来结束原型链。以Person构造函数为例,看看完整的原型链图

彻底理解js面向对象之继承

1.2原型和实例的关系判断

第一种使用instanceof操作符: 测试实例和原型链中出现的构造函数,结果为true

第二种使用isPrototypeOf()方法: 只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型

console.log(instance instanceof Object)   //都为true
    console.log(instance instanceof SuperType)
    console.log(instance instanceof SubType)

   
    console.log(Object.prototype.isPrototypeOf(instance)) //都为true
    console.log(SuperType.prototype.isPrototypeOf(instance))
    console.log(SubType.prototype.isPrototypeOf(instance))

1.3谨慎定义方法

注意:给原型对象添加方法,一定放在替换原型的后面,因为放在替换原型之前是找不到了,原型会被重写的;

注意:在通过原型链继承时,不能使用对象字面量创建原型方法,因为也会重写原型链;

function SuperType(){
        this.property=true;
    } 
    
    SuperType.prototype.getSuperValue=function(){
        return this.property
    }
    
    function SubType(){
        this.subproperty=false
    }
    
    //继承SuperType
    SubType.prototype=new SuperType()
    
    //使用字面量添加新方法,导致上一行无效   因为现在的原型替换了Object实例而非SuperType的实例,关系中断
    SubType.prototype={
       getSubValue:function(){
           return this.subproperty;
       },
       somOtherMethod:function(){
           return false
       }
    };

    var instance=new SubType()
    console.log(instance.getSuperValue())  //error

1.4原型链的问题

1、包含引用类型值的原型:当实例是另一函数的原型时,引用类型值就会变成原型上的属性,就会被另一函数的实例所共享。

function SuperType(){
       this.colors=["yellow","red","olive"]
    }

    function SubType(){
    }

    SubType.prototype=new SuperType()  //color实际上就是原型上的了

    var instance1=new SubType()
    instance1.colors.push("purple")
    var instance2=new SubType()

    console.log(instance1.colors==instance2.colors)  //true

2、创建子类型实例时,不能向超类型的构造函数传递参数(没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数)

2、借助构造函数

为了解决原型中包含引用类型值带来的问题,利用构造函数来解决

在子类型构造函数的内部调用超类型构造函数(函数是特定环境中执行代码的对象,可以通过apply或call调用)

function SuperType(){
        this.color=["yellow","red","olive"]
    }

    function SubType(){
        //继承了SuperType
        SuperType.call(this)
    }

    var instance1=new SubType()
    instance1.color.push("purple")
    var instance2=new SubType()

    console.log(instance1.color)  //["yellow","red","olive","purple"]
    console.log(instance2.color)  //["yellow","red","olive"]


    //传递参数
    function SuperType(name){
       this.name=name
    }
    function SubType(){
        SuperType.call(this,"double")
        this.age=12
    }

    var instance1=new SubType()
    console.log(instance1.name)  //double
    console.log(instance1.age)  //12

问题:仅仅借鉴构造函数,那么避免不了构造函数的问题,方法都在构造函数定义了,函数无法复用

3、组合继承(常用的还是组合,和原型与构造结合一样)

function SuperType(name){
        this.name=name;
        this.color=["yellow","red","olive"];
    }

    SuperType.prototype.sayName=function(){
        console.log(this.name);
    }
 
    function SubType(name,age){
        //继承属性,创建属性副本
        SuperType.call(this,name);
        this.age=age;
    }
    
    //继承属性和方法,只是原型中属性被后来的函数调用生成的属性副本遮盖
    SubType.prototype=new SuperType();

    alert(SubType.prototype.constructor)  //指向的是SuperType

    SubType.prototype.constructor=SubType; //将constructor回归到SubType构造函数身上
    SubType.prototype.sayAge=function(){
        console.log(this.age)
    }
    
    
    var instance1=new SubType("double",23)
    instance1.color.push("pink")
    console.log(instance1.color)     //["yellow","red","olive","pink"]
    instance1.sayName()         //double
    instance1.sayAge()          //23

    var instance2=new SubType("single",34)
    console.log(instance2.color)     //["yellow","red","olive"]
    instance2.sayName()         //single
    instance2.sayAge()          //34

还有其他的继承,花点时间写一下

1、原型式继承

克罗克福德写的;借助原型可以基于已有的对象创建新对象,同时不必创建自定义类型

function object(o){      //本质上object()函数对其中对象的浅复制
        function F(){}      //创建一个新的构造函数
        F.prototype=o      //构造函数原型为传入的对象
      return new F()      //返回构造函数的实例
    }

    var person={
        name:"double",
        friends:["tom","jack","mike"]
    }

    var person1=object(person)   //事实上为原型共享
    person1.name="grey"
    person1.friends.push("single")
    
    console.log(person1.friends)  //["tom", "jack", "mike", "single"]

    var person2=object(person)
    person2.name="red"
    console.log(person2.friends)   //["tom", "jack", "mike", "single"]

ES5为了规范原型式的继承,有个Object.create()来方便,IE9以上可以;只是想一个对象和另一个对象保持类似的情况,完全可以这种方法

var person={
        name:"double",
        friends:["tom","jack","mike"]
    }

    var person1=Object.create(person)
    person1.name="single"
    person1.friends.push("singles")

    var person2=Object.create(person)

    console.log(person1.friends==person2.friends) //true

    //Object.create()接受两个参数,一个为作为新对象原型的对象,一个为新对象定义额外属性对象
    var person={
        name:"double",
        friends:["tom","jack","mike"]
    }

    var person1=Object.create(person,{
         name:{ 
            value:"single"  //每个属性都是通过自己描述符定义的
         }
    })

2、寄生式继承

思路和原型式继承一脉相承,创建一个用于封装继承过程的函数,内部通过方式增强对象,返回对象;主要考虑对象时使用

function object(o){
       function F(){}
       F.prototype=o
       return new F()
    }

    function createPerson(original){
       var clone=object(original)   //继承原型
       clone.sayName=function(){ 
           alert("name")
       }
       return clone
    }

    var person={
       name:"double",
       friends:["single","tom","jack"]
    }

    var person1=createPerson(person)
    person1.sayName()  //name   引用类型值还是共享的

3、寄生组合继承

组合继承是继承中常常用到的,但是会调用两次超类型构造函数;寄生组合继承就是为了解决这个问题的

function object(o){
     function F(){}
     F.prototype=o
     return new F()
  }


  function inheritPrototype(subType,superType){
     var prototype=object(superType)    //创建对象  (superType实例)
     prototype.constructor=subType     //增强对象
     subType.prototype=prototype      //指定对象  (原型赋予实例)
  }
  

   function SuperType(name,sex){
      this.name=name
      this.sex=sex
      this.colors=["red"]
   }

   SuperType.prototype.sayName=function(){
     alert(this.name)
   }

   function SubType(name,sex,age){
      SuperType.call(this,name,sex)
      this.age=age
   }


   inheritPrototype(SubType,SuperType)    //目前subType.prototype什么都没有
   SubType.prototype.sayAge=function(){   //为subType.prototype添加个方法
      alert(this.age)
   }

   var person1=new SubType("double","man",34)
   console.log(person1.name)  //SuperType 这是个Bug
   console.log(person1.sex)   //man
   console.log(person1.colors) //["red"]
   person1.sayAge()       //34

到此,差不多结束啦,感谢你对三水点靠木的支持,希望我们整理的内容能够帮助到你。

Javascript 相关文章推荐
给moz-firefox下添加IE方法和属性
Apr 10 Javascript
JS获取页面窗口大小的代码解读
Dec 01 Javascript
Javascript中判断变量是数组还是对象(array还是object)
Aug 14 Javascript
简单的Jquery全选功能
Nov 07 Javascript
jQuery的deferred对象详解
Nov 12 Javascript
JS使用parseInt解析数字实现求和的方法
Aug 05 Javascript
jQuery中Ajax全局事件引用方式及各个事件(全局/局部)执行顺序
Jun 02 Javascript
浅谈JavaScript的push(),pop(),concat()方法
Jun 03 Javascript
实例教学如何写vue插件
Nov 30 Javascript
在vue 中使用 less的教程详解
Sep 26 Javascript
详解React中合并单元格的正确写法
Jan 08 Javascript
JavaScript this关键字指向常用情况解析
Sep 02 Javascript
vue里面父组件修改子组件样式的方法
Feb 03 #Javascript
vue中element组件样式修改无效的解决方法
Feb 03 #Javascript
浅谈vue中改elementUI默认样式引发的static与assets的区别
Feb 03 #Javascript
Vue修改mint-ui默认样式的方法
Feb 03 #Javascript
vue+webpack实现异步组件加载的方法
Feb 03 #Javascript
基于vue-resource jsonp跨域问题的解决方法
Feb 03 #Javascript
vue.js使用代理和使用Nginx来解决跨域的问题
Feb 03 #Javascript
You might like
丧钟首部独立剧集《丧钟:骑士与龙》北美正式开播,场面血腥
2020/04/09 欧美动漫
PHP print类函数使用总结
2010/06/25 PHP
php数据类型判断函数有哪些
2013/09/23 PHP
PHP网页游戏学习之Xnova(ogame)源码解读(一)
2014/06/23 PHP
php将html转成wml的WAP标记语言实例
2015/07/08 PHP
Zend Framework实现具有基本功能的留言本(附demo源码下载)
2016/03/22 PHP
PHP搭建大文件切割分块上传功能示例
2017/01/04 PHP
Yii2框架redis基本应用示例
2018/07/13 PHP
laravel中的一些简单实用功能
2018/11/03 PHP
深入浅析安装PhpStorm并激活的步骤详解
2020/09/17 PHP
jQuery控制元素显示、隐藏、切换、滑动的方法总结
2015/04/16 Javascript
使用Meteor配合Node.js编写实时聊天应用的范例
2015/06/23 Javascript
纯JavaScript代码实现移动设备绘图解锁
2015/10/16 Javascript
JavaScript文件的同步和异步加载的实现代码
2017/08/19 Javascript
JS实现二维数组横纵列转置的方法
2018/04/17 Javascript
jquery获取select选中值的文本,并赋值给另一个输入框的方法
2018/08/21 jQuery
微信小程序云开发修改云数据库中的数据方法
2019/05/18 Javascript
JS+CSS实现3D切割轮播图
2020/03/21 Javascript
python paramiko实现ssh远程访问的方法
2013/12/03 Python
python写的一个文本编辑器
2014/01/23 Python
Python pickle模块用法实例
2015/04/14 Python
Python实现扫描局域网活动ip(扫描在线电脑)
2015/04/28 Python
Python中的with语句与上下文管理器学习总结
2016/06/28 Python
Python 确定多项式拟合/回归的阶数实例
2018/12/29 Python
python将控制台输出保存至文件的方法
2019/01/07 Python
Python Pandas实现数据分组求平均值并填充nan的示例
2019/07/04 Python
python实现按首字母分类查找功能
2019/10/31 Python
pycharm如何使用anaconda中的各种包(操作步骤)
2020/07/31 Python
matplotlib自定义鼠标光标坐标格式的实现
2021/01/08 Python
Ralph Lauren法国官网:美国高品味时装品牌
2017/12/08 全球购物
单位在职证明范本
2014/01/09 职场文书
修理厂厂长岗位职责
2014/01/30 职场文书
音乐教育感言
2014/03/05 职场文书
2014新生大学四年计划书
2014/09/21 职场文书
十大最强妖精系宝可梦,哲尔尼亚斯实力最强,第五被称为大力士
2022/03/18 日漫
排查并解决MySQL生产库内存使用率高的报警
2022/04/11 MySQL