class 语法
class 的基本语法
- 类内部,方法之间无逗号分割,否则会报错
- 类的构造函数若未显式定义,会默认添加一个空的构造方法
- 类的构造方法默认返回类的实例对象 this,也可手动指向其他对象,此时生成的实例就不会是该类的实例对象(instanceof 为 false)
- 类的数据类型是函数:typeof Point ==== 'function'
- 类本身指向构造函数:Point === Point.prototype.constructor
- 类的实例调用方法等于类的原型调用方法(类的所有方法都定义在类的原型上):point.constructor === Point.prototype.constructor,所以:
- 可以使用 Object.assign 快速给类添加方法:Object.assign(Point.prototype, {....})
- 类的实例必须通过 new 生成,不能直接调用类,否则会报错,这和 ES5 的构造函数是不一样的
- 类内部所有定义的方法,都是不可枚举的(不能通过某些方法进行遍历读取:Object.keys()、for...in 等),这和 ES5 定义的类不一致;但都可以通过 Object.getOwnPropertyNames()获取
- 类的属性名,可以使用表达式形式形如
[expr]() {}
- 实例的属性除非显式定义在本身(用 this.xxx 定义的),否则都是定义在原型上(prototype);与 ES5 行为保持一致
- 类的所有实例共享一个原型对象,即proto属性(建议使用 Object.getPrototypeOf())都是相等的;与 ES5 保持一致,意味着:
- 可以使用该属性来修改原型
- 取值函数 getter 和存值函数 setter 可拦截该属性的存取行为
- 类可以使用表达式的形式,形如:
const Point = class P {}
,其中:- P 可以省略,P 可以在类的内部使用,指代当前类,在类的外部,只能用 Point 构建实例
- 类的 name 属性是 class 关键字后面的值
- 立即执行的类:
const Point = class P {}(args)
- 类和模块的内部,都是严格模式
- 类不存在变量提升
- 类的某个方法加上*号,表示是要给 generator 函数,比如
*[Symbol.iterator]
默认返回一个该类的遍历器 - 类中的 this,指向类的实例,若将类中方法单独提出来,由于内部是严格模式,所以该方法的 this 指向 undefined,解决方法:
- 在构造函数中,为该方法显式绑定this,比如:
this.xxx = this.xxx.bind(this)
- 使用箭头函数,指向定义时所在的对象,即实例对象
- 使用proxy,【尚未完成】
- 在构造函数中,为该方法显式绑定this,比如:
类的静态方法
- 定义:在方法前加上static关键字,表示该方法不会被实例继承,只能通过类名调用
- 静态方法中的this,指向的是类,而非类的实例,故而静态方法可以与非静态方法重名
- 父类的静态方法,可以被子类继承,通过super对象进行调用
类的静态属性
- 定义:即直接定义在类名上,或者在类的内部,使用static关键字表示静态属性,和静态方法类似
类的实例属性
- 实例属性除了定义在constructor中的this上,也可以直接定义在类的顶层,即:
- 与其他方法在同一个层次上:例如:
class P { _count = 0 }
,好处在于直观整洁
- 与其他方法在同一个层次上:例如:
私有属性和私有方法
- 变通方法,但仍然能够调用到该属性:
- 在命名上加以区分,比如私有属性名称在前面加一个下划线用以区分
- 通过bind的绑定,将外部的方法在内部调用,并绑定this:
class P { foo (baz) {bar.call(this.baz)} }
(可行) - 使用symbol作为类方法的方法名,一般情况不能获取,但有能够获取symbol值的方法
- 提案(目前测试chrome已经能够使用):
- 在私有属性/方法前面加上一个#号,表示私有
- 私有属性/方法仅仅能够在类的内部使用,不仅仅是this,只要是该类是实例,在类的内部均可引用该私有属性/方法
- 私有属性能够设置存取函数getter/setter
- 私有属性/方法能够加上static关键字,表示静态的私有,和静态属性类似,但仅仅在类的内部被类名调用
静态块
用途:
- 静态属性初始化:将类里面的静态属性设置,放在类里面,同时只运行一次;
- 与类外部代码分享私有属性
传统的静态属性初始值设置方式:
- 在类外面对静态属性进行设值,该方式会将类的内部逻辑写在外面,不便于管理
- 在类里面的constructor方法中对静态属性进行设值,该种方式会在每次新建实例的时候都运行一次
注意:
- 一个类中可以包含多个静态块,每个静态块只能访问前面声明的静态属性,不然得到的值是undefined
- 静态块内部不能有return语句
- 静态块内部可以使用类名或this指代当前类
javascript
// 静态块
class Person {
static name: 'jade qiu'
static age
static sex
static {
try {
const person = setStaticValue()
this.age = person.age
this.sex = person.sex
} catch {
this.age = '28'
this.sex = 'M'
}
}
}
function setStaticValue () {
return {
age: 18,
sex: 'F'
}
}
// 外部访问类私有属性
let getAge
class Person {
#age = 10
static {
// 注意此处必须传值,不然可能会报错
getAge = obj => obj.#age
}
}
getAge(new Person())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
new.target属性
- 定义:用在构造函数中,返回new命令作用于的那个构造函数,即类名(Point),用于确定构造函数以何种方式进行调用的,当:
- 构造函数不是通过new或者Reflect.construct()调用时,返回undefined
- 当对子类进行实例化时,new.target的对象是子类,此时可以通过new.target构造出一个不能实例化,只能继承的父类
class Parent { constructor () { if (new.target === Parent) {throw new Error()} } }
继承的基本知识
- 关键字:extends
- 当子类显式声明了constructor方法时,内部必须调用
super()
方法,否则会报错,因为:- 子类的this对象,是通过父类的构造函数完成构造的,得到父类的实例和属性加工后,再加上子类自身的实例和属性
- 不调用super,子类无法获取this
- 只有先调用super后,才能够使用this
- 子类未声明constructor方法时,默认是加上了super方法的
- 父类的静态方法/属性,能够被子类继承
- 判断一个类是否继承了另一个类,使用
Object.getPrototypeOf(ColorPoint) === Point
super关键字
- super关键字,可当作函数使用,亦可当作对象使用,当:
- super作为函数使用时,代表父类的构造函数,只能在子类constructor中首行使用,其他地方会报错
- 虽然super代表父类构造函数,但是返回的是子类的实例,即:
Parent.prototype.constructor.call(this)
- 虽然super代表父类构造函数,但是返回的是子类的实例,即:
- super作为对象时,普通方法指向父类的原型对象,静态方法指向父类本身,所以父类实例上的属性(通过this调用的)/方法无法通过super调用
- 由于普通方法中super返回子类实例,所以通过super对属性进行赋值时,赋值的属性会变为子类实例的属性
- 使用super时,必须显式指定为对象
super.xxx
/函数super()
,否则会报错
- super作为函数使用时,代表父类的构造函数,只能在子类constructor中首行使用,其他地方会报错
- 由于对象总是继承自其他对象的,所以在任何一个对象中,均可使用super关键字
prototype属性和__proto__属性
Child.__proto__ === Parent
:子类是__proto__属性,表示构造函数的继承,指向父类本身Child.prototype.__proto__ === Parent.prototype
:表示方法的继承,指向父类的prototype属性child.__proto__.__proto__ === parent.__proto__
:子类原型的原型,是父类的原型(即子类的原型是父类),则可以:- 通过这个修改父类实例的行为
- extends后面可以跟多种类型的值(除了Function.prototype)🍤
原生构造函数的继承
- JavaScript中的原生构造函数有:Boolean、Number、String、Array、Date、Function、Regexp、Error、Object等
- 在ES5中,是不能继承原生构造函数的,若继承,子类的行为将与其不一致,因为:
- 在ES5中,先创建子类的实例对象,再将父类的属性和方法添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数
- 即使通过apply绑定或原型对象绑定,仍然拿不到内部的属性
- 在ES6中,由于先创建父类的实例对象this,然后用子类的构造函数修饰this,使得能够继承父类的所有行为
- 在ES6中,extends关键字不仅能够继承类,也能够继承原生构造函数,比如通过继承Array来对数组增强处理
- 当继承Object时,无法通过super方法像父类传参,因为ES6改变了Object构造函数的行为,若不是通过new Object的方式进行调用,Object的构造函数会忽略参数
Mixin模式(多个对象合成一个对象)
- 定义:Mixin指的是将多个对象合成一个对象,新对象具有各个组成成员的接口(属性/方法)🍬
- 一个空的类:通过Reflect.ownKeys()可以获取三个属性,分别是length、prototype、name
javascript
class Parent {}
class Child {}
// Child的实例继承Parent的实例
Object.setPrototypeOf(Child.prototype, Parent.prototype)
// setPrototypeOf方法的实现:
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto
return obj
}
// 所以:
Child.prototype.__proto === Parent.prototype
// Child继承Parent的静态属性
Object.setPrototypeOf(Child, Parent)
// 所以:
Child.__proto__ === Parent
const child = new Child()
// 🍤:当不存在任何类型的继承时:
class NoneExtendsClass {}
NoneExtendsClass.__proto__ === Function.prototype
NoneExtendsClass.prototype.__proto__ === Object.prototype
// 🍬:多个对象混合成一个对象mixins
function mix (...mixins) {
class Mix {
constructor () {
for (let mixin of mixins) {
// 拷贝实例属性
copyProperties(this, new Mixin())
}
}
}
for (let mixin of mixins) {
// 拷贝静态属性
copyProperties(Mix, mixin)
// 拷贝原型对象属性
copyProperties(Mix.prototype, mixin.prototype)
}
return Mix
}
function copyProperties (target, source) {
for (let key of Reflect.ownKeys(source)) {
if (['name', 'prototype', 'constructor'].indexOf(key) !== -1) {
return false
}
let desc = Object.getOwnpropertyDescripter(source, key)
Object.defineProperty(target, key, desc)
}
}
// 继承混合类
class Child extends mix(Parent1, Parent2) {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62