类的简单认识(二)

302 阅读7分钟

二.静态方法

  • 类相当于实例对象的原型对象,所以所有在类上定义的方法,都会被实例继承。
  • 如果在一个方法前加上static关键字,那么就表示该方法不会被实例继承,而是直接通过类来调用
 class Foo{
  static getName(){
   return 'yiye'
  }
 }
 // 1. 直接通过类来调用static方法
 console.log(Foo.getName()) //yiye
 // 2.通过类的实例来调用static方法会报错!
 // (intermediate value).getName is not a function
 console.log(new Foo().getName())
  • 如果在静态方法中包含this关键字,那么这个this指的是类,而不是实例!
 class Foo{
  static bar(){
  // static方法内部的this指的是类本身,而不是实例!
   return this.func()
  }
  // 一个类里面可以存在同名函数,一个是静态的(类直接调用),一个是普通的(类的实例调用)
  static func(){
   console.log('static方法是类直接调用')
  }
  // 类的实例来调用
  func(){
   console.log('普通方法是类的实例来调用')
  }
 }
 //1. static方法是类直接调用
 Foo.bar()
 // 2.类的实例来调用
 var f=new Foo()
 // 2.1 类的实例对象不可以调用静态方法
 // f.bar() //f.bar is not a function
 // 2.2 类的实例可以调用普通函数
 f.func() //普通方法是类的实例来调用
  • 父类的静态方法可以被子类继承,子类可以通过super.xxx()来调用父类的方法
 class Foo{
  static getName(){
   return 'foo'
  }
 }
 class child extends Foo{
  static print(){
   console.log('父类的名字是:'+super.getName())
  }
 }
 child.print() //父类的名字是:foo

