彻底理解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 相关文章推荐
js 判断 enter 事件
Feb 12 Javascript
33个优秀的 jQuery 图片展示插件分享
Mar 14 Javascript
jquery div拖动效果示例代码
Dec 08 Javascript
jquery实现右键菜单插件
Mar 29 Javascript
Node连接mysql数据库方法介绍
Feb 07 Javascript
Bootstrap导航中表单简单实现代码
Mar 06 Javascript
浅谈关于.vue文件中style的scoped属性
Aug 19 Javascript
vue 引入公共css文件的简单方法(推荐)
Jan 20 Javascript
基于openlayers4实现点的扩散效果
Aug 17 Javascript
JS实现读取xml内容并输出到div中的方法示例
Apr 19 Javascript
浅谈webpack4.x 入门(一篇足矣)
Sep 05 Javascript
详解vue-cli 脚手架 安装
Apr 16 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
php的header和asp中的redirect比较
2006/10/09 PHP
一些php项目中比较通用的php自建函数的详解
2013/06/06 PHP
php生成二维码
2015/08/10 PHP
最准确的php截取字符串长度函数
2015/10/29 PHP
PHP生成加减算法方式的验证码实例
2018/03/12 PHP
JavaScript面向对象编程
2008/03/02 Javascript
javascript 特性检测并非浏览器检测
2010/01/15 Javascript
Javascript常考语句107条收集
2010/03/09 Javascript
jQuery多项选项卡的实现思路附样式及代码
2014/06/03 Javascript
运用jQuery定时器的原理实现banner图片切换
2014/10/22 Javascript
Jquery的基本对象转换和文档加载用法实例
2015/02/25 Javascript
javascript包装对象实例分析
2015/03/27 Javascript
javascript常见数据验证插件大全
2015/08/03 Javascript
基于javascript实现窗口抖动效果
2016/01/03 Javascript
jquery的ajax提交form表单的两种方法小结(推荐)
2016/05/25 Javascript
javascript 组合按键事件监听实现代码
2017/02/21 Javascript
常用的js方法合集
2017/03/10 Javascript
微信小程序 在线支付功能的实现
2017/03/14 Javascript
JS实现可视化文件上传
2018/09/08 Javascript
angularjs的单选框+ng-repeat的实现方法
2018/09/12 Javascript
swiper.js插件实现pc端文本上下滑动功能示例
2018/12/03 Javascript
TypeScript类型声明书写详解
2019/08/28 Javascript
在Django同1个页面中的多表单处理详解
2017/01/25 Python
PyCharm汉化安装及永久激活详细教程(靠谱)
2020/01/16 Python
TensorFlow2.0:张量的合并与分割实例
2020/01/19 Python
沃尔玛加拿大:Walmart.ca
2020/03/02 全球购物
经典C++面试题一
2016/11/06 面试题
历史学专业毕业生求职信
2013/09/27 职场文书
小学生春游活动方案
2014/08/20 职场文书
优秀教师先进事迹材料
2014/12/15 职场文书
六一儿童节新闻稿
2015/07/17 职场文书
Python爬虫基础初探selenium
2021/05/31 Python
DjangoRestFramework 使用 simpleJWT 登陆认证完整记录
2021/06/22 Python
HTML基础详解(上)
2021/10/16 HTML / CSS
在Windows Server 2012上安装 .NET Framework 3.5 所遇到的问题
2022/04/29 Servers
Java版 单机五子棋
2022/05/04 Java/Android