继承及继承的方式

继承

JS中继承的概念:

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

为什么要使用继承?

  • 有些对象会有方法(动作、行为),而这些方法都是函数,如果把这些方法和函数都放在构造函数中声明就会导致内存的浪费
  • 把子类中共同的成员提取到父类中,实现代码重用。
    function Person(){
        this.say = function(){
            console.log("你好")
        }
    }
    var p1 = new Person();
    var p2 = new Person();
    var p3 = new Person();
    var p4 = new Person();
    ...
    console.log(p1.say === p2.say);   //false 由于say方法可能功能相似,但是不是同一个方法(没有指向同一块内存,会造成内存浪费)

继承的方式

实现继承首先需要一个父类,在js中实际上是没有类的概念,在es6中class虽然很像类,但实际上只是es5上语法糖而已。

    // 父类
    function Person(name, age, sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    // 子类
    function Student(name, age, sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

第一种方式:原型链继承(父类的实例作为子类的原型)

    function Student(){}

    Student.prototype = new Person();
    Student.prototype.constructor = Student;
    Student.prototype.name = 'zhangsan';
    Student.prototype.age= 18;
    var s1 = new Student();
  • 优点: 简单易于实现,父类新增的原型方法/原型属性,子类都能访问
  • 缺点:
    1. 要想为子类新增属性和方法,必须要在 new 父类构造函数的后面执行,不能放到构造器中
    2. 无法实现多继承
    3. 创建子类实例时,不能向父类构造函数中传参数

第二种方式:借用构造函数实现继承 .call() .apply()

function Student(name,age, sex, score){
    this.score = score;
    Person.call(this, name, age, sex);
    // ->等价于
    //Person.apply(this, [name, age, sex])
}

var s1 = new Student('zhangsan', 18, 'male', 90);
  • 场景:适用于2种构造函数之间逻辑有相似的情况,如 Student(), Person()
  • 优点:
    1. 解决了子类构造函数向父类构造函数中传递参数
    2. 可以实现多继承(call或者apply多个父类)
  • 缺点:
    1. 方法都在构造函数中定义,无法复用。
    2. 不能继承原型属性/方法,只能继承父类的实例属性和方法。

第三种方式:组合式继承(借用构造函数 + 原型链继承)

  • 核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Student(name,age, sex, score){
    this.score = score;
    Person.call(this, name, age, sex);
    // ->等价于
    //Person.apply(this, [name, age, sex])
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
Student.prototype.exam = function(){
    console.log('考试了')
}
var s1 = new Student('zhangsan', 18, 'male', 90);
  • 优点:
    1. 可以继承属性和方法,并且可以继承原型的属性和方法
    2. 函数可以复用
    3. 不存在引用属性问题
  • 缺点:
    1. 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。

第四种方式:拷贝继承(混入继承)

  • 场景:有时候想使用某个对象中的属性,但是又不能直接修改它,于是就可以创建一个该对象的拷贝
  • 实现1:
    var source={name:"李白",age:15}
    var target={};
    target.name=source.name
    target.age=source.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}
    var target={ ...source }
    var target={ ...source, age:18}

第六种方式:原型式继承

  • 场景:
    • 创建一个纯洁的对象
    • 创建一个继承自某个父对象的子对象
  • 使用方式:
    • 空对象:Object.create(null)
        var o1={ say:function(){} }
        var o2=Object.create(o1);
    

其它方式:寄生继承、寄生组合继承

bind()、call()、apply() 区别:

  • 共同点:改变this指向。
  • 不同点:
    示例:
      function fn(x,y){
        console.dir(this);   //this --> window
        console.log(x + y);
      }
      fn(2, 3)
    
  • bind() 改变 this 的指向,不会调用函数,返回一个新的函数;
        var o = {name:'wang'};
        var fn1 = fn.bind(o, 2, 3);
        fn1(); // this --> o
    
  • call() 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。
        var o = {name:'wang'};
        fn.call(o, 2, 3); // this --> o
    
  • apply() 方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的参数。
        var o = {name:'wang'};
        fn.apply(o, [2, 3]); // this --> o
    

函数内 this 指向的不同场景

函数的调用方式决定了 this 指向的不同:

调用方式 非严格模式 备注
普通函数调用 window 严格模式下是 undefined
构造函数调用 实例对象 原型方法中 this 也是实例对象
对象方法调用 该方法所属对象 紧挨着的对象
事件绑定方法 绑定事件对象
定时器函数 window
posted @ 2020-05-16 16:43  十一是假期啊  阅读(187)  评论(0编辑  收藏  举报