仿类结构

先创建一个构造器函数,然后在这个函数的原型中存储方法,这个构造器函数生成的实例从原型继承了存储的方法。

function Con(n) {
  this.n = n;
}

Con.prototype.fun = function () {
  console.log(this.n);
};

let c = new Con("zzz");
c.fun(); // "zzz"

类的声明

class Name {
  constructor(name) {
    this.name = name;
  }

  say() {
    console.log(this.name);
  }
}

let n = new Name("xqy");
n.say();    // "xqy"
typeof Name // "function"

声明的类本质上还是一个函数,类的方法依然作为属性存储在这个函数的原型中。

自有属性(own properties)是定义在实例而不是原型上的属性,一般在类的构造器中进行声明。

类声明不会发生自举,和 let 定义变量类似。
类声明默认在严格模式下执行,不会退出严格模式。
类中定义的方法(不包括构造器)不可枚举。
类中的方法如果没有 [[Construct]] 属性,则不能使用 new 调用,使用 new 调用会抛出错误。
类中的构造器只能使用 new 调用,不使用 new 调用会抛出错误。
类名在类的内部视为 const 变量,不可更改,否则抛出错误。但是类名在类的外部视为 let 定义的变量,可以更改。

类表达式

与上面类声明等价的类表达式为

let Name = class {
  constructor(name) {
    this.name = name;
  }

  say() {
    console.log(this.name);
  }
}

创建有名称的类表达式

let Name = class Inner {
  constructor(name) {
    this.name = name;
  }

  say() {
    console.log(this.name);
  }
}

typeof Name  // "function"
typeof Inner // undefined

Inner 是具名类表达式的名称,这个名称相当于是在类的内部定义,在类的外部无法访问,所以返回结果为 undefined。

类可以当作值来使用,也可以作为函数参数、函数返回值,给变量赋值。

立即调用类构造器创建单例

let n = new class {
  constructor(name) {
    this.name = name;
  }

  say() {
    console.log(this.name);
  }
}("xyz");

n.say(); // "xyz"

在创建类的时候传递参数实例化该类,该类的自有属性已经被固定,同时该类也只有一个实例和一个引用。

访问器属性

访问器属性实质上是在原型中定义的。下面的代码片段中 getValue()setValue(v) 方法都是定义在 Num 的原型中。value 是自有属性,定义在实例上。

class Num {
  constructor(v) {
    this.value = v;
  }
  
  get value() {
    return this.value;
  }
  
  set value(v) {
    this.value = v;
  }
}

计算的成员名

类中的方法名和访问器属性可以使用需计算的名称。

let mName = "say",
    n = "num";
class Num {
  constructor(name, v) {
    this.name = name;
    this.value = v;
  }
  
  [mName]() {
    console.log(this.name);
  }
  
  get [n]() {
    return this.value;
  }
  
  set [n](v) {
    this.value = v;
  }
}

let n = Num("xyz", 25);
n.say(); // "xyz"

生成器方法

在类中可以创建生成器。为 Symbol.iterator 属性定义一个生成器作为默认的迭代器。

class Coll {
  constructor() {
    this.items = [];
  }
  
  *[Symbol.iterator]() {
    yield *this.items.values();
  }
}

静态成员

类本身具有的方法或属性称为静态成员,静态成员不属于类的原型。访问静态成员不能使用类实例而需要直接使用类。类成员前面添加 static 表示该成员是静态的,构造器不允许是静态成员。

class Name {
  constructor(name) {
    this.name = name;
  }
  
  // 等价于 Name.prototype.say()
  say() {
    console.log(this.name);
  }
  
  // 等价于 Name.create()
  static create(name) {
    return new Name(name);
  }
}

let n = Name.create("xyz");

派生类继承

类的继承使用 extends

class Rect {
  constructor(length, width) {
    this.length = length;
    this.width = width;
  }
  
  getArea() {
    return this.length * this.width;
  }
}

class Square extends Rect {
  constructor(length) {
    super(length, length);
  }
}

let s = new Square(3);
s.getArea(); // 9

子类中如果自定义构造器,则必须在构造器中调用 super() 初始化基类,super 表示基类。如果不定义构造器,则默认构造器会在用 new 创建子类时,将传入子类的所有参数作为 super 的参数调用 super()

只能在子类中使用 super()。在构造器中必须在 super() 语句调用之后才能访问 this,因为 this 需要 super() 调用之后才会初始化。构造器中不使用 super() 的唯一方式是构造器返回一个对象。

子类中定义与基类同名的方法时,子类中的方法会屏蔽基类中的同名方法。如果此时需要调用基类中被屏蔽的方法,使用 super.method() 访问。

类继承中,基类中的静态成员会被子类继承。

具有 [[Construct]] 属性和原型的函数可以作为基类使用 extends 被继承。任何结果(返回值)为前述函数的表达式(函数)可以位于 extends 后面使用。

继承内置对象时,this 的值先由基类创建,再被子类修改。子类的行为默认与基类(内置对象)一致。返回内置对象实例的方法如果接收的参数为继承自内置对象的子类,则该方法返回的实例类型为这个子类。

Symbol.species 是返回一个函数的静态访问器属性。类的方法(构造器除外)需要创建一个新实例时,会调用 Symbol.species 返回的函数作为构造器创建这个类实例。

内置类型 Array、ArrayBuffer、Map、Promise、RegExp、Set、类型化数组的默认 Symbol.species 返回的是 this。

class Base {
  constructor(v) {
    this.value = v;
  }
  
  // this.constructor[Symbol.species] 为 Symbol.species 返回的函数
  duplicate() {
    return new this.constructor[Symbol.species](this.value);
  }
  
  static get [Symbol.species]() {
    return this;
  }
}

class Sub1 extends Base {}
class Sub2 extends Base {
  static get [Symbol.species]() {
    return Base;
  }
}

let sub1 = new Sub1("sub1")
    ds1 = sub1.duplicate();  // 创建的对象类型为 Sub1
let sub2 = new Sub2("sub2"),
    ds2 = sub2.duplicate();  // 创建的对象类型为 Base

Symbol.species 返回的函数决定类的方法创建新实例时该实例的类型。

构造器中使用 new.target

使用 new 创建类实例时, new.target 的值为 new 的目标类型,也就是类。所以当使用 new 实例化类时,在构造器中 new.target 总存在值。

在基类的构造器中使用了 new.target 的前提下,用 new 实例化子类时,子类的构造器会调用基类的构造器,此时 new.target 的值是子类而不是基类,可能基类中 new.target 的使用在这种情况下会出现意料之外的结果。

参考

[1] Zakas, Understanding ECMAScript 6, 2017.

 posted on 2024-05-31 16:11  x-yun  阅读(6)  评论(0编辑  收藏  举报