[js] 继承 原型继承,call继承,组合继承,ES6继承

继承 :

  • 继承是一个关于 构造函数 的高阶应用

  • 继承一定是出现在 两个构造函数 之间的关系

  • 什么是 继承 :

    => 当 构造函数 A 的 实例 , 使用了 构造函数 B 原型 上书写的属性和方法

    => 此时, 我们就说 A 和 B 出现了 继承关系

    => 构造函数 A 继承自 构造函数 B

    => 构造函数 A 是 构造函数 B 的 子类

    => 构造函数 B 是 构造函数 A 的 父类

为什么需要继承 ?

  • 我们面向对象开发的核心, 是 => 高内聚 低耦合 <=

  • 来思考 ? 进行假设 :

    => 我书写一个 苹果类 :

    ~> 颜色, 品牌, 重量, 水分, 名称

    ~> 吃的方法: 削皮吃, 去核吃

    => 我书写一个 橘子类 :

    ~> 颜色, 品牌, 重量, 水分, 名称

    ~> 吃的方法: 剥皮吃, 吐籽吃

可以看到 : 苹果类 和 橘子类 有 共同的 部分

  • 所以换一种书写方法 :

    => 水果类 :

    ~> 颜色, 品牌, 重量, 水分, 名称

    => 苹果类 :

    ~> 继承 水果类

    ~> 书写我独立的内容

    ~> 形状

    => 橘子类 :

    ~> 继承 水果类

    ~> 书写我独立的内容

    ~> 额外用途

构造函数 :

  • 构造函数 :

    => 原型 ( prototype ) :

每一个函数 天生自带一个属性, 叫做 prototype , 是一个 对象数据类型

=> 为什么需要原型 :

~> 为了解决 构造函数 的不合理

~> 书写 构造函数 的时候

~> 属性 直接书写在 构造函数体内

~> 方法 书写在 构造函数 的 原型 上

=> 目的: 为了书写一些方法, 给到该 构造函数 的 实例 使用

~> 把每一个实例都会用到的方法, 提取出来放在了 构造函数 的 原型 上

A 继承自 C , A 是 子类 , C 是 父类

B 继承自 C , B 是 子类 , C 是 父类

继承的目的 , 就是为了能够使用 构造函数 C 身上的 属性 和 方法 ,

把公共的东西提取出来成为更公共的东西

常见的继承方案 :

1. 原型继承 : Student.prototype = new Person(‘Jack’, 18)

原型继承,就是在本身的原型链上加一层结构

function Student() {}
Student.prototype = new Person('Jack',20)

原型继承(原型链继承) :

  • 核心: 让子类的 原型 指向 父类的 实例

  • 语法: 子类.prototype = new 父类( )

优点:

  • 父类的 构造函数体内的 属性 和 原型 上的 方法 都可以继承下来

缺点:

  • 本该一个构造函数完成的所有参数, 需要分开两个地方传递

  • 同样都是给 子类实例 使用的属性, 在两个位置传递参数

  • 继承下来的属性不在自己身上, 子类的实例的所有属性分开了两部分书写

  • 访问的属性需要去到 __proto__ 上才能找到

// 父类
function Person(name, age) {
  this.name = name
  this.age = age
}
Person.prototype.sayHi = function () { console.log('hello world') }
const p = new Person('Jack', 18)
/*
  父类的实例 :

    p === {
      name: 'Jack',
      age: 18,
      __proto__(Person.prototype): {
        sayHi: function () {},
        constructor: Person,
        __proto__(Object.prototype): {
          __proto__: null
        }
      }
    }
  不管是谁继承了 Person 类
    + 要求继承的构造函数的实例, 可以使用 name 和 age 属性
    + 要求继承的构造函数的实例, 可以使用 sayHi 方法
*/

在这里插入图片描述

在这里插入图片描述

	function Person(name, age) {
	  this.name = name
	  this.age = age
	}

    // 当 Person构造函数书写完毕以后
    // 此时会伴生一个 Person.prototype 是一个对象数据类型
    // 我就可以向 Person.prototype 上添加一个成员
    // Person.prototype.a = 100
    // 我也可可以把 Person.prototype 从新赋值

    // 本身你的 Person.prototype 保存的是一个 对象数据类型 的地址
    // 我给你赋值为一个新的 对象数据类型 的地址
    // 就把你本身保存的地址覆盖了
    const obj = {
      message: '我是自定义的 obj 对象'
    }
    Person.prototype = obj

在这里插入图片描述

    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }




    function Student(gender) {
      this.gender = gender
    }

    const p = new Person('Jack', 18)
    Student.prototype = p

在这里插入图片描述

    // 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }


    // 子类
    function Student(gender) {
      this.gender = gender
    }

    const p = new Person('Jack', 18)
    
    // Student 的实例可以使用 Person 书写的属性和方法了
    // 我们就说 Student 继承自 Person
    // Student 是 Person 的 子类
    // Person 是 Student 的 父类
    Student.prototype = p


    // s 是 Student 的实例
    // s 所属的构造函数是 Student
    // s.__proto__ 指向 所属构造函数的 Prototype
    // 因为 Student.prototype === p
    // s.__proto__ === p
    const s = new Student('男')
    console.log(s)

