js 继承 的 6 种 方式

114 阅读7分钟

JS 继承

1. 认识继承

继承(英语:inheritance)是面向对象软件技术当中的一个概念。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的属性和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

2. 原型继承

所谓原型继承 就是 利用自定义原型的方式实现继承关系

核心 : 将子类的原型修改为父类的实例化对象

优点 : 可以使用弗雷的方法和属性,实现了继承

缺点 :

  • 1.原本原型上的方法不能时使用了,因为原型对象改变了
  • 2.继承到的属性并不在自己身上,而是在原型对象上,但不影响使用
    //构造函数1
    function Person(name) {
      this.name = name
    }
    Person.prototype.init = function () { //原型上的init方法
      console.log('我是Person原型上的函数')
    }
    //构造函数2
    function Stu(age) {
      this.age = age
    }
    Stu.prototype.sayHi = function () {
      console.log('你好')
    }

    Stu.prototype = new Person('张三')
    //stu的原型 通过new 关键字调用person函数,并在调用函数的括号内书写实参
    let s1 = new Stu(18, '张三')
    console.log(s1)
    s1.init()//方法可以调用
    s1.sayHi()//原方法被但单独排挤在外,因此无法在原函数上调用
    
   /**
    我们基于这个创建的实例化对象他们的name 属性都是 张三
    如果 name 属性不同, 那就需要在单独处理了
    */
    //例如:
    Stu.prototype = new Person('张三')
    let p = new Stu(18,'张三')
    Stu.prototype = new Person('里斯')
    let s = new Stu(99,'李四')
    console.log(p)
    console.log(s)
   

3. 借用构造函数继承

核心: 把父类构造函数当作普通函数调用,利用call修改这个函数内部的this指向 (如果不需改,this指向就会指向别的地方)

优点 : 把父类得到全部属性继承到自己身上

缺点 :

    1. 只能继承属性,不能继承方法
    1. 每次调用Stu 时,Stu内部还会自动调用一次 Person 函数
    //构造函数1
    function Person(name) {
      this.name = name
    }
    Person.prototype.init = function () {
      console.lg('我是Person原型上的函数')
    }
    //构造函数2
    function Stu(age,name) {
      //1.自动创建一个对象
      //2.手动添加属性
      this.age = age
      //通过call 方法调用Person属性,改变this指向,使this指向Stu函数内部在自动创建的对象
      Person.call(this,name)
      //3.自动返回这个对象

    }
    Stu.prototype.sayHi = function () {
      console.lg('你好')
    }
    // 通过new 关键字调用stu构造函数,得到一个实例化对象保存在常量 p s  中
    let p = new Stu(189,'里斯')
    let s = new Stu(99,'李四')
    console.log(p)
    console.log(s)
    p.init()//报错,因为不能继承方法
    p.info()

4. 组合继承

核心 : 把原型继承和构造函数继承结合继起来使用

优点 : 实例化对象具有继承到的属性,并且能够继承父类原型上的方法

缺点 : 实例化对象上 与原型对象上 ,都有父类的属性(多了一套,但是并不影响使用)

    //构造函数1
     function Person(name) {
      this.name = name
    }
    Person.prototype.init = function () {
      console.log('我是Person原型上的函数')
    }
    //构造函数2
    function Stu(age,name) {
      this.age = age
      //1.借用构造函数继承,得到父类属性(放在对象上,但没有继承父类的方法)
      Person.call(this,name)
    }
    
    //2.利用原型继承,得到父类的属性(原型上)和方法
    Stu.prototype = new Person('这个字符串没有意义')
    //上面代码必须放在下面sayHi方法的的上面,不然sayHi方法无法调用
    Stu.prototype.sayHi = function () {
      console.log('你好')
    }

    //创建实例化对象
    const s = new Stu(18,'张三')
    console.log(s)
    console.log(s.__proto__)

5. 拷贝继承

for... in 遍历,可以遍历到对象的原型上的方法

    //构造函数1
    function Person(name) {
      this.name = name
    }
    Person.prototype.init = function () {///原型上的init方法
      console.log('我是Person原型上的函数')
    }
    //以下就是拷贝继承原理
    // const p1 = new Person('张山')
    // for (let key in p1){
    //   console.log(key,p1[key])
    // }

    //构造函数2
    function Stu(age) {
      this.age = age
      //将上面代码变形写到下方,完成拷贝继承
      /**
       * 在子类构造函数实例化父类构造函数,得到父类构造函数的实例化对象
       *      然后利用 for...in遍历
      */
      const p1 = new Person('张山')
      for (let key in p1) {
        Stu.prototype[key] = p1[key]
      }

    }
    Stu.prototype.sayHi = function () {
      console.log('你好')
    }

    let s = new Stu(18,'张三')
    console.log(s)
    console.log('Stu原型对象', s.__proto__)
    s.init()
    s.info()

6. 类的继承:

语法要求:

    1. 书写子类时, 格式: class Stu extends 父类 {}
    1. 书写 constructor 时,内部需要书写 super('父类需要的参数')

注意:

    1. extends 和 super 必须同时出现
    1. super 必须书写在 constructor 的第一行

