• JS高级(二)--继承


    一、JS中继承的概念:

    • 通过【某种方式】让一个对象可以访问到另一个对象中的属性和方法,我们把这种方式称之为继承 `并不是所谓的xxx extends yyy`

     二、为什么要使用继承?

    • 有些对象会有方法(动作、行为),而这些方法都是函数,如果把这些方法和函数都放在构造函数中声明就会导致内存的浪费
    1 function Person(){
    2     this.say=function(){
    3        console.log("你好")
    4     }
    5 }
    6 var p1=new Person();
    7 var p2=new Person();
    8 console.log(p1.say === p2.say); //false
     1 function Person(name,age){
     2         this.name=name;
     3         this.age=age;
     4         this.say=function(){}
     5     }
     6     var p1=new Person();
     7     var p2=new Person();
     8     
     9     //p1对象和p2对象的say方法是否是同一个方法:false
    10     console.log(p1.say===p2.say);
    11 
    12     //由于say方法可能功能相似,但是不是同一个方法(没有指向同一块内存,会造成内存浪费)
    13     //解决方案:把say方法写在他们共同的(父对象)中
    14     //其实他们共同的父对象,就可以通过:Person.prototype来获取
    15 
    16     //-->只要把say方法写在Person.prototype中,那么say方法就是同一个方法
    17     Person.prototype.run=function(){
    18         console.log('时速500KM');
    19     }
    20     //此时p1和p2都可以访问到run方法
    21     p1.run();
    22     p2.run();
    23     //验证p1.run和p2.run是否是同一个方法?
    24     console.log(p1.run == p2.run); //指向同一个方法,这种方法避免了内存的浪费
    25     
    26     console.log(p1.run == Person.prototype.run);//true
    27 
    28     var p3=new Person();
    29     console.log(p3.run == p1.run); //true
    30     console.log(p3.run === p1.run);//true
    31     //结论:只要往某个构造函数的prototype对象中添加某个属性、方法,那么这样的属性、方法都可以被所有的构造函数的实例所共享
    32     //==>这里的【构造函数的prototype对象】称之为原型对象
    33     //  Person.prototype是 p1 p2 p3 的原型对象
    34     //  Person.prototype是Person构造函数的【实例】的原型对象
    35 
    36     //猜猜看?
    37     //  Person的原型对象是谁呢?
    38     //  -->首先要知道Person的构造函数:-->Function
    39     //  -->所以Person的原型对象是:Function.prototype
    40 
    41     //  p1的原型对象是谁呢?
    42     //  -->首先要知道p1是谁创建的?    -->Person
    43     //  -->所以p1的原型对象时:     Person.prototype

    三、继承的第一种方式:原型链继承1

     1     function Dog(){
     2 
     3     }
     4     var d1=new Dog();
     5     //为了让d1有一个say的方法,
     6     //错误:d1.say=function(){}
     7     //正确:
     8     Dog.prototype.say=function(){
     9         console.log('汪汪汪');
    10     }
    • 缺点:添加1、2个方法无所谓,但是如果方法很多会导致过多的代码冗余

    四、继承的第二种方式:原型链继承2

     1     function Cat(name){
     2         this.name=name;
     3     }
     4     var tom=new Cat("汤姆");
     5     //目的:把say方法放在tom的原型对象中(Cat.prototype)
     6     Cat.prototype.say=function(){}
     7 
     8     //问题:
     9     Cat.prototype.s1=function(){}
    10     Cat.prototype.s2=function(){}
    11     Cat.prototype.s3=function(){}
    12     Cat.prototype.s4=function(){}
    13     Cat.prototype.s5=function(){}
    14     //通过上面的方式,给tom的原型对象添加了好多方法,也就是让tom拥有了好多方法,但是代码产生了不少的冗余(重复)
    15 
    16     //-->为了减少这种重复,改良版:
    17     Cat.prototype = {
    18         a1:function(){},
    19         a2:function(){},
    20         a3:function(){},
    21         a4:function(){},
    22         a5:function(){}
    23     }
    24     console.log(tom.s1);    //可以访问
    25     console.log(tom.a1);    //undefined
    26     //原因:tom对象在创建的时候已经有了一个确定的原型对象,就是旧的Cat.prototype
    27     //由于Cat.prototype后面被重新赋值,但是tom对象的原型对象却没有改变,所以tom对象并不能访问到新原型对象中的a1-a5方法
    28 
    29     //如何解决这个问题?
    30     //-->先改变原型、再创建对象
     1     function Tiger(){
     2 
     3     }
     4     Tiger.prototype={
     5         a:function(){
     6             
     7         },
     8         b:function(){
     9 
    10         }
    11     }
    12     //创建tiger实例,此时的tiger实例的原型对象是新原型,所以tiger可以访问到新原型中的属性和方法(a/b)
    13     var tiger=new Tiger();
    14     console.log(tiger.a);
    15     console.log(tiger.b);
    •  注意点:

    a、一般情况下,应该先改变原型对象,再创建对象

    b、一般情况下,对于新原型,会添加一个constructor属性,从而不破坏原有的原型对象的结构

    五、继承的第三种方式:拷贝继承(混入继承)

    场景:有时候想使用某个对象中的属性,但是又不能直接修改它,于是就可以创建一个该对象的拷贝

    实际应用:

    jquery:$.extend:编写jquery插件的必经之路 (基于jquery封装一个表格控件)

    • 实现1:
    var source={name:"李白",age:15}
    var target={};
    target.name=source.name
    target.age=source.age;
    • 深拷贝和浅拷贝:
    1. - 浅拷贝只是拷贝一层属性,没有内部对象
    2. - 深拷贝其实是利用了递归的原理,将对象的若干层属性拷贝出来
    var students=[
        {name:"",age:""},
        {name:"",age:""}
    ]
    

      

    • 上面的方式很明显无法重用,实际代码编写过程中,很多时候都会使用拷贝继承的方式,所以为了重用,可以编写一个函数把他们封装起来:
        function extend(target,source){
            for(key in source){
                target[key]=source[key];
            }
            return target;
        }
        extend(target,source)  
    • 由于拷贝继承在实际开发中使用场景非常多,所以很多库都对此有了实现

    - jquery:$.extend

    • es6中有了对象扩展运算符仿佛就是专门为了拷贝继承而生:
    var source={name:"李白",age:15}
    //让target是一个新对象,同时拥有了name、age属性
    var target={ ...source }    
    var target2={ ...source,age:18 }
     1     //1、已经拥有了o3对象
     2     var o3={gender:"男",grade:"初三",group:"第五组",name:"张三"};
     3     //2、创建一个o3对象的拷贝(克隆):for...in循环
     4     var o4={};
     5         //a、取出o3对象中的每一个属性
     6     for (var key in o3) {
     7           //key就是o3对象中的每一个属性
     8         //b、获取到对应的属性值
     9         var value = o3[key];
    10         //c、把属性值放到o4中
    11         o4[key] = value;
    12     }
    13     //3、修改克隆对象,把该对象的name属性改为"李四"
    14     o4.name="李四"
    15     console.log(o4);    //最终的目标对象的结果
    16     //。。。后续如果修改了o4对象中的相关属性,就不会影响到o3
     1     // 封装拷贝继承函数
     2     function extend(source,target){
     3       for (var key in source) {
     4         var value = source[key];
     5         target[key] = value;
     6       }
     7     }
     8     var o3={gender:"男",grade:"初三",group:"第五组",name:"张三"};
     9 
    10     var o4={};
    11     extend(o3,o4);
    12     o4.name="李四";
    13 
    14     var o5={};
    15     extend(o3,o5);
    16     o5.name="李小龙";

    六、继承的第四种方式:原型式继承

    • 场景:
    1. 创建一个纯洁的对象
    2. 创建一个继承自某个父对象的子对象
    var parent={ age:18,gender:"男"};
    var student=Object.create(parent);
    //student.__proto__===parent
    • 使用方式:

    空对象:Object.create(null)

    var o1={ say:function(){} }
    var o2=Object.create(o1); 

    七、继承的第五种方式:借用构造函数实现继承

    • 场景:适用于2种构造函数之间逻辑有相似的情况
    • 原理:函数的call和apply调用方法
    function Animal(name){
        this.name=name;
    }
    function Person(name,age){
        this.name=name;
        this.age=age;
    }
    
    • 局限性:Animal(父类构造函数)的代码必须完全适用于Person(子类构造函数)
    • 以上代码用借用构造函数实现
     1 function Animal(name,age){
     2     this.name=name;
     3     this.age=age;
     4 }
     5 function Person(name,age,address){
     6     Animal.call(this,name);
     7     //this.name=name;
     8     //this.age=age;
     9     this.address=address;
    10 }
     1     function Animal(name,age,gender){
     2         this.name=name;
     3         this.age=age;
     4         this.gender=gender;
     5     }
     6     function Person(name,age,gender,say){
     7         //这段代码调用错误
     8         //为什么错误?
     9         //因为这种函数的调用方式,函数内部的this只能指向window
    10             //Animal(name,age,gender);
    11         //目的:将Animal函数内部的this指向Person的实例
    12             //Animal.call(this,name,age,gender)
    13             //-->等价于:
    14             Animal.apply(this,[name,age,gender])
    15         this.say=say;
    16     }
    17     var p1=new Person("宋小宝",15,"男",
    18         function(){
    19 
    20     });

    八、还有其他继承方式:寄生继承、寄生组合继承

  • 相关阅读:
    rsync命令使用方法
    Mysql(MyISAM和InnoDB)及Btree和索引优化
    初级java程序员-各公司技能要求
    Redis学习笔记二 (BitMap算法分析与BitCount语法)
    HTTP、TCP、IP协议常见面试题
    Redis学习笔记一(Redis的详细安装及Linux环境变量配置和启动)
    java-部分精选面试题
    Python基础-TypeError:takes 2 positional arguments but 3 were given
    Python3.7中urllib.urlopen 报错问题
    几道关于springboot、springCloud的面试题。
  • 原文地址:https://www.cnblogs.com/junjingyi/p/9188312.html
Copyright © 2020-2023  润新知