在这里插入图片描述

在这里插入图片描述
根据原型链的规则, 对象访问
当你需要访问 s 的 gender 成员的时候,s 自己本身就有, 直接使用

console.log(s.gender) // 自己的, 男

在这里插入图片描述

当你需要访问 sname 成员的时候
s 自己本身没有, 需要去到 自己的 __proto__ 上查找,s 自己本身的 __proto__p, 所以就是在使用 p身上的name成员,pPerson 的实例,所以 Student 的实例使用的是 Person 构造函数体内书写的属性

    console.log(s.name)

在这里插入图片描述

当你需要访问 ssayHi 成员的时候

s 自己本身没有, 需要去到 自己的 __proto__ 上查找
因为自己的 __proto__(就是p) 上也没有, 再去到 p.__proto__上查找,其实这时候就是去 Person.prototype上查找的内容,此时 Student 的实例使用了 Person.protptype 上书写的 方法

s.sayHi()

在这里插入图片描述

总结:

   // 因为这句代码的执行
    // Student 的实例可以使用 Person 书写的属性和方法了
    // 我们就说 Student 继承自 Person
    // Student 是 Person 的 子类
    // Person 是 Student 的 父类
    Student.prototype = new Person('Jack', 18)

2. call 继承 : Person.call(this, name, age)

把父类构造函数体借用过来使用一下而已

function Student(name, age) {
      Person.call(this, name, age)
}

call 继承 ( 借用构造函数继承 ) :

  • 核心: 把父类构造函数体, 当做普通函数调用,

    利用 call 方法调用父类构造函数 并 改变 this 指向

  • 语法: 在子类构造函数体内书写 父类.call(this)

优点:

  • 一个实例使用的属性可以在一个位置传递参数

  • 可以把继承来的属性直接出现在 子类的实例 身上

缺点:

  • 只能继承 父类构造函数体内 书写的内容, 父类构造函数 原型上 的内容不能继承(就是继承不了sayHi了)
	// 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }



    // 子类
    function Student(gender, name, age) {
      this.gender = gender

      // 这里是 Student 的构造函数体内
      // 这里的 this 是 Student 的每个实例对象
      // 利用 call 调用 Person 构造函数, 把 Person 内的 this 修改为 Student 内的 this
      // 把 Person 内的 this 修改为 Student 的每一个实例对象
      // Person 构造函数体内书写成员就都添加到了 Student 的实例身上
      Person.call(this, name, age)
    }
    Student.prototype.play = function () { console.log('你好 世界') }
    
    const s = new Student('男', '张三', 20)
    console.log(s)

从小例子开始分析:

A.call(B,x,y)
改变函数A的this指向,使之指向B;

    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }

    // 既然 Person 可以当做普通函数调用
    // call 方法就是用来调用函数, 并且改变 this 指向的
    const obj = { message: '我是自定义的一个 obj 对象' }

    // 在调用 Person 的时候
    // 使用 call 方法, 改变Person的this指向,把 Person 内的 this 改成 obj
    // Person 内书写的两个代码添加的成员, 加在 obj 身上了
    Person.call(obj, 'Jack', 20)
    console.log(obj)

obj身上就有name和age了
在这里插入图片描述

    // 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }
    
    // 子类
    function Student(gender, name, age) {
      this.gender = gender
    }
    Student.prototype.play = function () { console.log('你好 世界') }

    const s = new Student('男', '张三', 20)


    // 因为 call 方法调用 Person 的时候, 把 Person 内的 this 修改为 s 了
    // 所以 name 和 age 成员添加到了 s 身上
    Person.call(s, 'Rose', 20)
    console.log(s)

在这里插入图片描述

    // 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }
    
    
    // 子类
    function Student(gender, name, age) {
      this.gender = gender

      // 这里是 Student 的构造函数体内
      // 这里的 this 是 Student 的每个实例对象
      // 利用 call 调用 Person 构造函数, 把 Person 内的 this 修改为 Student 内的 this
      // 把 Person 内的 this 修改为 Student 的每一个实例对象
      // Person 构造函数体内书写成员就都添加到了 Student 的实例身上
      Person.call(this, name, age)
    }
    Student.prototype.play = function () { console.log('你好 世界') }

    const s = new Student('男', '张三', 20)
    console.log(s)

在这里插入图片描述

    const s2 = new Student('女', '李四', 22)
    console.log(s2)

在这里插入图片描述

扩展:用apply写

在这里插入图片描述

3. 组合继承 :

就是把 原型继承 和 借用构造函数继承 两个方式组合在一起

function Student() {
    Person.call(this)
}
Student.prototype = new Person('Jack',20)
  • 核心: 把 原型继承 和 call 继承 合并在一起就做组合继承

  • 优点:

    => 父类构造函数体内的属性和原型上的方法都可以继承

    => 继承下来的属性在子类实例对象自己的身上

  • 缺点:

    => 原型上会多一套继承下来的属性名