额外扩展 : ES6 类也能继承 ES5 的构造函数

  • 验证方法:将 Person ES6 类的写法更改为ES5 的构造函数写法即可
    //父类
    // class Person {
    //   constructor(name) {
    //     this.name = name
    //   }
    //   init() {
    //     console.log('我是 Person 原型上的函数')
    //   }
    // }


   ** // ES6 类也能继承 ES5 的构造函数**
    function Person(name) {
      this.name = name
    }
    Person.prototype.init = function () {
      console.log('我是 Person 原型上的函数')
    }
    

    //子类    ==> 格式: class Stu extends 父类 {}
    class Stu extends Person {
      constructor(age, name) {
        // super('父类需要的参数,都写在这里')//强制语法,且必须写在第一行
        super(name)
        this.age = age
      }
      sayHi() {
        console.log('你好')
      }
    }

    const s1 = new Stu(18, '张三')
    console.log(s1)

7. 深浅拷贝

**含义: **

  • 通常是指加密和一个引用数据类型 , 拷贝到另一个变量中,
  • 但是根据拷贝的方法不同 , 展示出的效果也有差异

浅拷贝

    let obj = {
      name: '张三',
      age: 18,
      info: {
        width: 100,
        height: 280
      }
    }
   // 浅拷贝  => 可以拷贝出一个一样的对像,但是两者相关联,  内层  属性值会被同步修改
    let obj2 = {}
    for(let key in obj){
      obj2[key] = obj[key]
    }
    obj2.age = 99
    obj2.info.width = 999

    console.log('原对象', obj)   //{name: '张三', age: 18, info: {…}}
    console.log('浅拷贝', obj2)  //{name: '张三', age: 99, info: {…}}

深拷贝

    let obj = {
            name: '张三',
            age: 18,
            info: {
                width: 100,
                height: 280
            }
        }
        let newObj = {}

        // 通过下面代码帮助我们理解深拷贝
        function deepClone(target, origin) {
            /**
             *  target: 目标对象
             *  origin: 原始对象
             * 
             *      需求: 将原始对象 origin 内部的所有属性, 拷贝到目标对象 target 中
            */

            // 1. 通过 for...in 遍历对象, 拿到对象的所有的 key 与 对应的 value
            for (let key in origin) {
                // 2. 根据 遍历到的这个属性的属性值是什么类型的, 决定执行什么代码
                if (Object.prototype.toString.call(origin[key]) === '[object Object]') {
                    // 表明当前这个 key 的值为一个对象
                    target[key] = {}
                    deepClone(target[key], origin[key])


                } else if (Object.prototype.toString.call(origin[key]) === '[object Array]') {
                    // 表明当前这个 key 的值为一个数组
                    target[key] = []
                    deepClone(target[key], origin[key])
                } else {
                    // 表明当前这个 key 的值 一定不是对象或者数组
                    target[key] = origin[key]
                }
            }
        }

        /**
         *  分析:
         *      调用 deepClone, 第一个参数是一个空对象
         *          第二个参数内部有三个属性, 分别为 name-age-info, 其中 info 是一个对象
         *
         *      函数在执行的时候, 首先会执行一个 for...in 循环遍历 第二个参数
         *
         *          for..in 循环第一次执行  key === 'name'  origin[key] === '张三'
         *              开始运行 循环内部的 分支语句, 因为当前的 value 类型为 string, 所以会走最后一个 else 分支
         *              那么就是:   target[key] = origin[key]       target.name = '张三'
         *
         *          for...in 循环第二次执行 key === 'age'   origin[key] === 18
         *              开始运行 循环内不的 分支语句, 因为当前的 value 类型为 number, 所以会走最后一个 else 分支
         *              那么就是: target[key] = origin[key]         target.age = 18
         *
         *          两轮循环结束
         *              此时 origin 这个对象 {name: '张三', age: 18}
         *
         *          for...in 循环第三次执行 key === 'info'  origin[key] === {width: 100, height: 280}
         *              开始运行 循环内不的 分支语句, 因为当前的 value 类型为 Object, 所以会走第一个 分支
         *              分支内部的代码
         *                  1. target[key] = {}        {name: '张三', age: 18, info: {}}
         *
         *                  2. deepClone(target[key], origin[key])
         *                          调用 deepClone 第一个参数是一个 空对象
         *                          第二个参数内部有两个属性, 分别为 width, height
         *
         *                          函数在执行的时候, 首先会执行一个 for...in 循环遍历第二个参数
         *                              for...in 循环第一次执行 key === 'width' origin[key] === 100
         *                              开始运行 循环内部的 分支语句, 因为当前的 value 类型为 number, 所以会走最后一个 else 分支
         *                              那么就是: target[key] = origin[key]         target.width = 100
         *
         *                              for...in 循环第二次执行 key === 'height' origin[key] === 280
         *                              开始运行 循环内部的 分支语句, 因为当前的 value 类型为 number, 所以会走最后一个 else 分支
         *                              那么就是: target[key] = origin[key]         target.height = 280
         *                  这两轮 for 执行完毕后 target.info === {width: 100, height: 280}
        */

        // 上述代码仅仅是为了帮助我们更快更直观的了解什么深拷贝
        //但是这种代码是比较麻烦且繁琐
        //其实想要完成深拷贝,秩序下面一行代码就可以了
        newObj = JSON.parse(JSON.stringify(obj))
         /**
         * 1、 JSON.stringify()  从一个对象中解析出字符串
         *      例如:
         *          JSON.stringify({"a":"1","b":"2"})
         *          结果是:
         *                {"a":"1","b":"2"}
         * 
         * 2、JSON.parse()从一个字符串中解析出JSON对象
         *      例如: 
         *          var str = '{"a":"1","b":"2"}';
         *          JSON.parse(str);
         *          结果是:
         *                Object{
         *                      a:"1",
         *                      b:"2"
         *                    }
        */

        newObj.age = 99
        newObj.info.width = 999
        console.log('newObj', newObj)
        console.log('obj', obj)