1.类定义
与函数类型相似,定义类也有两种主要方式:类声明和类表达式
// 类声明
class Person {}
// 类表达式
class Animal = class {}
类表达式在它们被求值之前不能引用,声明不可以提升。函数受函数作用域限制,而类受块作用域限制
类的构成
类可以包括构造函数方法、实例方法、获取函数、设置函数和静态类方法。 类名的首字母要大写,以区别于通过它创建的实例。
// 空类定义,有效
class Foo {}
// 有构造函数的类,有效
class Bar {
constructor() {}
}
// 有获取函数的类, 有效
class Baz {
get myBaz() {}
}
// 有静态方法的类,有效
class Qux {
static myQux() {}
}
类表达式的名称是可选的。在把类表达式赋值给变量后,可以通过 name 属性取得类表达式的名称字符串。但不能在类表达式作用域外部访问这个标识符。
2.类构造函数
constructor 关键字用于在类定义块内部创建类的构造函数。方法名 constructor 会告诉解释器在使用 new 操作符创建类的新实例时,应该调用这个函数。构造函数的定义不是必需的,不定义构造函数相当于将构造函数定义为空函数。
1.实例化
使用 new 操作符实例化 Person 的操作等于使用 new 调用其构造函数。唯一可感知的不同之处就是,JavaScript 解释器知道使用 new 和类意味着应该使用 constructor 函数进行时实例化。 使用 new 调用类的构造函数会执行如下操作。
- 在内存中创建一个新对象
- 这个新对象内部的[[Prototype]]指针被赋值为构造函数的 prototype 属性。
- 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
- 执行构造函数内部的代码(给新对象添加属性)
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
2.把类当成特殊函数
ESMAScript 中没有正式的类这个类型。从个方面来看,ECMAScript 类就是一种特殊函数。声明一个类之后,通过 typeof 操作符检测类标识符,表明它是一个函数:
class Person {}
console.log(Person) // class Person
console.log(typeof Person) // function
类标识符有 prototype 属性,而这个原型也有一个 constructor 属性指向类本身:
class Person {}
console.log(Person.prototype) // {constructor: f()}
console.log(Person === Person.prototype.constructor) // true
使用 instanceof 操作符检查构造函数原型是否在实例的原型链中
class Person {}
let p = new Person()
console.log(p instanceof Person) // true
由此可知,可以使用 instanceof 操作符检查一个对象与类构造函数,以确定这个对象是不是类的实例 类本身具有与普通构造函数一样的行为。在类的上下文中,类本身在使用 new 调用时就会被当成构造函数。重点在于,类中定义的 constructor 方法不会被当成构造函数,对它使用 instanceof 会返回 false。但是如果在创建实例时直接将类构造函数当成普通构造函数来使用,那么 instanceof 的返回值会反转:
class Person {}
let p1 = new Person()
console.log(p1.constructor === Person) // true
console.log(p1 instanceof Person) // true
console.log(p1 instanceof Person.constructor) // false
let p2 = new Person.constructor()
console.log(p2.constructor === Person) // false
console.log(p2 instanceof Person) // false
console.log(p2 instanceof Person.constructor) // true
3.实例、原型和类成员
类的语法可以非常方便地定义应该存在于实例上的成员、应该存在于原型上的成员,以及应该存在于类本身的成员
1.实例成员
每次通过 new 调用类标识符时,都会执行类构造函数。在函数内部,可以为新创建的实例添加“自有属性”。 每个实例都对应一个唯一的成员对象,这意味着所有成员都不会在原型上共享:
class Person {
constructor() {
this.name = new String('Jack')
this.sayName = () => console.log(this.name)
this.nicknames = ['Jack', '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
2.原型方法与访问器
为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法。
class Person {
construtor() {
this.locate = () => console.log('instance')
}
// 在类块中定义的多有内容都会定义在类的原型上
locate() {
console.log('prototype')
}
}
let p = new Person()
p.locate() // instance
Person.prototype.locate() // prototype
可以把方法定义在类构造函数中或者类块中,但不能在类块中给原型添加原始值或对象作为成员数据 类方法等同于对象属性,因此可以使用字符串、符号或计算的值作为键:
const symbolKey = Symbol('key')
class Person {
stringKey() {
console.log('stringKey')
}
[symbolKey]() {
console.log('symbolKey')
}
['computed' + 'Key']() {
console.log('computedKey')
}
}
类定义也支持获取和设置访问器。语法行为跟普通对象一样
class Person {
set name(newName) {
this.name_ = newName
}
get name() {
return this.name_
}
}
3.静态类方法
可以在类上定义静态方法。这些方法通常用语执行不特定于实例的操作,也不要求存在类的实例。与原型成员类似,静态成员每个类上只能有一个。 静态类成员在类定义中使用 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 {
constructor(age) {
this.age_ = age
}
sayAge() {
console.log(this.age_)
}
static create() {
return new Person(Math.floor(Math.random() * 100))
}
}
console.log(Person.create())
4.非函数原型和类成员
类定义不显式支持在原型或类上添加成员数据,在类定义外部可以手动添加
5.迭代去与生成器方法
类定义语法支持在原型和类本身定义生成器方法
class Person {
// 在原型上定义生成器方法
*createNameIterator() {
yield 'Jack'
yield 'Jake'
yield 'J-Dog'
}
static *createJobIterator() {
yield 'BUTCHER'
yield 'ADD'
yield 'UZI'
}
}
let jobIter = Person.createJobIterator()
jobIter.next().value // jack
jobIter.next().value // jake
jobIter.next().value // j-dog
let p = new Person()
let nameIter = p.createNameIterator()
nameIter.next().value // BUTCHER
nameIter.next().value // ADD
nameIter.next().value // UZI
因为支持生成器方法,所以可以通过添加一个默认的迭代器,把类实例变成可迭代对象:
class Person {
constructor() {
this.names = ['Faker', 'the Shy', 'UZI']
}
*[Symbol.interator]() {
yield* this.names.entries()
}
}
let p = new Person()
for (let [idx, name] of p) {
console.log(name)
}
也可以只返回迭代器实例:
class Person {
constructor() {
this.names = ['Faker', 'the Shy', 'UZI']
}
[Symbol.interator]() {
return this.names.entries()
}
}
let p = new Person()
for (let [idx, name] of p) {
console.log(name)
}