JavaScript中的Class

  1. 类的基本语法
  2. 类的继承
  3. 修饰器

1. 类的基本语法

可以看成ES5中构造函数的语法糖,它的大部分功能ES5都可以做到

1.1 定义一个类

ES5

function Student(name,age){
    this.name = name
    this.age = age
}
Student.prototype = {
    constructor:Student,
    showName(){
        return this.name
    }
}

Class

class Student {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    showName() {
        return this.name
    }
}
console.log(Student.prototype);//{constuctor showName}
class的方法constructor与showName都是定义在原型上的,每个方法之间不用逗号隔开

1.2 constructor方法

class默认的方法,没有定义就默认加一个空的方法,在用new生成实例时自动调用该方法

class Student {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    showName() {
        return this.name
    }
}
const std1 = new Student("张三")
console.log(std1);//{name:"张三",age:undefined}
与ES5不同的是,定义类不存在变量提升,就是实例化一定要在定义之后

1.3 私有方法和私有属性

只在类的内部调用和使用的方法与属性
私有方法

const private1 = Symbol('private1')
const prop1 = Symbol('prop1')
class Studnet {
    //公有方法
    foo(parm1, parm2) {
        //内部使用私有方法
        this[private1](parm1)
        private2.call(this, parm2)
    }
    //私有方法
    [private1](parm) {
        return this[prop1] = parm
    }
}
使用Symbol或者外部的构造函数的方式,可以实现方法内部的私有,实例化的对象不能访问到这些方法

私有属性

class ClassA {
    constructor(prop1, prop2) {
        const prop = Symbol('prop')
        this[prop] = prop1
        this.age = prop2
    }
}
let obj = new ClassA(1)
console.log(obj[prop]); // ReferenceError
console.log(obj[(Object.getOwnPropertySymbols(obj)[0])]);// 1
使用Symbol可以实现一定意义上的私有属性,但是使用Object.getOwnPropertySymbols()方法仍然可以访问到这些属性
class Studnet {
    #a
    constructor(a) {
        this.#a = a
        this.a = a
    }
    get() {
        return this.#a
    }
}
let student = new Student()
console.log(Object.getOwnPropertyNames(student)) // ['a'] 并不能访问到属性#a
用#来标识私有属性,在外部不能直接访问这个属性

1.4 静态方法和静态属性

不能被实例继承的方法与属性
静态方法

class Foo {
    static fn1() {
        this.fn2()
    }
    static fn2() {
        console.log("Hello");
    }
    fn2() {
        console.log("World");
    }
}
Foo.fn1() //"Hello"
let foo = new Foo()
foo.fn2() //"World"
静态方法可以和实例方法重名,静态方法只能在类上调用
class Foo1 extends Foo {
}
Foo1.fn2()// "Hello"
子类可以继承父类的静态方法

静态属性
与静态方法相同,在实例属性前加static即可

class Foo {
    static _name = "a"
    _name = "b"
}

console.log(Foo._name);//"a"
let foo = new Foo()
console.log(foo._name);//"b"
静态方法可以与实例方法重名,实例不能继承到静态方法,它属于构造器,不属于原型对象
同样静态方法可以被子类继承

1.5 new.target属性

在类中指向该类,父类被继承时指向子类

class Foo {
    constructor() {
        console.log(new.target);
    }
}
class Foo1 extends Foo { }
let foo = new Foo() //Foo
let foo1 = new Foo1()//Foo1
  • 用该方法可以控制一些类必须在继承后才能使用

    class Foo {
      constructor() {
          if (new.target === Foo) {
              throw new Error("需要先继承")
          } else {
              console.log(new.target);
          }
      }
    }
    class Foo1 extends Foo { }
    let foo = new Foo() //Error 需要先继承
    let foo1 = new Foo1() //Foo1

2 Class的继承

使用extends关键字将父类中的属性与方法拷贝到子类

2.1 super关键字

  • 类继承的实质是先创建父类的实例对象this,再用子类的构造函数修改this
  • super就为子类提供了父类的this对象,相当于Parent.prototype.constructor.call(this),执行父类构造函数并修改this

    class Parent {
      constructor(x, y) {
          this.x = x
          this.y = y
      }
      getX() {
          console.log(this.x);
      }
    }
    class Child extends Parent {
      constructor(x, y, z) {
          super(x, y)
          this.z = z
      }
      getY() {
          console.log(this.y);
      }
    }
    let obj1 = new Child(1, 2, 3)
    console.log(obj1);//{x:1,y:2,z:3}
    obj1.getX()//1
    obj1.getY()//2
  • super指向父类的原型,可以作为一个对象去调用原型上的方法,并修改this指向子类

    class Parent {
      constructor(x) {
          this.x = x
          this.y = "a"
      }
      getY() {
          console.log(this.y);
      }
    }
    class Child extends Parent {
      constructor(x, y) {
          super(x)
          this.y = y
      }
      runner() {
          super.getY()
      }
    }
    let obj1 = new Child(1, 2)
    obj1.runner()//2
    super.getY()相当于Parent.prototype.getY.call(this)
    如果Child上没有y属性,则返回父类的y值为"a"
    如果Child上有y属性,但实例化的时候没有传值,则返回undefined,不会去原型链上找

2.2 原型链

class A { }
class B extends A { }
const a1 = new A()
const b1 = new B()

//子类的__proto__指向父类
B.__proto__ === A//true
//父类的原型对象是子类原型对象的原型
B.prototype.__proto__ === A.prototype//true
//父类实例的原型是子类实例的原型的原型就
b1.__proto__.__proto__ === a1.__proto__
关键还是在于第二个示例,父类的原型对象是子类原型对象的原型
因为类的原型对象就是实例对象的__proto__指向的对象,可以推出第三个示例

2.3混入(Minxin)

实现继承多个类,先将多个类混入到一个类中,再继承
Minxin的实现

//混入
function mix(...mixins) {
    class Mix { }
    for (let mixin of mixins) {
        copyProperties(Max, mixin)
        copyProperties(Max.prototype, mixin.prototype)
    }
    return Mix
}
//拷贝属性
function copyProperties(target, source) {
    for (let key of Reflect.ownKeys(source)) {
        if (key !== "constructor"
            && ket !== "prototype"
            && key !== "name"
        ) {
            let desc = Object.getOwnPropertyDescriptor(source, key);
            Object.defineProperty(target, key, desc)
        }
    }
}

ES6标准 阮一峰

如果只是方法的追加则使用Object.assign(target,source)即可

3 修饰器(Decorator)

修饰器可以用来修饰类的行为
修饰器还在提案中,但是babel转码器已经支持修饰器
可以安装babel的模块去编译 使用了修饰器的写法,转码成向下兼容的ES5写法,这样就可以得到想要的结果

  • 这里仅举一个类修饰器的例子

    @addStatic
    class A{}
    
    function addStatic(target){
      target.hello = "Hello"
    }
    target就是类A,@ + 修饰函数 相当于给类A添加一个静态属性hello
    属性修饰器以及其他内容不再举例

怼怼
73 声望6 粉丝