三.实例属性的简洁写法

  • 实例属性除了可以定义在constuctor构造函数中之外。还可以定义在类的顶层和类的方法处于同一层级
 // 1. 构造器函数中饭定义的实例属性写在顶层
 class Foo{
  // 注意这里不要使用let/var/const(因为相当于属性了) !!!
    // 也不要使用this.xxx=xxx,这是在构造器函数内的写法
  name='foo';
  get val(){
   return this.name;
  }
  print(){
   console.log('name:'+this.name)
  }
 }
 var f=new Foo()
 console.log(f.name// foo
 console.log(f.val// foo
 f.print() //name:foo
  • 这样写的好处就在于一眼看过去就知道该类具有什么实例属性

四.静态属性

  • 在类的外部想要定义static静态属性/方法可以直接赋值
 class Foo{}
 // 1.静态属性
 Foo.a='yiye'
 console.log(Foo.a)  //yiye
 // 2.静态方法
 Foo.func=function(){
  return 'i am yiye'
 }
 console.log(Foo.func()) // i am yiye
  • 如果要定义实例方法(类的实例可以调用的方法)就通过prototype定义在类的原型上

五.私有方法和私有属性

  • 目前ES6还没有提供私有方法和私有属性,只有ts才能提供private关键字。
  • 替代方案是约定命名为_xxx(){}这种为私有方法,_xxz为私有属性
  • 另一种方法是使用Symbol来模拟,但是在外部依旧可以通过Reflect.ownKeys()来获取
 const bar=Symbol('bar')
 const foo=Symbol('foo')
 class F{
  // 私有方法
  [bar](){
   this[foo]='yiye'
   return this[foo]
  }
  // 公有方法
  getName(){
   console.log('ddd')
  }
 }
 var f=new F()
 f.getName()  // ddd
 console.log(f[bar]()) //yiye
 // 一般情况下,没办法获取类的[bar]方法
 console.log(Reflect.ownKeys(f)) //[Symbol(foo)]
 console.log(Reflect.ownKeys(f.__proto__)) // ["constructor", "getName", Symbol(bar)],而每个Symbol都是独一无二的,所以知道了值也没用,必须要得到所指的变量
 
 // 类的内部定义的方法默认都是不可枚举的!除非在类的原型上定义(默认是枚举的)
 console.log(Object.keys(f.__proto__)) //[]
 // {writable: true, enumerable: false, configurable: true, value: ƒ}
 console.log(Object.getOwnPropertyDescriptor(f.__proto__,'getName'))

六.new.target属性

  • new是从构造函数生成实例对象的命令,ES6为new命令引入了一个new.target属性
  • 如果构造函数不是通过new或者Reflect.constructor()调用的,net.target会返回undefined
  • 注意:如果是子类继承父类,那么父类内部的new.target返回的是子类!
    // 1. 对于函数来说
 function person(name){
  if(new.target!==undefined){
   this.name=name;
  }else{
   throw new Error('必须使用new命令来生成实例')
  }
 }
 var per=new person('yiye')
 console.log(per.name//yiye
 // 2. 对于func.call(xxx,'')来说
 // Error: 必须使用new命令来生成实例
 // var b=person.call(per,'yiye')
 
 // 3.类使用new.target返回的是自身
 class One{
  constructor(name) {
   if(new.target===One){
    console.log(name)
   }else{
    console.log(new.target)
    throw new Error('必须使用new命令生成类的实例')
   }
  }
 }
 new One('hh'// hh 
 
 // 4.子类继承父类,但是父类此时内部的new.target是子类
 class C extends One{
  constructor() {
      super('bb')
  }
 }
 new C() //Error: 必须使用new命令生成类的实例(所以执行到else抛出错误)
  • 也可以使用这个属性来实现抽象类,判断new.target为本类的时候报错,不为本类才可以和抽象类一致(只能被继承)

七.class的继承介绍

简介

  1. class可以通过extends实现继承,比起es5修改原型链实现继承要清晰
  2. class继承必须在构造器函数中使用super()方法,用于表示父类的构造函数,用于新建父类的this对象
  3. 子类必须在构造器函数中调用super方法是因为子类自己的this对象必须先通过父类的构造函数完成塑造,得到和父类相同的实例属性和方法
  4. 然后才能继续加上子类自己的实例属性和方法,如果不调用super方法,子类就得不到this对象,就没办法新建实例
 class A{
  constructor(name) {
      this.name=name;
  }
 }
/*  // 1. 子类调用super 
 class B extends A{
  constructor(name) {
      super(name)
  }
 }
 console.log(new B('hh').name);// hh */
 // 2.子类不调用super方法 
 class B extends A{
  constructor(name) {
      this.name=name;
      console.log(this.name)
  }
 }
 // 不调用super(),类是也可以使用的 b
 console.log('不调用super(),类也是可以使用的',B.name);
 // 但是!
 console.log(new B('aa').name)
 /* 
   Must call super constructor in derived class 
   before accessing 'this' or returning from derived constructor
  */
 /* 
  也就是子类必须在构造函数中调用super()
  而且必须在调用super()之后才能使用this关键字,一般在第一行代码调用
  如果不调用,没办法新建实例
  */
  • 注意:必须在调用super()之后才能使用this关键字(这是因为子类实例的构建基于父类实例,所以必须先使用super()调用父类实例)
  • 即使不声明构造器函数,class也会默认创建一个constructor函数
 class A{
  constructor(name) {
      this.name=name;
  }
 }
 // class B extends A{}
 // console.log(new B('hhhh').name)//hhhh
 // 等同于
 class B extends A{
  constructor(...args) {
      super(...args)
  }
 }
 console.log(new B('hhhh').name)//hhhh

子类会继承父类的属性和方法

  • 子类extends父类,所以子类.__proto__全等于父类,所以相当于父类挂载到子类原型链上
  • 此外,即使是父类的静态方法,也会被子类所继承的!
 // 子类extends父类,所以子类.__proto__全等于父类
 class Father{
  speak(){
   console.log('speak')
  }
 }
 
 class Child extends Father{
  eat(){
   console.log('eat')
  }
 }
 console.log(Child)
 console.log(Child.prototype)
 console.log(Child.__proto__ === Father// true
  • Object.getPrototypeOf()可以用于获取子类的父类 console.log(Object.getPrototypeOf(Child)) //class Father

本文参考阮一峰ES6教程