zoukankan      html  css  js  c++  java
  • [学习]es6-类

    JavaScript类

    类实质上是 JavaScript 现有的基于原型继承的语法糖。类的主体是在大括号中间的部分。

    class Animal { 
      constructor(name) {
        this.name = name;
      }
      speak() {
        return this;
      }
    }
    
    console.log(typeof Animal); // function
    console.log(Animal === Animal.prototype.constructor); // true
    

    上面的代码用es5的方式写:

    function Animal(name) {
      this.name = name;
    }
    Animal.prototype.speak = function()  {
      return this;
    }
    

    类声明和类表达式的主体都执行在严格模式下。比如,构造函数,静态方法,原型方法,getter和setter都在严格模式下执行。

    类声明

    类声明不像函数声明一样会提升,所以要先声明类,然后再使用。

    var rectangle = new Rectangle(); // 在类的声明语句之前使用类会报错
    class Rectangle {
     
    }
    

    类表达式

    类表达式可以是具名的,也可以是匿名的。

    具名类表达式

    一个具名类表达式的名称是类内的一个局部属性,它可以通过类本身(而不是类实例)的name属性来获取。

    // 具名类
    let Rectangle = class Rectangle2 {
        constructor(height, width) {
            this.height = height;
            this.width = width;
        }
    };
    console.log(Rectangle.name);
    // 输出: "Rectangle2"
    

    匿名类表达式

    // 匿名类
    let Rectangle = class {
        constructor(height, width) {
            this.height = height;
           this.width = width;
        }
    };
    console.log(Rectangle.name);
    // output: "Rectangle"
    

    类体和方法定义

    构造函数 constructor

    constructor方法是一个特殊的方法,这种方法用于创建和初始化一个由class创建的对象。
    一个类只能拥有一个名为 “constructor”的特殊方法。如果类包含多个constructor的方法,则将抛出 一个SyntaxError 。
    constructor方法默认返回实例对象(即this),可以手动改写指定返回另外一个对象。
    没有显式定义类的constructor方法,会默认为其添加一个空的constructor方法:constructor() {}。

    class Animal {
      // 没有显示定义Animal的constructor方法,会默认为其添加一个空的constructor方法。
      // constructor() {}
      speak() {
        console.log('Hello world!');
      }
    }
    console.log(Animal === Animal.prototype.constructor); // true
    // class Animal {} 实际上对应的是ES5中的 function Animal {} 写法
    

    super

    super关键字的规则比较复杂,既可以作为函数使用,也可以作为对象使用。

    super([arguments]); 
    
    super.functionOnParent([arguments]); 
    

    在子类中, 在使用'this'之前, 必须先调用super(), 否则, 将导致引用错误。

    class Polygon {
      constructor(height, width) {
        this.height = height;
        this.width = width;
      }
    }
    
    class Square extends Polygon {
      constructor(length) {
        // 注意: 在子类中, 在你使用'this'之前, 必须先调用super()。否则, 将导致引用错误。
        this.height = lenght; // ReferenceError,super 需要先被调用!
        
        // 这里,调用父类的构造函数, 
        super(length, length); 
      }
    }
    

    super在子类中使用的位置不一样,可调用的方法也不一样。

    • 在子类的构造函数中单独使用时,只能作为父类的构造函数调用:super(),此时其内部的this指向子类的实例
    • 在子类的构造函数中或者在子类的原型方法中作为对象使用时,此时super指向父类原型,通过super可以调用父类中的原型方法和访问父类原型上的属性,通过super调用的方法内部的this指向子类的实例(有一点要额外说明在子类构造函数中使用super可以修改子类实例的属性值,可以给子类实例添加属性)
    • 在子类的静态方法或者给子类的静态公有字段赋值时,此时super指向父类,通过super可以调用父类的静态方法和静态公有字段,通过super调用的父类静态方法中的this指向子类本身;

    使用super给公有实例字段赋值,super指向父类的原型。将在公有实例字段一节中说明。

    验证第一条和第二条的例子:

    class Polygon {
      constructor(height, width) {
        this.name = 'Rectangle';
        this.height = height || 0;
        this.width = width || 0;
        return this;
      }
      sayName() {
        console.log(this.name);
      }
      get area() {
        return this.height * this.width;
      }
      set area(value) {
        this._area = value;
      }
    }
    Polygon.prototype.pp = 100; // 定义在父类原型上的属性
    
    class Square extends Polygon {
      constructor(length) {
        // 这里,调用父类的构造函数, 
        // 但实质上是通过父类的构造函数给子类的实例添加了name,height,width属性
        // 再稍微深入一想,super作为函数使用时,在子类的构造函数中调用,其内部的this指向了子类的实例
        console.log(super(length, length)); // 打印出的是子类的实例
        this.test = 'test';
        this.name = 'Square';
        // 访问的是父类原型上的方法,方法内部的this同样指向了子类的实例
        super.sayName(); // Square
      }
      sayName() {
        super.sayName(); // Square
        console.log(super.pp); // 100 通过super访问父类原型上的属性
        console.log(super.constructor === Polygon); // true
      }
    }
    

    验证第三条的例子:

    class Animal {
      static test = 'ttt';
      constructor(name) {
        this.name = name;
        return this;
      }
      static printFather(str) {
        console.log('this is father' + '---' + str + '--' + this.name);
      }
      
    }
    
    class Dog extends Animal {
      static dd = super.test; // 还可以访问父类的静态公有字段 此时super指向父类自身
      pp = super.test; // 此时super指向父类的原型
      static printSon() {
        super.printFather('son'); // 调用父类的静态方法,此时printFather方法中的this指向了Dog类自身
      }
      constructor(name) {
        super(name);
      }
    }
    
    var ss = new Dog('pubby');
    Dog.printSon(); // this is father---son--Dog
    console.log(Dog.dd); // ttt
    console.log(ss.pp); // undefined
    

    对于第二条的额外说明的验证:

    class A {
      constructor() {
        this.x = 1;
        this.z = 12;
      }
    }
    
    class B extends A {
      constructor() {
        super();
        this.x = 2;
        // 根据结果来看,下面两条语句的super实际上是this
        super.x = 3;
        super.y = 4;
        console.log(super.x); // undefined,这里super指向了父类的原型
        console.log(this.x); // 3
      }
    }
    
    let b = new B();
    console.log(b.x); // 3
    console.log(b.y); // 4
    

    类主体中的严格模式

    类主体中的所有的函数、方法、构造函数、getters或setters都在严格模式下执行。

    class Animal { 
      speak() {
        return this;
      }
      static eat() {
        return this;
      }
    }
    
    let obj = new Animal();
    obj.speak(); // Animal {}
    let speak = obj.speak;
    speak(); // undefined  speak方法是在严格模式下声明的,单独调用时this不会指向全局对象
    
    Animal.eat() // class Animal
    let eat = Animal.eat;
    eat(); // undefined  eat方法是在严格模式下声明的,单独调用时this不会指向全局对象
    
    

    实例属性

    实例的属性一般定义在this上(也可以使用其他方法定义实例属性,下文会讲到):

    class Rectangle {
      constructor(height, width) {    
        this.height = height; // height是实例属性
        this.width = width; // width是实例属性
      }
      test() { // test方法定义在类的原型上(Rectangle.prototype)
        console.log('test');
      }
    }
    

    静态的和原型的数据属性:

    Rectangle.staticWidth = 20; // 静态属性 --- 相当于定义在类的构造函数上
    Rectangle.prototype.prototypeWidth = 25; // 原型属性 --- 定义在原型上
    
    

    字段声明

    • 公有字段
    • 公共方法
    • 私有字段
    • 私有方法

    公有字段

    静态公有字段

    静态公有字段用关键字static声明。
    我们声明的静态公有字段,本质上是使用Object.defineProperty方法添加到类的构造函数上。
    在类被声明之后,可以通过类的构造函数访问静态公有字段。

    class ClassWithStaticField {
      static staticField = 'static field';
      // 没有设置值的字段将默认被初始化为undefined。
      static staticParam;
    }
    
    console.log(ClassWithStaticField.staticField); // 输出值: "static field"​
    console.log(ClassWithStaticField.staticParam); // undefined
    

    静态公有字段不会在子类里重复初始化,但我们可以通过原型链访问它们。
    下面的代码中子类Dog通过原型链访问到了父类Animal上的静态公有字段test。

    class Animal {
      static test = 'ttt';
      constructor(name) {
        this.name = name;
      }
    }
    
    
    class Dog extends Animal {
      static tt = 'pp';
      constructor(name) {
        super(name);
      }
    }
    
    console.log(Dog.tt); // pp
    console.log(Dog.test); // ttt
    

    当初始化字段时,类主体中的this指向的是类的构造函数(前面验证过静态方法中的this也是指向类的构造函数)。

    class Animal {
      static test = 'ttt';
      static testOne = this.test; // 这里this指向Animal
      constructor(name) {
        this.name = name;
        this.test = 'instance';
      }
    }
    
    class Dog extends Animal {
      static tt = 'pp';
      constructor(name) {
        super(name);
        console.log(super.test); // undefined 此时super指向的是父类的原型Animal.prototype
      }
    }
    
    var dog = new Dog('pubby');
    console.log(Dog.testOne); // ttt
    

    公有实例字段

    公有实例字段是声明实例属性的另外一种方式。可以在类的构造函数之前声明,也可以在类的构造函数之后声明。
    注意,如果在类的构造函数之后声明公有实例字段,必须先在构造函数中调用父类的构造函数 -- super。

    class Animal {
      vv = 'vv';
    }
    
    class Dog extends Animal {
      oo = 'oo';
    }
    var animal = new Animal();
    var dog = new Dog();
    console.log(dog.oo); // oo
    console.log(animal.vv); // oo
    
    // 下面这种写法会报错。必须在构造函数中调用super()后才可以正常运行
    
    class Animal {
      vv = 'vv';
    }
    
    class Dog extends Animal {
      oo = 'oo'; 
      constructor() {}
    }
    var dog = new Dog();
    console.log(dog.oo);
    
    class Animal {
      vv = 'vv';
    }
    
    class Dog extends Animal {
      oo = super.test;
      constructor(name) {
        super();// super此时是父类的构造函数
      }
      xxx = 'xxx'; // 如果不先调用super()就声明xxx,将会报错
    }
    var dog = new Dog();
    console.log(dog.xxx); // xxx
    

    公有实例字段如果只声明而不赋值则会初始化为undefined。

    class Animal {
      vv;
    }
    var animal = new Animal();
    console.log(animal.vv); // undefined
    

    公有实例字段也可以由计算得出:

    var str = 'Person';
    class Person {
      ['the' + str] = 'Jack'; 
    }
    var person = new Person();
    console.log(person.thePerson); // Jack
    

    下面看一个复杂点的例子,注意例子中的this和super:

    class Animal {
      fatherClass = 'Animal';
      copyFatherClass = this.fatherClass;
    }
    Animal.prototype.childClass = 'i am a child';
    
    class Dog extends Animal {
      childClass = super.childClass;
    }
    var animal = new Animal();
    var dog = new Dog();
    console.log(animal.copyFatherClass); // Animal
    console.log(dog.childClass); // i am a child
    

    使用super给公有实例字段赋值,super指向父类的原型。

    公共方法

    静态方法

    在类中定义的方法都存在于原型上,都会被实例继承。但也有例外。
    如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
    不能通过一个类的实例调用静态方法。静态方法通常用于为一个应用程序创建工具函数。

    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
      static distance(a, b) {
        return a * b;
      }
    }
    
    const p1 = new Point(5, 5);
    console.log(Point.distance(10,20)); // 200
    console.log(p1.distance(10,20)); // TypeError: p1.distance is not a function
    

    如果静态方法包含this关键字,这个this指的是类本身(构造函数),而不是实例。

    class Foo {
      static oneFun() {
        this.baz(); // 在静态方法内,this指向类本身,而不是类的实例
      }
      static baz() {
        console.log('hello'); // 静态方法可以和实例方法重名,而不会覆盖实例方法
      }
      baz() {
        console.log('world');
      }
    }
    
    Foo.oneFun() // hello 
    

    父类的静态方法可以被子类继承:

    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    class Bar extends Foo {
    }
    
    Bar.classMethod() // 'hello'
    
    公共实例方法
    class Animal {
      say(str) {
        return str; 
      }
    }
    

    上面代码中的say方法就是一个公共实例方法。
    公共实例方法是定义在类的原型上的。

    getter和setter

    getter和setter是和类的属性绑定的特殊方法,分别会在其绑定的属性被取值、赋值时调用。
    使用get和set句法定义实例的公共getter和setter。

    // MDN上的例子
    class ClassWithGetSet {
      #msg = 'hello world'; // 这是一个私有字段
      get msg() {
        return this.#msg;
      }
      set msg(x) {
        this.#msg = `hello ${x}`;
      }
    }
    
    const instance = new ClassWithGetSet();
    console.log(instance.msg); // hello worl​d
    
    instance.msg = 'cake';
    console.log(instance.msg); // hello cake
    
    console.log(instance.hasOwnProperty('msg')); // false
    
    

    下面的例子是《ECMAScript 6 入门》里的,解释了类中getter和setter的本质:

    class CustomHTMLElement {
      constructor(element) {
        this.element = element;
      }
    
      get html() {
        return this.element.innerHTML;
      }
    
      set html(value) {
        this.element.innerHTML = value;
      }
    }
    
    var descriptor = Object.getOwnPropertyDescriptor(
      CustomHTMLElement.prototype, "html"
    );
    
    "get" in descriptor  // true
    "set" in descriptor  // true
    

    公共实例方法也可以是一个Generator函数。

    私有字段

    声明私有字段的时候需要加上#前缀。
    例如:
    #test是一个私有字段,#与后面的test是一个整体,使用的时候要写完整。
    也可就是说#test与test是完全不同的两个字段。

    静态私有字段

    静态私有字段需要使用static关键字声明;

    class Animal {
      static #test = 'doom';
    
      ppp() {
        return Animal.#test;
      }
    }
    var animal = new Animal();
    console.log(animal.ppp()); // 'doom'
    

    静态私有字段必须在声明他的类的内部使用(在哪个类内部声明,在哪个类的内部使用,不参与继承):

    // 虽然子类继承了父类的静态方法,但是并没有继承父类的静态私有属性,也不能使用他
    class BaseClassWithPrivateStaticField {
      static #PRIVATE_STATIC_FIELD;
    
      static basePublicStaticMethod() {
        this.#PRIVATE_STATIC_FIELD = 42;
        return this.#PRIVATE_STATIC_FIELD;
      }
    }
    
    class SubClass extends BaseClassWithPrivateStaticField { };
    console.log(SubClass.basePublicStaticMethod());// 报错:Cannot write private member #PRIVATE_STATIC_FIELD to an object whose class did not declare it
    
    
    私有实例字段

    声明私有实例字段要使用#前缀,不使用static关键字。
    私有实例字段也只能在类的内部使用。

    class Animal {
      #test = 'test';
      say() {
        console.log(this.#test);
      }
    }
    
    var animal = new Animal();
    animal.say();
    console.log(animal.#test); // SyntaxError
    

    私有方法

    私有方法的内容目前还是提案,在node 12版本中还不支持

    静态私有方法

    静态私有方法使用static关键字进行声明,需要添加#前缀。
    静态私有方法与静态方法一样,只能通过类本身调用,并且只能在类的声明主体中使用:

    class Animal {
      static #test() {
        console.log('static #test call');
      }
      ttt() {
        Animal.#test();
      }
    }
    var animal = new Animal();
    animal.ttt();
    
    私有实例方法

    私有实例方法在类的实例中可用,它的访问方式的限制和私有实例字段相同。

    class ClassWithPrivateMethod {
      #privateMethod() {
        return 'hello world';
      }
    
      getPrivateMessage() {
          return #privateMethod();
      }
    }
    
    const instance = new ClassWithPrivateMethod();
    console.log(instance.getPrivateMessage());
    // 预期输出值: "hello worl​d"
    

    私有实例方法可以是生成器、异步或者异步生成器函数。私有getter和setter也是可以的:

    class ClassWithPrivateAccessor {
      #message;
    
      get #decoratedMessage() {
        return `✨${this.#message}✨`;
      }
      set #decoratedMessage(msg) {
        this.#message = msg;
      }
    
      constructor() {
        this.#decoratedMessage = 'hello world';
        console.log(this.#decoratedMessage);
      }
    }
    
    new ClassWithPrivateAccessor();
    // 预期输出值: "✨hello worl​d✨"
    

    参考资料

    1. ECMAScript 6 入门
  • 相关阅读:
    升级Visual Studio安装已完成,出现警告
    C# 字符串格式
    C# TimeSpan
    git 手册
    Mac clion 远程调试 linux 服务器进程
    C++ 左值引用和右值引用的用处
    Mac OS 快捷键
    MacPorts Guide
    同步异步阻塞非阻塞
    tcp 简单实现
  • 原文地址:https://www.cnblogs.com/fogwind/p/12858608.html
Copyright © 2011-2022 走看看