十六、继承

84 阅读5分钟

1. 原型

1.1 原型:

每个 函数 都自带一个 prototype属性 ,它是一个 对象 。prototype就是函数的原型。

1.2 原型的作用:

给构造函数添加公共的属性和方法。不建议将属性放在原型上面(因为每个对象都有自己独特的属性值)

1.3 批量添加原型方法

  • 可以给一个新对象,但是新对象里面需要手动添加构造器(constructor)

     function Person(name,age){
         this.name=name;
         this.age=age;
     }
     
     // 1. 原型上添加一些方法:直接添加
     Person.prototype.eat=function(){};
     Person.prototype.run=function(){};
     
     // 2. 原型上添加一些方法:新对象+构造器
     Person.prototype={
         constructor:Person;
         eat(){};
         run(){};
     }
     
    

1.4 通过原型重写数组的高阶方法

1.4.1 重写forEach ---> 遍历,无返回值
Array.prototype.myForeach = function (callback){
    for(let i = 0; i < this.length; i++){
        callback(this[i],i,this); // 原型方法里面的this,谁调用this指向谁
    }
}
1.4.2 重写map ---> 遍历,返回一个数组
Array.prototype.myMap = function(callback){
    let arr = [];
    for(let i = 0; i < this.length; i++){
        arr.push(callback(this[i],i,this)); // 原型方法里面的this,谁调用this指向谁
    }
    return arr;
}
1.4.3 重写some ---> 判断是否有成员满足条件,有一个满足就返回true
Array.prototype.mySome = function(callback){
    for(let i = 0; i < this.length; i++){
        if(callback(this[i],i,this)){ // 原型方法里面的this,谁调用this指向谁
            return true;
        };
    }
    return false;
}
1.4.4 重写every ---> 判断是否所有成员满足条件,所有满足才返回true
Array.prototype.myEvery = function(callback){
    for(let i = 0; i < this.length; i++){
        if(!(callback(this[i],i,this))){ // 原型方法里面的this,谁调用this指向谁
            return false;
        };
    }
    return true;
}
1.4.5 重写filter ---> 过滤掉不满足条件的成员,返回符合条件的成员组成的数组
Array.prototype.myFilter = function(callback){
    let arr = [];
    for(let i = 0; i < this.length; i++){
        if(callback(this[i],i,this)){ // 原型方法里面的this,谁调用this指向谁
            arr.push(this[i]);
        };
    }
    return arr;
}
1.4.6 重写reduce ---> 累加器,返回最终的累加值
Array.prototype.myReduce = function(callback,init){
    let pre = arguments.length == 2 ? init : this[0];
    let index = arguments.length == 2 ? 0 : 1;
    for(let i = index ; i < this.length; i++){ // 原型方法里面的this,谁调用this指向谁
        pre = callback(pre,this[i],i,this);
    }
    return pre;
}

2. 原型链

2.1 指针:

每个 实例对象 都会自带一个 __proto__属性 ,__proto__就是对象的指针。指向构造函数的原型

2.2 原型链:

当实例对象访问某个属性或方法时,先会通过实例对象的构造器constructor访问构造函数内部的属性和方法,如果不存在,则会通过 实例对象.__proto__ 访问构造函数的原型对象上的属性和方法,如果还是查找不到,继续通过.__proto__查找上一层的原型,直到null还查找不到就报错,这个查找的过程就叫原型链。

QQ图片20230421210538.jpg

QQ图片20230421210435.jpg

QQ图片20230421210446.png

3. 面向对象

3.1 面向对象的方法:

  • 面向对象分析 OOA :有哪些对象
  • 面向对象设计 OOD :对象和对象之间的关系
  • 面向对象编程 OOP :编程阶段

3.2 面向对象编程的3大特点:

  • 封装将相同的属性和方法提取成为一个类
    • 类:抽象。 对象:具体。

    • 类是对象的模板,对象是类的实例

  • 继承
    • 代码复用:子类会继承父类的属性和方法

    • 更灵活:子类可以添加或者修改属性或者方法

  • 多态
    • 重写(override):子类重写父类的属性和方法(基于继承)

    • 重载(overload):在同一个类中,同名不同参(函数名相同,参数不同)

    • 【注意】JS中不存在重载,因为js的函数名相同会被覆盖,但是可以模拟出重载。

        class Person{
            show(){
                if(arguments.length==1){
                    return arguments[0]*10;
                }
                if(arguments.length==2){
                    return arguments[0]*arguments[1];
                }
            }
        }
      

