js继承的七种常用方式

1.原型链继承

    function SuperType () {
      this.property = true
    }
    SuperType.prototype.getSuperValue = function () {
      return this.property
    }

    function SubType () {
      this.subproperty = false
    }
    // 让子类的原型等于父类的实例
    SubType.prototype = new SuperType()
    SubType.prototype.getSubValue = function () {
      return this.property
    }

    var instance = new SubType()
    console.log(instance.getSuperValue())  //true
    console.log(SubType.prototype.constructor === SuperType);  //true

基本思想:让子类的原型等于父类的实例。

优点:子类的实例可继承的属性——子类的实例的构造函数的属性、父类构造函数属性、父类原型的属性,但不会继承父类实例的属性

缺点

  1. 在创建子类的实例时,不能向父类的构造函数中传递参数。严格来说,是没有办法在不影响所有对象实例的情况下,给父类的构造函数传递参数
  2. 所有子类的实例都会共享父类的属性:包含引用类型值的原型属性会被所有实例共享。
  3. 继承方式单一。

2.构造函数继承

    function SuperType (age) {
      this.name = ['Mark','Jane','John']
      this.age = age
    }
    function SubType (age) {
      // 通过call()方法继承了SuperType,同时传递了参数
      SuperType.call(this, age)
    }

    let ins1 = new SubType(18)
    ins1.name.pop()
    console.log(ins1.name)    // ['Mark','Jane']

    let ins2 = new SubType(20)
    console.log(ins2.name)    // ['Mark','Jane','John']
    console.log(ins1.age, ins2.age); // 18 20

基本思想:通过call()或apply() 方法在子类构造函数的内部调用父类构造函数

优点

  1. 子类实例可以向父类构造函数传递参数
  2. 解决了包含引用类型值的原型属性会被所有实例共享的问题
  3. 使用call()或apply()可以同时继承多个构造函数的属性,解决了继承方式单一的问题

缺点

  1. 只能继承父类构造函数的属性。
  2. 每次使用都要重新调用,无法实现构造函数的复用。
  3. 每个子类实例都有父类构造函数的副本。

3.组合继承

    function SuperType (age) {
      this.name = ['Mark','Jane','John']
      this.age = age
    }
    SuperType.prototype.callAge = function () {
      alert(this.age)
    }
    function SubType (age) {
      // 第二次调用SuperType()
      SuperType.call(this, age)
    }
    // 第一次调用SuperType()
    SubType.prototype = new SuperType()
    SubType.prototype.constructor = SubType

    let ins1 = new SubType(18)
    ins1.name.pop()
    ins1.callAge()  // 18
    console.log(ins1.name)    // ['Mark','Jane']

    let ins2 = new SubType(20)
    ins2.callAge()  // 20
    console.log(ins2.name)    // ['Mark','Jane','John']
    console.log(ins1.age, ins2.age); // 18 20

基本思想:使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。

优点

  1. 可以复用、也可以传参、还可以继承父类原型上的属性
  2. 每个子类的实例引入的构造函数属性都是私有的。

缺点:调用了两次父类构造函数,而且子类的构造函数会代替原型上的父类构造函数

4.原型式继承

    function object (obj) {
      function fn () {}
      fn.prototype = obj
      return new fn()
    }
    
    let person = {
      name: 'Mark',
      hobbies: ['football', 'basketball', 'swimmming']
    }
    
    let p1 = object(person)
    p1.name = 'John'
    p1.hobbies.pop()

    let p2 = object(person)
    p2.name = 'Jane'
    p2.hobbies.push('volleyball')

    console.log(person.hobbies)  // ["football", "basketball", "volleyball"]

基本思想:在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。你可以根据具体需求对得到的对象加以修改。object.create() 方法规范化了原型式继承。
优点:本质上,object()对传入其中的对象执行了一次浅复制
缺点:类似于原型链继承,包含引用类型值的属性始终都会共享相应的值;而且子类实例属性都是后面才添加的,无法实现复用。

5.寄生式继承

    function object (obj) {
      function fn () {}
      fn.prototype = obj
      return new fn()
    }
    
    function createObj (obj) {
      // 通过调用函数创建一个新对象
      var clone = object(obj)
      // 以某种方式增强这个对象
      clone.callHello = function () {
        alert('Hello')
      }
      // 返回这个对象
      return clone
    }
    
    let person = {
      name: 'Mark',
      hobbies: ['football', 'basketball', 'swimmming']
    }
    
    let p1 = createObj(person)
    p1.callHello()  // Hello
    p1.name = 'John'
    p1.hobbies.pop()

    let p2 = createObj(person)
    p2.name = 'Jane'
    p2.hobbies.push('volleyball')
    p2.callHello()  // Hello

    console.log(person.hobbies)  // ["football", "basketball", "volleyball"]

基本思想:与寄生构造函数和工厂函数类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。可以理解为在原型式继承的基础上包了一层特殊的壳
优点:没有自定义类型,适合使用在主要考虑对象而不是自定义类型和构造函数的情况。
缺点:类似于构造函数继承,没有使用原型,不能做到函数复用而降低效率

6.寄生组合式继承

    function object (obj) {
      function fn () {}
      fn.prototype = obj
      return new fn()
    }
    
    // 寄生组合式继承的基本模式
    function inheritProtoType (subType, superType) {
      // 创建对象
      var prototype = object(superType.prototype)
      // 增强对象
      prototype.constructor = subType
      // 指定对象
      subType.prototype = prototype
    }
    
    function SuperType (name) {
      this.name = name
      this.hobbies = ['football', 'basketball', 'swimming']
    }

    SuperType.prototype.callName = function () {
      alert(this.name)
    }

    function SubType (name, age) {
      SuperType.call(this, name)
      this.age = age
    }

    inheritProtoType(SubType, SuperType)

    SubType.prototype.callAge = function () {
      alert(this.age)
    }

    let sub1 = new SubType('Mark', 20)
    sub1.callName() // 'Mark'
    sub1.callAge()  // 20
    console.log(sub1.hobbies);  // ['football', 'basketball', 'swimming']

基本思想:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。我们需要的是父类原型的一个副本,不必为了指定子类的原型而调用父类的构造函数。本质上,其实就是使用寄生式继承来继承父类的原型,然后再将结果指定给子类的原型。
优点只调用了一次父类构造函数,大大提高了效率。集寄生式继承和组合继承的优点于一身,是引用类型最理想的继承范式。

7.class继承

    class Phone{
      // 构造方法
      constructor(brand , price){
          this.brand = brand
          this.price = price
      }
      // 父类的成员属性
      call(){
        console.log('可以打电话')
      }
    }
    class SmartPhone extends Phone{
      // 构造方法
      constructor(brand , price , color , size){
        // 调用父类的方法
        super(brand , price)
        this.color = color
        this.size = size
      }
      photo(){
        console.log('可以拍照')
      }
      playgame(){
        console.log('可以玩游戏')
      }
      // 重写
      call(){
        console.log('可以进行视频通话')
      }
    }
    const iPhone = new SmartPhone('苹果',2999,'black','4.7inch')
    console.log(iPhone) // SmartPhone {brand: "苹果", price: 2999, color: "black", size: "4.7inch"}
    iPhone.call()       // 可以进行视频通话
    iPhone.photo()      // 可以拍照
    iPhone.playgame()   // 可以玩游戏

ES6中的class可以看作是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰,更加面向对象编程的语法而已。
详细请参见: 阮一峰的ES6入门 class的继承.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值