虽然 ECMAScript 6 类表面上看起来能够支持正式的面向对象编程,但实际上它背后使用的仍然是原型和构造函数的概念。 | header |
---|---|
与函数类型类似,定义类也有两种主要方式:类声明和类表达式。这两种方式都使用 class 关键字加大括号:javascript
// 类声明 class Person {} // 类表达式 const Animal = class {};
与函数表达式相似,类表达式在它们被求值前也不能引用。不过,与函数定义不一样的是,虽然函数声明能够提高,但类定义不能。java
另外一个跟函数声明不一样的地方是,函数受函数做用域限制,而类受块做用域限制编程
类能够包含构造函数方法、实例方法、获取函数、设置函数和静态类方法,但这些都不是必需的。空的类定义照样有效。默认状况下,类定义中的代码都在严格模式下执行。数组
与函数构造函数同样,多数编程风格都建议类名的首字母要大写,以区别于经过它建立的实例(好比,经过 class Foo {}建立实例 foo):ide
// 空类定义,有效 class Foo {} // 有构造函数的类,有效 class Bar { constructor() {} } // 有获取函数的类,有效 class Baz { get myBaz() {} } // 有静态方法的类,有效 class Qux { static myQux() {} }
类表达式的名称是可选的。在把类表达式赋值给变量后,能够经过 name 属性取得类表达式的名称字符串。但不能在类表达式做用域外部访问这个标识符。函数
let Person = class PersonName { 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
使用 new 操做符实例化 Person 的操做等于使用 new 调用其构造函数。惟一可感知的不一样之处就是,JavaScript 解释器知道使用 new 和类意味着应该使用 constructor 函数进行实例化。测试
若是构造函数返回非空对象,则返回该对象;不然,返回刚建立的新对象。this
class Person { 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
默认状况下,类构造函数会在执行以后返回 this 对象。构造函数返回的对象会被用做实例化的对象,若是没有什么引用新建立的 this 对象,那么这个对象会被销毁。不过,若是返回的不是 this 对象,而是其余对象,那么这个对象不会经过 instanceof 操做符检测出跟类有关联,由于这个对象的原型指针并无被修改。prototype
class Person { 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
类构造函数与构造函数的主要区别是,调用类构造函数必须使用 new 操做符。而普通构造函数若是不使用 new 调用,那么就会以全局的 this(一般是 window)做为内部对象。指针
调用类构造函数时若是忘了使用 new 则会抛出错误:
function Person() {} class Animal {} // 把 window 做为 this 来构建实例 let p = Person(); let a = Animal(); // TypeError: class constructor Animal cannot be invoked without 'new'
类构造函数没有什么特殊之处,实例化以后,它会成为普通的实例方法(但做为类构造函数,仍然要使用 new 调用)。所以,实例化以后能够在实例上引用它。
ECMAScript 中没有正式的类这个类型。从各方面来看,ECMAScript 类就是一种特殊函数。声明一个类以后,经过 typeof
操做符检测类标识符,代表它是一个函数。
类标识符有 prototype
属性,而这个原型也有一个 constructor 属性
指向类自身。
与普通构造函数同样,可使用 instanceof
操做符检查构造函数原型是否存在于实例的原型链中。
类是 JavaScript 的一等公民,所以能够像其余对象或函数引用同样把类做为参数传递:
// 类能够像函数同样在任何地方定义,好比在数组中 let classList = [ class { constructor(id) { this.id_ = id; console.log(`instance ${this.id_}`); } } ]; function createInstance(classDefinition, id) { return new classDefinition(id); } let foo = createInstance(classList[0], 3141); // instance 3141
与当即调用函数表达式类似,类也能够当即实例化:
// 由于是一个类表达式,因此类名是可选的 let p = new class Foo { constructor(x) { console.log(x); } }('bar'); // bar console.log(p); // Foo {}
类的语法能够很是方便地定义应该存在于实例上的成员、应该存在于原型上的成员,以及应该存在于类自己的成员。
每次经过new调用类标识符时,都会执行类构造函数。在这个函数内部,能够为新建立的实例(this)添加“自有”属性。至于添加什么样的属性,则没有限制。另外,在构造函数执行完毕后,仍然能够给实例继续添加新成员。
每一个实例都对应一个惟一的成员对象,这意味着全部成员都不会在原型上共享:
class Person { constructor() { // 这个例子先使用对象包装类型定义一个字符串 // 为的是在下面测试两个对象的相等性 this.name = new String('Jack'); this.sayName = () => console.log(this.name); this.nicknames = ['Jake', 'J-Dog'] } } let p1 = new Person(), p2 = new Person(); p1.sayName(); // Jack p2.sayName(); // Jack console.log(p1.name === p2.name); // false console.log(p1.sayName === p2.sayName); // false console.log(p1.nicknames === p2.nicknames); // false p1.name = p1.nicknames[0]; p2.name = p2.nicknames[1]; p1.sayName(); // Jake p2.sayName(); // J-Dog
为了在实例间共享方法,类定义语法把在类块中定义的方法做为原型方法。
class Person { constructor() { // 添加到 this 的全部内容都会存在于不一样的实例上 this.locate = () => console.log('instance'); } // 在类块中定义的全部内容都会定义在类的原型上 locate() { console.log('prototype'); } } let p = new Person(); p.locate(); // instance Person.prototype.locate(); // prototype
能够把方法定义在类构造函数中或者类块中,但不能在类块中给原型添加原始值或对象做为成员数据:
class Person { name: 'Jake' } // Uncaught SyntaxError: Unexpected token
类方法等同于对象属性,所以可使用字符串、符号或计算的值做为键:
const symbolKey = Symbol('symbolKey'); class Person { stringKey() { console.log('invoked stringKey'); } [symbolKey]() { console.log('invoked symbolKey'); } ['computed' + 'Key']() { console.log('invoked computedKey'); } } let p = new Person(); p.stringKey(); // invoked stringKey p[symbolKey](); // invoked symbolKey p.computedKey(); // invoked computedKey
类定义也支持获取和设置访问器。语法与行为跟普通对象同样:
class Person { set name(newName) { this.name_ = newName; } get name() { return this.name_; } } let p = new Person(); p.name = 'Jake'; console.log(p.name); // Jake
能够在类上定义静态方法。这些方法一般用于执行不特定于实例的操做,也不要求存在类的实例。与原型成员相似,静态成员每一个类上只能有一个。
静态类成员在类定义中使用 static
关键字做为前缀。在静态成员中,this 引用类自身。其余全部约定跟原型成员同样:
class Person { constructor() { // 添加到 this 的全部内容都会存在于不一样的实例上 this.locate = () => console.log('instance', this); } // 定义在类的原型对象上 locate() { console.log('prototype', this); } // 定义在类自己上 static locate() { console.log('class', this); } } let p = new Person(); p.locate(); // instance, Person {} Person.prototype.locate(); // prototype, {constructor: ... } Person.locate(); // class, class Person {}
虽然类定义并不显式支持在原型或类上添加成员数据,但在类定义外部,能够手动添加:
class Person { 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
类定义语法支持在原型和类自己上定义生成器方法:
class Person { // 在原型上定义生成器方法 *createNicknameIterator() { yield 'Jack'; yield 'Jake'; yield 'J-Dog'; } // 在类上定义生成器方法 static *createJobIterator() { yield 'Butcher'; yield 'Baker'; yield 'Candlestick maker'; } } let jobIter = Person.createJobIterator(); console.log(jobIter.next().value); // Butcher console.log(jobIter.next().value); // Baker console.log(jobIter.next().value); // Candlestick maker 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
由于支持生成器方法,因此能够经过添加一个默认的迭代器,把类实例变成可迭代对象:
class Person { 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
也能够只返回迭代器实例:
class Person { constructor() { this.nicknames = ['Jack', 'Jake', 'J-Dog']; } [Symbol.iterator]() { return this.nicknames.entries(); } } let p = new Person(); for (let [idx, nickname] of p) { console.log(nickname); } // Jack // Jake // J-Dog