原型继承 优缺点 :
在这里插入图片描述

call继承 优缺点:
在这里插入图片描述
两种方法的优缺点刚好互补

    // 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }
    
    // 子类
    function Student(gender, name, age) {
      this.gender = gender

      // 实现 call 继承
      // 目的: 为了把父类构造函数体内的内容放在子类实例自己身上
      Person.call(this, name, age)
    }

    // 实现原型继承
    // 目的: 为了把 Person 的 prototype 内的内容继承下来
    Student.prototype = new Person('Jack',20)

    Student.prototype.play = function () { console.log('你好 世界') }

    // 创建子类的实例
    const s = new Student('男', '张三', 20)
    console.log(s)

在这里插入图片描述
Student.prototype = new Person()可以不用传参数,因为Student自己有name,age,直接拿自己的了

    // 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }
    
    // 子类
    function Student(gender, name, age) {
      this.gender = gender

      // 实现 call 继承
      // 目的: 为了把父类构造函数体内的内容放在子类实例自己身上
      Person.call(this, name, age)
    }

    // 实现原型继承
    // 目的: 为了把 Person 的 prototype 内的内容继承下来
    Student.prototype = new Person()

    Student.prototype.play = function () { console.log('你好 世界') }

    // 创建子类的实例
    const s = new Student('男', '张三', 20)
    console.log(s)

在这里插入图片描述

4. ES6 的继承语法

下面表示创造一个 Student 类,继承自 Person 类

class Student extends Person {
    constructor (name, age) {
        // 必须在 constructor 里面执行一下 super() 完成继承 
        super(name, age)
    }
}

ES6 的继承方案 :

  • ES6 官方提出了 关键字 来实现继承

ES6 的继承语法:

  • 语法分成两个部分 :
  1. 书写子类的时候, 使用 extends 关键字

    => class 子类名 extends 父类 { ... }

  2. 在子类的 constructor 内书写

    => super( )

  • 注意:

    => 必须要两个条件同时书写
    => 在 constructor 内书写 super 的时候, 必须写在所有 this 的 最前面
    ( 必须要先继承 : 你必须要继承传统 , 然后再创新 )


	// ES6写法的父类
    // class Person {
    //   constructor (name, age) {
    //     this.name = name
    //     this.age = age
    //   }

    //   sayHi () { console.log('hello world') }
    // }


    // ES5写法的父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }


    // ES6 的类的继承
    // 创建一个 继承自 Person 的 Student 类
    // extends 关键字相当于原型继承
    class Student extends Person {
      constructor (gender, name, age) {
        // 相当于在调用父类构造函数体, 把 name 和 age 传递过去
        // 相当于 call 继承
        super(name, age)
        this.gender = gender
      }

      play () { console.log('你好 世界') }
    }

    const s = new Student('男', 'Jack', 20)
    console.log(s)

在这里插入图片描述

总结:

原型继承方法代码

    // 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }

    // 子类
    function Student(gender) {
      this.gender = gender
    }
     
    // 原型继承方法核心代码
    Student.prototype = new Person('Jack', 18)
    
    const s = new Student('男')
    console.log(s)

在这里插入图片描述

call继承方法代码

    // 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }


    // 子类
    function Student(gender, name, age) {
      this.gender = gender
      
	  // call 继承核心代码
      Person.call(this, name, age)
    }
    
    Student.prototype.play = function () { console.log('你好 世界') }

    const s = new Student('男', '张三', 20)
    console.log(s)

在这里插入图片描述

组合继承方法代码

    // 父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }
    
    // 子类
    function Student(gender, name, age) {
      this.gender = gender

      // 实现 call 继承
      // 目的: 为了把父类构造函数体内的内容放在子类实例自己身上
      Person.call(this, name, age)
    }

    // 实现原型继承
    // 目的: 为了把 Person 的 prototype 内的内容继承下来
    Student.prototype = new Person()

    Student.prototype.play = function () { console.log('你好 世界') }

    // 创建子类的实例
    const s = new Student('男', '张三', 20)
    console.log(s)

在这里插入图片描述

ES6 的继承语法

	// ES6写法的父类
    // class Person {
    //   constructor (name, age) {
    //     this.name = name
    //     this.age = age
    //   }

    //   sayHi () { console.log('hello world') }
    // }


    // ES5写法的父类
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.sayHi = function () { console.log('hello world') }


    // ES6 的类的继承
    // 创建一个 继承自 Person 的 Student 类
    // extends 关键字相当于原型继承
    class Student extends Person {
      constructor (gender, name, age) {
        // 相当于在调用父类构造函数体, 把 name 和 age 传递过去
        // 相当于 call 继承
        super(name, age)
        this.gender = gender
      }

      play () { console.log('你好 世界') }
    }

    const s = new Student('男', 'Jack', 20)
    console.log(s)

在这里插入图片描述






前端 JavaScript 之 继承 _ 原理版

JavaScript学习笔记(二十九)-- 闭包与继承

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值