Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Javascript之继承 #5

Open
feiyayshx opened this issue Mar 11, 2020 · 0 comments
Open

Javascript之继承 #5

feiyayshx opened this issue Mar 11, 2020 · 0 comments

Comments

@feiyayshx
Copy link
Owner

feiyayshx commented Mar 11, 2020

1. 相关概念及继承分类

  • 原型:

    • 显式原型:每次创建一个函数,该函数都会自带一个prototype属性,该属性就是原型,它指向一个对象,叫做原型对象;但是Function.prototype是个例外,它是原型对象,又是函数对象,作为一个函数对象,它没有prototype属性,其他函数都有prototype对象
    • 隐式原型:js中的对象都有一个内置的__proto_属性,指向创建该对象的构造函数的prototype
  • 原型链: 对于js对象都有一个内置的原型(proto)属性,当访问一个对象的属性或方法时,先从对象自身搜索,如果找不到,就从原型对象(proto)上搜索,该原型对象上也有自己的原型对象(proto),依次层层向上搜索,直到找到匹配的属性或方法返回, 如果找不到返回null,由原型(proto)属性构成的依次向上层原型对象进行搜索的链条就是原型链;原型链是实现继承的主要方法.

  • 构造函数,原型对象,实例对象的关系如图:

image

  • 继承方法分类如下图:

image

2. JS实现继承的方法

2.1 原型链继承: 重写原型对象,即使用父类的实例重写子类的原型对象

     // 定义父类构造函数
     function Animal() {
        this.name = '动物'
        this.colors = ['black', 'white', 'gray']
      }
      Animal.prototype.getName = function() {
        return this.name
      }

      // 定义子类构造函数
      function Cat() {
        this.language = '喵喵'
      }
      // 重写子类的原型对象
      Cat.prototype = new Animal()

      Cat.prototype.getLanguage = function() {
        return this.language
      }

      let cat1 = new Cat()
      cat1.colors.push('yellow')

      // 动物
      console.log(cat1.getName(),'cat1 getname')

      // 喵喵
      console.log(cat1.getLanguage())

      // ["black", "white", "gray", "yellow"]
      console.log(cat1.colors, 'cat1')  

      let cat2 = new Cat()

      // ["black", "white", "gray", "yellow"]
      console.log(cat2.colors, 'cat2')  

分析

  • 优点:多个实例共享父类的属性和方法
  • 缺点:某个实例对父类的引用属性进行了操作,其他实例对该属性的访问会被篡改;

2.2 构造函数继承:复制父类的实例给子类

     function Animal() {
        this.name = '动物'
        this.colors = ['black', 'white', 'gray']
      }
      Animal.prototype.getName = function() {
        return this.name
      }
      function Cat() {
        this.language = '喵喵'
        // 复制父类的实例给子类
        Animal.call(this)
      }
      Cat.prototype.getLanguage = function() {
        return this.language
      }
      let cat1 = new Cat()

      // TypeError: cat1.getName is not a function
      console.log(cat1.getName(),'cat1 getname')

      // 喵喵
      console.log(cat1.getLanguage())
      
      cat1.colors.push('yellow')
      console.log(cat1.colors, 'cat1') // ['black', 'white', 'gray','yellow']

      let cat2 = new Cat()
      console.log(cat2.colors, 'cat2') // ['black', 'white', 'gray']

分析:

  • 优点:父类的引用属性不会被共享;子类构建实例时可以向父类传递参数;
  • 缺点:
    • 只能继承父类实例属性和方法,不能继承原型对象上的属性和方法;
    • 无法实现复用,每个子类实例都有父类实例函数的副本,影响性能

2.3 组合继承:原型链继承和构造函数继承的结合,借用原型链实现对父类原型对象的属性和方法的继承,借用构造函数实现对父类实例属性和方法的继承

      function Animal() {
          this.name = '动物'
          this.colors = ['black', 'white', 'gray']
      }
      Animal.prototype.getName = function() {
        return this.name
      }
      function Cat() {
        this.language = '喵喵'

        // 复制父类的实例给子类
        Animal.call(this)
      }
      // 重写子类的原型对象
      Cat.prototype = new Animal()
      // or 优化
      Cat.prototype = Animal.prototype
      

分析:

  • 缺点:父类的构造函数执行了两次,实际没有必要

2.4 原型式继承:让新对象的原型对象指向参数对象

      let Animal = { name: '动物', colors: ['black', 'white', 'gray'] }
      let cat1 = Object.create(Animal)
      let cat2 = Object.create(Animal)
      // 动物
      console.log(cat1.name, 'cat1')
      // 动物
      console.log(cat2.name, 'cat2')

      cat1.colors.push('yellow')

      // ["black", "white", "gray", "yellow"]
      console.log(cat1.colors, 'cat1')
      // ["black", "white", "gray", "yellow"]
      console.log(cat2.colors, 'cat2')

分析:

  • 优点:复用参数对象上的属性和方法;
  • 缺点:多个实例对象共享参数对象的属性,有篡改可能

2.5 寄生式继承:使用原型式继承获得参数对象的浅复制,然后增强这个对象

      function Cat(target) {
        let cloneObj = Object.create(target)
        clone.language = function() {
          console.log('喵喵')
        }
        return cloneObj
      }
      let Animal = { name: '动物', colors: ['black', 'white', 'gray'] }
      let cat1 = new Cat(Animal)

      console.log(cat1.name)
      cat1.language()

分析:

  • 优点:实现继承的一种方式而已
  • 缺点:多个实例对象共享参数对象的属性,有篡改可能

2.6 寄生组合继承:解决组合继承会两次调用父类构造函数的问题;完美的一种继承方式

      function Animal() {
        this.name = '动物'
        this.colors = ['black', 'white', 'gray']
      }
      Animal.prototype.getName = function() {
        return this.name
      }
      function Cat() {
        this.language = '喵喵'
        Animal.call(this)
      }
      // 创建父类原型的一个副本,主要与父类原型对象隔离
      Cat.prototype = Object.create(Animal.prototype)

      // 重写原型后,修改constructor的指向
      Cat.prototype.constructor = Cat

      let cat1 = new Cat()
      // true
      console.log(cat1 instanceof Cat)

      // true
      console.log(cat1 instanceof Animal)

      // Animal {}
      console.log(cat1.__proto__) 

      // Cat {}
      console.log(cat1.constructor)

分析:

  • instanceof作用:判断一个对象是不是某个函数的实例; 例如:obj instanceof fn,实际上是判断fn的prototype是不是在obj的原型链上。

2.7 ES6 class extends: 寄生组合继承的语法糖

       class Animal {
          constructor() {
            this.name = '动物'
            this.colors = ['black', 'white', 'gray']
          }
        }
        class Cat extends Animal {
          constructor() {
            super()
            this.language = '喵喵'
          }
        }
        let cat1 = new Cat()
        console.log(cat1.name)

分析:

  • ES5继承:实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))
  • ES6继承:子类实例的创建是基于父类的,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错
  • super:
    • 作为函数调用时,代表父类的构造函数,ES6要求,子类的构造函数必须执行一次super函数。
    • 作为对象使用时,指向父类的原型对象

引用参考文章

一篇文章理解JS继承——原型链/构造函数/组合/原型式/寄生式/寄生组合/Class extends

再谈javascriptjs原型与原型链及继承相关问题

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant