├── Class 的基本语法
│ ├── 简介
│ │ └─ 类的由来
│ │ └─ 可以看到里面有一个`constructor()`方法,这就是构造方法,而`this`关键字则代表实例对象。这种新的 Class 写法,本质上与本章开头的 ES5 的构造函数`Point`是一致的。
│ │ └─ `Point`类除了构造方法,还定义了一个`toString()`方法。注意,定义`toString()`方法的时候,前面不需要加上`function`这个关键字,直接把函数定义放进去了就可以了。另外,方法与方法之间不需要逗号分隔,加了会报错。
│ │ └─ 类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
│ │ └─ `prototype`对象的`constructor()`属性,直接指向“类”的本身
│ │ └─ toString()`方法是`Point`类内部定义的方法,它是不可枚举的`, 采用 ES5 的写法,`toString()`方法就是可枚举的。
│ │ └─ constructor 方法
│ │ └─ `constructor()`方法是类的默认方法,通过`new`命令生成对象实例时,自动调用该方法。一个类必须有`constructor()`方法,如果没有显式定义,一个空的`constructor()`方法会被默认添加。
│ │ └─ 定义了一个空的类`Point`,JavaScript 引擎会自动为它添加一个空的`constructor()`方法。 `constructor()`方法默认返回实例对象(即`this`),完全可以指定返回另外一个对象。
│ │ └─ 类的实例
│ │ └─ 生成类的实例的写法,与 ES5 完全一样,也是使用`new`命令。前面说过,如果忘记加上`new`,像函数那样调用`Class`,将会报错。
│ │ └─
│ │ └─ 取值函数(getter)和存值函数(setter)
│ │ └─ 与 ES5 一样,在“类”的内部可以使用`get`和`set`关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
│ │ └─ 存值函数和取值函数是设置在属性的 Descriptor 对象上的。 存值函数和取值函数是定义在`html`属性的描述对象上面,这与 ES5 完全一致。
│ │ └─ 属性表达式
│ │ └─ 类的属性名,可以采用表达式。
│ │ └─
│ │ └─ Class 表达式
│ │ └─ 与函数一样,类也可以使用表达式的形式定义。
│ │ └─ 上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是`Me`,但是`Me`只在 Class 的内部可用,指代当前类。在 Class 外部,这个类只能用`MyClass`引用。
│ │ └─ 注意点
│ │ └─ (1)严格模式
│ │ └─ 类和模块的内部,默认就是严格模式,所以不需要使用`use strict`指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
│ │ └─
│ │ └─ (2)不存在提升
│ │ └─ 类不存在变量提升(hoist),这一点与 ES5 完全不同。
│ │ └─ `Foo`类使用在前,定义在后,这样会报错,因为 ES6 不会把类的声明提升到代码头部。这种规定的原因与下文要提到的继承有关,必须保证子类在父类之后定义。
│ │ └─ (3)name 属性
│ │ └─ 由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被`Class`继承,包括`name`属性。
│ │ └─ `name`属性总是返回紧跟在`class`关键字后面的类名。
│ │ └─ (4)Generator 方法
│ │ └─ 如果某个方法之前加上星号(`*`),就表示该方法是一个 Generator 函数。
│ │ └─ `Foo`类的`Symbol.iterator`方法前有一个星号,表示该方法是一个 Generator 函数。`Symbol.iterator`方法返回一个`Foo`类的默认遍历器,`for...of`循环会自动调用这个遍历器。
│ │ └─ (5)this 的指向
│ │ └─ 类的方法内部如果含有`this`,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
│ │ └─ `printName`方法中的`this`,默认指向`Logger`类的实例。但是,如果将这个方法提取出来单独使用,`this`会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是`undefined`),从而导致找不到`print`方法而报错。
│ │ └─ 一个比较简单的解决方法是,在构造方法中绑定`this`,这样就不会找不到`print`方法了。
│ │ └─ 另一种解决方法是使用箭头函数。 箭头函数内部的`this`总是指向定义时所在的对象。上面代码中,箭头函数位于构造函数内部,它的定义生效的时候,是在构造函数执行的时候。这时,箭头函数所在的运行环境,肯定是实例对象,所以`this`会总是指向实例对象。
│ │ └─ 还有一种解决方法是使用`Proxy`,获取方法的时候,自动绑定`this`。
│ ├── 静态方法
│ │ └─ 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上`static`关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
│ │ └─ 注意,如果静态方法包含`this`关键字,这个`this`指的是类,而不是实例。
│ │ └─ 。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。
│ │ └─ 父类的静态方法,可以被子类继承。
│ │ └─ 静态方法也是可以从`super`对象上调用的。
│ ├── 实例属性的新写法
│ │ └─ 实例属性除了定义在`constructor()`方法里面的`this`上面,也可以定义在类的最顶层。
│ │ └─ 这种新写法的好处是,所有实例对象自身的属性都定义在类的头部,看上去比较整齐,一眼就能看出这个类有哪些实例属性。
│ ├── 静态属性
│ │ └─ 静态属性指的是 Class 本身的属性,即`Class.propName`,而不是定义在实例对象(`this`)上的属性。
│ │ └─ ES6 明确规定,Class 内部只有静态方法,没有静态属性。
│ │ └─ 提供了类的静态属性,写法是在实例属性的前面,加上`static`关键字。
│ ├── 私有方法和私有属性
│ │ └─ 现有的解决方案
│ │ └─ 一种做法是在命名上加以区别。
│ │ └─ 另一种方法就是索性将私有方法移出类,因为类内部的所有方法都是对外可见的。
│ │ └─ 还有一种方法是利用`Symbol`值的唯一性,将私有方法的名字命名为一个`Symbol`值。
│ │ └─
│ │ └─ 私有属性的提案
│ │ └─ 为`class`加了私有属性。方法是在属性名之前,使用`#`表示。
│ │ └─ `#count`就是私有属性,只能在类的内部使用(`this.#count`)。如果在类的外部使用,就会报错。
│ │ └─ 之所以要引入一个新的前缀`#`表示私有属性,而没有采用`private`关键字,是因为 JavaScript 是一门动态语言,没有类型声明,使用独立的符号似乎是唯一的比较方便可靠的方法,能够准确地区分一种属性是否为私有属性。另外,Ruby 语言使用`@`表示私有属性,ES6 没有用这个符号而使用`#`,是因为`@`已经被留给了 Decorator。
│ │ └─ 这种写法不仅可以写私有属性,还可以用来写私有方法。
│ │ └─ 另外,私有属性也可以设置 getter 和 setter 方法。
│ │ └─ in 运算符
│ │ └─ `try...catch`结构可以用来判断是否存在某个私有属性。
│ │ └─ 判断私有属性时,`in`只能用在定义该私有属性的类的内部。
│ │ └─ `in`也可以跟`this`一起配合使用。
│ │ └─ 类`A`的私有属性`#foo`,只能在类`A`内部使用`in`运算符判断,而且只对`A`的实例返回`true`,对于其他对象都返回`false`。
│ │ └─ 子类从父类继承的私有属性,也可以使用`in`运算符来判断。
│ │ └─ `in`运算符对于`Object.create()`、`Object.setPrototypeOf`形成的继承,是无效的,因为这种继承不会传递私有属性。
│ │ └─ 对于修改原型链形成的继承,子类都取不到父类的私有属性,所以`in`运算符无效。
│ ├── 静态块
│ │ └─ 静态属性的一个问题是,它的初始化要么写在类的外部,要么写在`constructor()`方法里面。
│ │ └─ 允许在类的内部设置一个代码块,在类生成时运行一次,主要作用是对静态属性进行初始化。
│ │ └─ static 代码块,这就是静态块。它的好处是将静态属性`y`和`z`的初始化逻辑,写入了类的内部,而且只运行一次。
│ │ └─ 每个类只能有一个静态块,在静态属性声明后运行。静态块的内部不能有`return`语句。
│ │ └─ 静态块内部可以使用类名或`this`,指代当前类。
│ │ └─ 除了静态属性的初始化,静态块还有一个作用,就是将私有属性与类的外部代码分享。
│ │ └─ `#x`是类的私有属性,如果类外部的`getX()`方法希望获取这个属性,以前是要写在类的`constructor()`方法里面,这样的话,每次新建实例都会定义一次`getX()`方法。现在可以写在静态块里面,这样的话,只在类生成时定义一次。
│ ├── new.target 属性
│ │ └─ `new`是从构造函数生成实例对象的命令。ES6 为`new`命令引入了一个`new.target`属性,该属性一般用在构造函数之中,返回`new`命令作用于的那个构造函数。如果构造函数不是