类的简单认识(三)

239 阅读7分钟

八. super

  • super关键字在js中一般是作为函数使用,但是也可以作为对象去调用父类的方法
  • 第一种情况:es6规定,子类的构造函数必须执行一次super函数
 class Father{
  constructor(){
   console.log(new.target.name);
  }
 }
 class Child extends Father{
  constructor(){
   super()
  }
 }
 new Father() //Father 
 new Child()  // Child 
 // 其实Child里面使用super()也就相当于Father.prototype.constructor.call(this)
 // 所以 new.target.name会是Child
  • 第二种情况:子类中使用super.xxx()就是把super作为一个对象使用
  • 注意:不能super.xx来获取属性!子类使用super.xxx()就是子类去调用该方法,this指向子类
 class Father{
  p(){
   console.log(this); //Child {}
   return 'Father'
  }
 }
 class Child extends Father{
  constructor() {
      super()
      console.log(super.p()) // Father
  }
 }
 new Child();
  • super.xx不能获取父类属性,是因为super指向的是父类的原型对象,所以定义在父类实例上的方法或者属性是无法通过super来调用的
 class Father{
  name = 'Father';
  func = function(){return '类中通过this.xxx=function(){}得到的是实例方法'}
  constructor() {
      this.age = 1;
  }
  protoFunc(){
   return '父类原型上的方法'
  }
 }
 class Child extends Father{
    // 注意下面用的是super来实际调用父类原型对象的属性/方法
  getAge(){
   return super.age
  }
  getName(){
   return super.name
  }
  getFunc(){
   return super.func()
  }
  getProtoFunc(){
   return super.protoFunc()
  }
 }
 let b = new Child()
 // 子类内部super指向的是父类的原型对象,所以父类原型对象有的属性或方法,子类才能调用!
 console.log(Father.prototype)
 // constructor: class Father
 // protoFunc: ƒ protoFunc()
 console.log(b.age)  // 1,此时能拿到靠的是原型链!
 console.log(b.name)  // Father,并没有通过super这个路径,而是直接通过原型链
 console.log(b.getAge()) // undefined
 console.log(b.getName()) //undefined
 // console.log(b.getFunc()) // is not a functinon,因为super.func()中的func()定义在父类的实例对象上,并不是在原型对象上
 console.log(b.getProtoFunc()) //父类原型上的方法
 
 // 如果把属性/方法定义到父类的原型对象上,那么super就能拿到!
 Father.prototype.age = 'prototype方式赋值'
 console.log(b.getAge()) //prototype方式赋值
  • super.xxx=xxx赋值实际上是对子类的属性进行赋值
 class A {
   constructor() {
     this.x = 1;
   }
 }
 
 class B extends A {
   constructor() {
     super();
     this.x = 2;
  // 相当于A.prototype.x.call(B,3)
  // 也就是B.x=3 => this.x=3
     super.x = 3;
  // super.xxx是无法获取到父类的属性的
     console.log(super.x); // undefined
     console.log(this.x); // 3
   }
 }
 
 let b = new B();
  • static静态方法中使用super,super此时指向的是父类而不是父类的原型对象
  class Father{
   // 在类的实例上
   /* getName = function(name){
    return '实例方法:'+name
   } */
   // 在类上
   static getName(name){
    return 'static:'+name;
   }
   // 在类的原型链上
   getName(name){
    return 'Father内部普通方法:'+name;
   }
  }
  // 子类
  class Child extends Father{
   // 在类上
   static getName(name){
    return '子类static:'+super.getName(name);
   }
   // 在子类的原型链上
   getName(name){
    return '子类:'+super.getName(name);
   }
  }
  // 1. 类直接调用static实例方法,内部使用super调用的就是父类的static实例方法
  console.log(Child.getName('1'))//子类static:static:1
  // 2. 子类的实例调用父类的实例方法
  // (其实内部getName = function(){}就等于下面的函数了!)
  // 所以下面的Father.prototype.getName= 可以去掉看看
  // Father.prototype.getName=function(name){
  //  return 'prototype:'+name
  // }
  // 这个使用是直接通过原型链去调用父类的实例方法,所以没有经过调用子类的方法的步骤!
  console.log(new Child().getName('2'))//实例方法:2
  
  // 3. 所以父类原型对象对应方法就是构造器函数+getName方法
  console.log(Father.prototype//{constructor: ƒ, getName: ƒ}
  
  // 4. 把父类的getName =funtion(){}注释
  // 所以先调用子类的实例方法,然后再调用子类的实例方法
  console.log(new Child().getName('3'))//子类:Father内部普通方法:3
  
  // 综上所说,子类的static静态方法会调用父类的静态方法
  // 子类的实例方法会调用父类的实例方法
  // 当然,前提是通过super.xxx()来调用
  • 由于对象总是继承其他对象的,所以在任意对象中都可以使用super关键字
 // 前提是支持es6的super关键字语法
 let obj = {
  toString(){
   console.log(super.toString())
  }
 }
 let father = {
  toString(){
   return 'father'
  }
 }
 obj.__proto__ = father; //挂在原型链上
 console.log(obj) // {toString: ƒ}
 obj.toString() // father 

类具有两条继承链!

  • 类作为构造函数的语法糖,同时具有prototype和__proto__两条继承链
  • 第一条:子类的__proto__属性 表示构造函数的继承,总是指向父类
  • 第二条:子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性
 class Father{}
 class Child extends Father{}
 console.log(Child.__proto__ === Father//true 
 console.log(Child.prototype.__proto__ === Father.prototype// true
 // 也就等于,因为Child.prototype === new Child().__proto__ 
 console.log(new Child().__proto__.__proto__ === new Father().__proto__//true
 // 相当于
 class A{
  getName(){
   return 'a'
  }
 }
 class B{}
 // B的原型继承A的原型
 Object.setPrototypeOf(B.prototype, A.prototype)
 // B的实例继承A的实例
 Object.setPrototypeOf(B, A)
 console.log(A.prototype// {constructor: ƒ}
 console.log(B.prototype// A {constructor: ƒ}
 console.log(new A().__proto__)
 console.log(new B().__proto__)
 // 因为B的实例继承了A的实例,所以B类没有的方法可以在A类寻找
 console.log(new B().getName()) // a
  • Object.setPrototypeOf方法的原理
 Object.setPrototypeOf = function(ab){
  a.__proto__ = b;
  return a;
 }

类可以继承有prototype属性的对象

  • 第一种情况:子类继承Object对象(也就是原生构造对象,可以new的,有构造器函数的对象)/函数/类
 // 1. 继承没有构造器的对象
/*  let obj = {name: 'yiye'}
 class Child extends obj{
  getName(){
   return this.name;
  }
 }
 console.log(new Child().getName())
 //  Class extends value #<Objectis not a constructor or null */
 // 2. 继承Object对象
/*  class Child extends Object{
  getName(){
   return this.toString();
  }
 }
 console.log(new Child().getName()) //[object Object]
  */
 // 3. 继承自定义构造器的对象(事实证明自定义的构造器对象失败,不了解构造器底层实现)
 // Class extends value #<constructor> is not a constructor or null
/*  let obj = {
  constructor(){
   this.name = 'yiye'
  }
 }
 class Child extends obj{
  getName(){
   return this.toString();
  }
 }
 console.log(new Child().getName()) */
 // 4. 子类继承函数
 function A(){}
 class Child extends A{
  getName(){
   return this.toString();
  }
 }
 console.log(new Child().getName()) //[object Object]
 // 4.1 给函数原型对象添加属性
 A.prototype.name = 'yiye'
 console.log(new Child().name) // yiye 
  • 第二种情况:类默认继承
 class Child{}
 // Child作为一个基类也就是相当于一个普通函数,原型链直接指向Function的原型
 console.log(Child.__proto__ === Function.prototype//true
 // 因为函数的原型对象的实例直接指向对象的原型
 console.log(Function.prototype.__proto__ === Object.prototype//true
 // 所以Child.__proto__替换成Child.prototype,也是true
 console.log(Child.prototype.__proto__ === Object.prototype//true

本文参考阮一峰ES6教程