site infoHacknerd | Tech Blog
blog cover

🚙 [ECAMScript] 22.class

ES6JavaScript

由来

JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。

javascriptCopy
function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

类的实例

属性实例新写法

ES2022 为类的实例属性,又规定了一种新写法。实例属性现在除了可以定义在constructor()方法里面的this上面,也可以定义在类内部的最顶层。

javascriptCopy
// 原来的写法
class IncreasingCounter {
  constructor() {
    this._count = 0;
  }
  get value() {
    console.log('Getting the current value!');
    return this._count;
  }
  increment() {
    this._count++;
  }
}

属性名表达式

类的属性名,可以采用表达式。

javascriptCopy
let methodName = 'getArea';

class Square {
  constructor(length) {
    // ...
  }

  [methodName]() {
    // ...
  }
}

class表达式

与函数一样,类也可以使用表达式的形式定义。

javascriptCopy
const MyClass = class Me {
  getClassName() {
    return Me.name;
  }
};

静态属性静态方法

javascriptCopy
class Foo {
	static a = 1;
  static classMethod() {
    return 'hello';
  }
}

私有方法私有属性

ES2022正式为class添加了私有属性,方法是在属性名之前使用#表示。

javascriptCopy
class IncreasingCounter {
  #count = 0;
  get value() {
    console.log('Getting the current value!');
    return this.#count;
  }
  increment() {
    this.#count++;
  }
}

in 运算符 

直接访问某个类不存在的私有属性会报错,但是访问不存在的公开属性不会报错。这个特性可以用来判断,某个对象是否为类的实例。

javascriptCopy
class C {
  #brand;

  static isC(obj) {
    try {
      obj.#brand;
      return true;
    } catch {
      return false;
    }
  }

静态块

静态属性的一个问题是,如果它有初始化逻辑,这个逻辑要么写在类的外部,要么写在constructor()方法里面。这两种方法都不是很理想,前者是将类的内部逻辑写到了外部,后者则是每次新建实例都会运行一次。

为了解决这个问题,ES2022 引入了静态块(static block),允许在类的内部设置一个代码块,在类生成时运行且只运行一次,主要作用是对静态属性进行初始化。以后,新建类的实例时,这个块就不运行了

javascriptCopy
class C {
  static x = ...;
  static y;
  static z;

  static {
    try {
      const obj = doSomethingWith(this.x);
      this.y = obj.y;
      this.z = obj.z;
    }
    catch {
      this.y = ...;
      this.z = ...;
    }
  }
}

注意点

  • 1.严格模式。类和模块的内部,默认就是严格模式
  • 2.不存在变量提升。这种规定的原因与继承有关,必须保证子类在父类之后定义。
  • javascriptCopy
    new Foo(); // ReferenceError
    class Foo {}

  • 1.name属性 由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。
  • javascriptCopy
    class Point {}
    Point.name // "Point"

  • 1.Generator 方法
  • javascriptCopy
    class Foo {
      constructor(...args) {
        this.args = args;
      }
      * [Symbol.iterator]() {
        for (let arg of this.args) {
          yield arg;
        }
      }
    }

  • 1.this 指向
  • javascriptCopy
    class Logger {
      printName(name = 'there') {
        this.print(`Hello ${name}`);
      }
    
      print(text) {
        console.log(text);
      }
    }
    
    const logger = new Logger();
    const { printName } = logger;
    printName(); // TypeError: Cannot read property 'print' of undefined

    上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到print方法而报错。 一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了。

    javascriptCopy
    class Logger {
      constructor() {
        this.printName = this.printName.bind(this);
      }
    
      // ...
    }

    另一种解决方法是使用箭头函数。

    javascriptCopy
    class Obj {
      constructor() {
        this.getThis = () => this;
      }
    }
    
    const myObj = new Obj();
    myObj.getThis() === myObj // true

    new.target

    ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。

    Class 内部调用new.target,返回当前 Class。

    javascriptCopy
    class Rectangle {
      constructor(length, width) {
        console.log(new.target === Rectangle);
        this.length = length;
        this.width = width;
      }
    }
    
    var obj = new Rectangle(3, 4); // 输出 true

    需要注意的是,子类继承父类时,new.target会返回子类。

    javascriptCopy
    class Rectangle {
      constructor(length, width) {
        console.log(new.target === Rectangle);
        // ...
      }
    }
    
    class Square extends Rectangle {
      constructor(length, width) {
        super(length, width);
      }
    }
    
    var obj = new Square(3); // 输出 false

    Contents

    • 由来
    • 类的实例
    • 属性实例新写法
    • 属性名表达式
    • class表达式
    • 静态属性静态方法
    • 私有方法私有属性
    • in 运算符 
    • 静态块
    • 注意点
    • new.target

    2024/03/23 01:00