4. 纯函数

  • 有参数的输入,有结果的输出。
  • 不会产生副作用(不会对函数以外的变量产生影响)。

QQ图片20230420205112.png

5. 回调函数

5.1 回调函数:

  • 把一个函数当作另外一个函数的参数,在另外一个函数内部被执行或传递参数。

QQ图片20230420205112.png

5.2 回调函数的好处:

  • 解决异步
  • 扩展函数的功能

6. 构造函数

构造函数上的属性和方法优先于原型上的属性和方法。

    // 创建相同的对象
    var obj1 = {name:"小A",age:20,run:function(){}};
    var obj2 = {name:"小B",age:18,run:function(){}};
    
    // 解决重复创建相同对象,采用函数工厂,但是对象名称无法区分
    function createObj(name,age){
        var o = {};
        o.name=name;
        o.age=age;
        o.run=function(){};
        return o;
    }
    var o1 = createObj("小A",20);
    var o2 = createObj("小B",18);
    
    // 构造函数,解决重复创建相同对象和对象名称不明确的问题.(构造函数的函数名首字母大写,和普通函数进行区分)
    function Person(name,age){
        // 1. 在构造函数的内部创建了一个空对象
        var obj = new Object();
        // 2. 空对象的指针指向构造函数的原型
        obj.__proto__=Person.prototype;
        // 3. 构造函数的this指向空对象
        Person.call(obj,xxx)
        // 4. 在空对象上添加属性和方法
        this.name=name;
        this.age=age;
        this.run=function(){};
        // 5. 隐式的 return this
        return this;
    }
    var p1 = new Person("小A",20);
    var p2 = new Person("小B",18);

【重点】new操作符做了什么事情?

    1. 在构造函数内部创建一个空对象
    1. 空对象的指针__proto__指向构造函数的原型对象prototype
    1. 构造函数的this指向空对象
    1. 在空对象上添加属性和方法
    1. 隐式的return this

7. 继承

7.1 继承:

  • 子类继承父类的属性和方法。

7.2 继承的优点:

  • 代码复用:子类可以继承父类的属性和方法
  • 灵活:子类可以追加或者修改属性和方法

7.3 继承的类型:

7.3.1 原型链继承
  • ① 原型链继承:子类的原型指向父类的实例对象
  • ② 原型链继承的缺点:子类实例化的时候无法传递参数

QQ图片20230421212957.png

7.3.2 对象冒充继承
  • ① 对象冒充继承(又叫构造函数继承):利用父类调用 call() 或 apply() 或 bind()
  • ② 对象冒充继承的优点:子类能属性初始化,并且所有的属性不是共享的
  • ③ 对象冒充继承的缺点:无法继承父类原型prototype上的属性和方法

QQ图片20230421213746.png

7.3.3 组合继承:原型链继承+对象冒充继承
  • ① 组合继承:原型链继承+对象冒充继承
  • ② 组合继承的优点:子类能属性初始化,能继承父类原型上的属性和方法
  • ③ 组合继承的缺点:原型上会多出一些值为undefined的属性

QQ图片20230421214619.png

7.3.4 寄生组合继承:寄生继承+对象冒充继承
  • ① 寄生组合继承:寄生继承+对象冒充继承
  • ② 寄生组合继承的优点:子类能属性初始化,能继承父类原型上的属性和方法,子类的原型上没有多余的属性
  • ③ 寄生组合继承的缺点:代码比较多

【寄生继承】

function inhert(base,child){
    // 1. 创建一个父类原型的副本
    var basePrototype = Object.create(base.prototype);
    // 2. 给创建的副本设置构造器constructor
    basePrototype.constructor = child;
    // 3. 将副本赋值给子类的原型
    child.prototype = basePrototype;
}

QQ图片20230421215914.png

7.3.5 ES6类继承

QQ图片20230421221223.png