类基础
class Person {}
const Animal = class {};
类在声明之前如果被调用了就会报错
console.log(ClassDeclaration);
class ClassDeclaration {}
console.log(ClassDeclaration);
类受块作用域限制
{
function FunctionDeclaration() {}
class ClassDeclaration {}
}
console.log(FunctionDeclaration);
console.log(ClassDeclaration);
类构成
- 构造函数方法 <可选>
- 实例方法 <可选>
- 获取函数 <可选>
- 设置函数 <可选>
- 静态类方法 <可选>
class Foo {
static Qux() {}
constructor() {}
get myBaz() {}
}
类表达式名称
const Person = class PersonName {
identify() {
console.log(Person.name, PersonName.name);
}
}
const p = new Person;
p.identify();
console.log(Person.name);
console.log(PersonName);
类构造函数
constructor 告诉解释器在使用 new 操作符创建类的新实例时应该调用这个函数。不显示定义构造函数时相当于将构造函数定义为空函数。
实例化
使用 new 调用类的构造函数会执行如下操作:
- 在内存中创建一个新对象
- 这个新对象内部的 [[prototype]] 指针被赋值为构造函数的 prototype 属性 ( 类的原型对象 )
- 构造函数内部的 this 被赋值为这个新对象 ( 即 this 指向新对象 )
- 执行构造函数内部的代码 ( 给新对象添加属性 )
- 如果构造函数返回非空对象,返回这个对象; 否则,返回刚创建的新对象
class Animal {}
class Person {
constructor() {
console.log('person ctor');
}
}
class Vegetable {
constructor() {
this.color = 'orange';
}
}
const a = new Animal();
const p = new Person();
const v = new Vegetable();
console.log(v.color);
在实例化时传入的参数会被用作构造函数的参数。如果不需要参数,则类名后面的括号是可选的
class Person {
constructor(name) {
console.log(arguments.length);
this.name = name;
}
}
const p1 = new Person;
console.log(p1.name);
const p2 = new Person();
console.log(p1.name);
const p3 = new Person('hh');
console.log(p1.name);
默认情况下,类构造函数会在执行之后返回 this 对象,其会被用作实例化的对象。不过如果返回的是其他对象,那该对象不会通过 instanceof 操作符检测出跟类有关联,因为这个其他对象的原型指针并没有被修改(除非自己手动修改)。
class Person {
constructor(override) {
this.foo = 'foo';
if (override) {
return {
bar: 'bar'
};
}
}
}
const p1 = new Person(), p2 = new Person(true);
console.log(p1);
console.log(p1 instanceof Person);
console.log(p2);
console.log(p2 instanceof Person);
类构造函数必须使用 new 操作符。而普通构造函数如果不使用 new ,则以全局 this (通常是 window) 作为内部对象。调用类构造函数时如果不使用 new 则会抛出错误。
function Person() {}
class Animal {}
const p = Person();
const a = Animal();
类是一种特殊函数
class Person {}
console.log(typeof Person);
console.log(Person.prototype);
console.log(Person === Person.prototype.constructor);
const p = new Person();
console.log(p instanceof Person);
console.log(p instanceof Person.constructor);
和普通函数一样,类可以立即实例化。由于是一个类表达式,因此类名是可选的
const p = new class Person {
consturctor(name) {
console.log(name);
}
}('hh');
console.log(p);
实例、原型和类成员
实例
每个实例对应一个唯一的成员对象,所有成员都不会在原型上共享。
class Person {
constructor() {
this.name = new String('hh');
this.sayName = () => {};
}
}
const p1 = new Person, p2 = new Person;
console.log(p1.name === p2.name);
console.log(p1.sayName === p2.sayName);
原型方法和访问器
在类块中定义的方法是原型方法,其可以在实例间共享
class Person {
constructor() {
this.locate = () => console.log('instance');
}
locate() {
console.log('prototype');
}
}
类方法等同于对象属性,因此可以使用字符串、符号或计算的值作为键
const symbolKey = Symbol('symbolKey');
class Person {
stringKey() {
console.log('invoked stringKey');
}
[symbolKey]() {
console.log('invoked symbolKey');
}
['computed' + 'Key']() {
console.log('invoked computedKey');
}
}
const p = new Person;
p.stringKey();
p[symbolKey]();
p.computedKey();
获取和设置访问器
class Person {
set name(newName) {
this.name_ = newName;
}
get name() {
return this.name_;
}
}
const p = new Person;
p.name = 'hh';
console.log(p.name);
静态类方法
这些方法通常用于执行不特定于实例的操作(比如说单例和工厂),也不要求存在类的实例。
静态成员在类中使用 static 关键字作为前缀。在静态成员中,this 引用类自身。
class Person {
constructor(age) {
this.age_ = age;
this.locate = () => console.log('instance', this);
}
locate() {
console.log('prototype', this);
}
static locate() {
console.log('class', this);
}
static create() {
return new Person(Math.floor(Math.random() * 100));
}
}
在类外部定义的属性是绑定在类上的属性或静态方法
class Person {
sayName() {
console.log(`${Person.greeting} ${this.name}`)
}
}
Person.greeting = 'My name is';
Person.sayAge = () => console.log(18);
Person.prototype.name = 'hh';
const p = new Person;
p.sayName();
p.sayAge;
迭代器和生成器
class Person {
constructor() {
this.names = ['aa', 'bb', 'cc'];
}
*createNameIterator() {
yield 'aa';
yield 'bb';
}
static *createJobIterator() {
yield 'Butcher';
yield 'Hacker';
}
*[Symbol.iterator]() {
yield *this.names.entries();
}
}
const jobIter = Person.createJobIterator();
console.log(jobIter.next().value);
console.log(jobIter.next().value);
const p = new Person;
const nameIter = p.createNameIterator();
console.log(nameIter.next().value);
console.log(nameIter.next().value);
for (const [idx, name] of p) {
console.log(name);
}
继承
继承基础
使用 extends 继承任何拥有 [[Construct]] 和原型的对象。因此类不仅可以继承另一个类,还可以继承普通的构造函数。
class Vehicle {}
class Bus extends Vehicle {}
const b = new Bus();
console.log(b instanceof Bus);
console.log(b instanceof Vehicle);
function Person() {}
class Engineer extends Person {}
const e = new Engineer();
console.log(e instanceof Engineer);
console.log(e instanceof Person);
类和原型上定义的方法后都会带到派生类,this 的值会反映调用相应方法的实例或者类(new 调用的对象)。
extends 也可以在类表达式中使用
const Bar = class extends Foo{}
构造函数
派生类的方法可以通过 super 引用它们的原型。这个方法仅能在派生类中使用,而且仅限于在 类构造函数 、实例方法 和静态方法 内部使用。
在构造函数中使用 super 可以调用父类构造函数。
class Vehicle {
constructor() {
this.hasEngine = true;
}
static identify() {
console.log('vehicle');
}
}
class Bus extends Vehicle {
constructor() {
super();
console.log(this instanceof Vehicle);
console.log(this);
}
static identify() {
super.identify();
}
}
Bus.identify();
new Bus();
HomeObject
类构造函数和静态方法有个内部特性 [[HomeObject]],这是一个指向定义该方法对象的指针。
这个指针是自动赋值的,而且只能在 JS 引擎内部访问。
super 始终会定义为 [[HomeObject]]的原型。
let obj = {
method () {}
};
class Obj {
method () {}
}
function func () {}
class Sup {
method () {
console.log('Sup method called');
}
}
class Sub extends Sup {
method () {
console.log('Sub method called');
super.method();
}
}
super
-
super 只能在派生类 构造函数 和 静态方法 中使用 class Vehicle {
constructor() {
super();
}
}
-
不能单独使用 super,要么用它调用构造函数,要么用它引用静态方法 class Vehicle {}
class Bus extends Vehicle {
constructor() {
console.log(super);
}
}
-
调用 super() 会调用父类构造函数并将返回实例赋值给 this class Vehicle {}
class Bus extends Vehicle {
constructor() {
super();
console.log(this instanceof Vehicle);
}
}
new Bus();
-
super() 的行为如同调用构造函数,此时可以给父类构造函数传参 class Vehicle {
constructor(licensePlate) {
this.licensePlate = licensePlate;
}
}
class Bus extends Vehicle {
constructor(licensePlate) {
super(licensePlate);
}
}
console.log(new Bus('aa'));
-
派生类没有显式定义构造函数时,在 new 派生类时会把参数直接传给父类的 constructor class Vehicle {
constructor(licensePlate) {
this.licensePlate = licensePlate;
}
}
class Bus extends Vehicle {}
console.log(new Bus('aa'));
-
在类构造函数中,不能在 super() 前使用 this -
如果派生类显式定义了构造函数,则要么必须在其中调用 super(),要么必须在其中返回一个对象 class Vehicle {}
class Car extends Vehicle {}
class Bus extends Vehicle {
constructor() {
super();
}
}
class Van extends Vehicle {
constructor() {
return {};
}
}
console.log(new Car());
console.log(new BUs());
console.log(new Van());
抽象基类
JS 没有天然提供抽象基类的语法,但是可以通过 new.target 来实现。
new.target 指向 new 调用的类或构造函数。
开发者可以在构造函数中来检测 new.target 是不是指向抽象基类,如果是则组织对抽象基类的实例化。
class Vehicle {
constructor() {
console.log(new.target);
if (new.target === Vehicle) {
throw new Error('Vehicle cannot be directly instantiated');
}
}
}
class Bus extends Vehicle {}
new Bus();
new Vehicle();
可以看到抽象基类还是有返回新对象,只是在主线程中报错了阻止了程序的运行
我们还可以在抽象基类的构造函数中进行检查,要求派生类必须定义某个方法。因为原型方法再调用类构造函数之前已经存在了,所以可以通过 this 来检查相应的方法
class Vehicle {
constructor() {
console.log(new.target);
if (new.target === Vehicle) {
throw new Error('Vehicle cannot be directly instantiated');
}
if (!this.foo) {
throw new Error('Inheriting class must define foo()');
}
console.log('success');
}
}
class Bus extends Vehicle {
foo() {}
}
class Van extends Vehicle {}
new Bus();
new Van();
扩展内置类型
class SuperArray extends Array {
shuffle() {
for (let i = this.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[this[i], this[j]] = [this[j], this[i]];
}
return this;
}
}
const a = new SuperArray([1, 2, 3]);
console.log(a instanceof SuperArray);
console.log(a instanceof Array);
console.log(a);
a.shuffle();
有些内置类型的方法会返回新实例。默认情况下,返回实例类型和原始实例类型一致
class SuperArray extends Array {
shuffle() {
for (let i = this.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[this[i], this[j]] = [this[j], this[i]];
}
return this;
}
}
const a = new SuperArray([1, 2, 3]);
const b = a.filter(x => x % 2);
console.log(a instanceof SuperArray);
console.log(b instanceof SuperArray);
开发者可以通过 Symbol.species 访问器决定在创建返回的实例时使用的类
class SuperArray extends Array {
static get [Symbol.species]() {
return Array;
}
}
const a = new SuperArray([1, 2, 3]);
const b = a.filter(x => x % 2);
console.log(a instanceof SuperArray);
console.log(b instanceof SuperArray);
混入
JS 同样没有对混入有天然的语法支持,但是可以通过 Object.assign() 来实现。
class Vehicle {}
const FooMixin = (SuperClass) => class extends SuperClass {
foo() {
console.log('foo');
}
}
const BarMixin = (SuperClass) => class extends SuperClass {
bar() {
console.log('bar');
}
}
class Bus extends FooMixin(BarMixin(Vehicle)) {}
const b = new Bus();
b.foo();
b.bar();
可以用一个辅助函数来优化上述过程
class Vehicle {}
const FooMixin = (SuperClass) => class extends SuperClass {
foo() {
console.log('foo');
}
}
const BarMixin = (SuperClass) => class extends SuperClass {
bar() {
console.log('bar');
}
}
function mix(BaseClass, ...Mixinx) {
return Mixins.reduce((pre, cur) => current(pre), BaseClass);
}
class Bus extends mix(Vehicle, FooMixin, BarMixin) {}
const b = new Bus();
b.foo();
b.bar();
值得注意的是,在平时开发中:组合要胜过继承 (composition over inheritance)
|