下述内容翻译自 Professional JavaScript for Web Developes,4th Edition(JavaScript 高级程序设计第四版),251 页,Objects, Classes, and Object-Oriented Programming (对象、类和面向对象)章节内,前半部分与第三版的 138 页第 6 章面向对象的程序设计相似,所以主要翻译后半部分 302 页——CLASSES。
console.log(ClassExpression); // undefined var ClassExpression = class{}; console.log(ClassExpression); // class {}
console.log(ClassDeclaration); // ReferenceError: ClassDeclaration is not defined classClassDeclaration{} console.log(ClassDeclaration); // class ClassDeclaration {}
与函数声明不同的是,类声明的作用域是块级:
1 2 3 4 5 6
{ functionFunctionDeclaration() {} classClassDeclaration{} } console.log(FunctionDeclaration); // FunctionDeclaration() {} console.log(ClassDeclaration); // ReferenceError: ClassDeclaration is not defined
类表达式可以带有可选的命名。当表达式分配给变量时,可以使用 name 属性检索类表达式名称字符串,但是标识符本身不能超出类表达式作用域。
1 2 3 4 5 6 7 8 9
let Person = classPersonName{ identify() { console.log(Person.name, PersonName.name); } }; let p = new Person(); p.identify(); // PersonName, PersonName console.log(Person.name); // PersonName console.log(PersonName); // ReferenceError: PersonName is not defined
constructor 的 this 值被分配给新对象(因此当在 constructor 内部引用时,this 指向新对象)。
执行 constructor 中的代码(将属性添加到新对象中)。
如果 constructor 返回一个对象,则返回该对象。否则,将返回刚刚创建的新对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classAnimal{} classPerson{ constructor() { console.log('person ctor'); } } classVegetable{ constructor() { this.color = 'orange'; } } let a = new Animal(); let p = new Person(); // person ctor let v = new Vegetable(); console.log(v.color); // orange
classPerson{ constructor(name) { console.log(arguments.length); this.name = name || null; } } let p1 = new Person(); // 0 console.log(p1.name); // null let p2 = new Person(); // 0 console.log(p2.name); // null let p3 = new Person('Jake'); // 1 console.log(p3.name); // Jake
默认情况下,constructor 将在执行后返回 this 对象。如果从构造函数返回一个对象,该对象将被用作实例化对象,如果对该对象的引用没有保留,则新创建的该对象将被丢弃。但是,如果返回一个不同的对象,返回的对象将不会通过 instanceof 与类关联,因为新对象的原型指针从未被修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classPerson{ constructor(override) { this.foo = 'foo'; if (override) { return { bar: 'bar', }; } } } let p1 = new Person(), p2 = new Person(true); console.log(p1); // Person{ foo: 'foo' } console.log(p1 instanceof Person); // true console.log(p2); // { bar: 'bar' } console.log(p2 instanceof Person); // false
与构造函数的一个主要区别是,使用带有 constructor 的类时 new 运算符是强制性的。对于函数构造函数,当选择不使用 new 运算符时,构造函数将使用构造函数内部的全局 this 值(通常是 window 对象)。对于带有 constructor 的类,忽略 new 操作符会抛出一个错误:
1 2 3 4 5 6
functionPerson() {} classAnimal{} // 构造函数直接调用时使用window作为this let p = Person(); let a = Animal(); // TypeError: class constructor Animal cannot be invoked without 'new'
classPerson{ set name(newName) { this.name_ = newName; } get name() { returnthis.name_; } } let p = new Person(); p.name = 'Jake'; console.log(p.name); // Jake
classPerson{ sayName() { console.log('${Person.greeting} ${this.name}'); } } // 在类上定义数据 Person.greeting = 'My name is'; // 在原型上定义数据 Person.prototype.name = 'Jake'; let p = new Person(); p.sayName(); // My name is Jake
注意:不显式允许定义属性的主要原因是,共享对象中的可变数据成员可能是反面模式。通常,对象实例应该直接拥有它们从 this 引用的数据。
let p = new Person(); let nicknameIter = p.createNicknameIterator(); console.log(nicknameIter.next().value); // Jack console.log(nicknameIter.next().value); // Jake console.log(nicknameIter.next().value); // J-Dog
因为支持生成器方法,所以可以通过添加默认迭代器使类实例可迭代:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classPerson{ constructor() { this.nicknames = ['Jack', 'Jake', 'J-Dog']; } *[Symbol.iterator]() { yield* this.nicknames.entries(); } } let p = new Person(); for (let [idx, nickname] of p) { console.log(nickname); } // Jack // Jake // J-Dog
或者只返回一个迭代器实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classPerson{ constructor() { this.nicknames = ['Jack', 'Jake', 'J-Dog']; } [Symbol.iterator]() { returnthis.nicknames.entries(); } } let p = new Person(); for (let [idx, nickname] of p) { console.log(nickname); } // Jack // Jake // J-Dog
classVehicle{} // 继承自类 classBusextendsVehicle{} let b = new Bus(); console.log(b instanceof Bus); // true console.log(b instanceof Vehicle); // true functionPerson() {} // 继承自构造函数 classEngineerextendsPerson{} let e = new Engineer(); console.log(e instanceof Engineer); // true console.log(e instanceof Person); // true
类静态方法和原型方法都传递到子类。this 值反映了调用方法的类和实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classVehicle{ identifyPrototype(id) { console.log(id, this); } static identifyClass(id) { console.log(id, this); } } classBusextendsVehicle{} let v = new Vehicle(); let b = new Bus(); b.identifyPrototype('bus'); // bus, Bus {} v.identifyPrototype('vehicle'); // vehicle, Vehicle {} Bus.identifyClass('bus'); // bus, class Bus {} Vehicle.identifyClass('vehicle'); // vehicle, class Vehicle {}
注意:extends 关键字在类表达式中是有效的,所以 let Bar = class extends Foo {}是完全有效的语法。
Constructors、HomeObjects 和 super()
子类的方法可以通过 super 关键字去引用它们的原型。这只对子类有效,并且只能在 constructor 或静态方法内部使用。在 constructor 内部使用 super 来控制何时调用父类的 constructor。
classVehicle{} classBusextendsVehicle{ constructor() { console.log(this); } } new Bus(); // ReferenceError: Must call super constructor in derived class // before accessing 'this' or returning from derived constructor