在这里我会根据阮老师ES6书中的介绍走。
一、类的由来。
// ES5做法【在原型链上直接操控,没有很明显的语义化,不便于可读性和操作性及可维护性】
const Person = function () {
this.mouse = 'mouse',
this.eyes = 'eyes'
}
Object.assign(Person.prototype, {
getEyes() {
return 'this amout is two'
}
})
const person = new Person()
console.log(person, person.getEyes())
// Person {mouse: "mouse", eyes: "eyes"} "this amout is two"
// ES6做法【利用class,只需要语义化命令就可以操作原型链,具有可读性和操作性及可维护性】
class ManKind {
constructor() {
this.mouse = 'mouse',
this.eyes = 'eyes'
}
getEyes() {
return 'this amout is two'
}
}
const manKind = new ManKind()
console.log(manKind, manKind.getEyes())
// ManKind {mouse: "mouse", eyes: "eyes"} "this amout is two"
我们都知道,js中实例的构造函数及实例上的方法本质上都是通过操作原型链达到目的,比如我们知晓实例的方法其实本质上都是其构造函数的prototype属性对象中的属性方法。
ES5:当我们编写了一个构造函数时,都是另外重新编写对其prototype属性对象的修改,从而实现实例方法的添加,这样主要有三个弊端。
1、语义化不明确
contructorName.prototype.wayName = function () { code... }
【但我们只需添加一种实例方法时,我们可以直接添加;当有多个时,我们可以利用Object.assgin()
添加,如上述代码】,这样很明显,语义极度不明确化。
2、操作性较差
当添加的实例方法数量不同时我们还要分而待之,这是我们一般不想做的,我们一般想的是一种方法就ok,并且还有较良好的扩展性。
3、维护性较差
当我想修改/增加/删除一个实例构造函数上的某个属性和实例方法中的某个方法,那样我得先找到实例对象的构造函数,然后再去找到设置构造函数prototype
属性对象内容的地方【这是因为构造函数和构造函数实例方法分而编写所造成的】
ES6:当我们想编写一个构造函数时,我们只需要将构造函数的属性值写在constructor
方法中,当我们想添加实例方法时,我们只需要在contructor
方法其下编写你想要添加的方法,这样做的优点有三点。
1、清晰的语义
使语义更加接近于传统语言的写法,比如Java和C++,即便是新手也可以很好的从表面理解其作用。
2、较好的操作性和扩展性
我们就不需要对构造函数的属性和实例方法分开两个地方编写,并且还不需要考虑根据实例方法数量的多少而做不同的处理操作。
3、较好的维护性
当我们想做上述雷同的修改时,我们只需要找到实例对象的构造类就ok了。
所以class的由来是为了更好的实现构造函数及关于原型链上的继承。
二、constructor方法
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加,返回值为实例对象(即this
)。
class Point {
}
// 等同于
class Point {
constructor() {}
}
三、实例方法命名可遵循对象属性表达式命名
const method = 'getEyes'
class ManKind {
constructor() {
this.mouse = 'mouse',
this.eyes = 'eyes'
}
[method]() {
return 'this amout is two'
}
}
const manKind = new ManKind()
console.log(manKind, manKind.getEyes())
// ManKind {mouse: "mouse", eyes: "eyes"} "this amout is two"
四、注意点
1、严格模式
类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。
2、不存在变量提升
new Foo(); // ReferenceError
class Foo {}
3、name属性
由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。
class Point {}
Point.name // "Point"
4、Generator方法
如果某个方法之前加上星号(*),就表示该方法是一个 Generator 函数。比如为class添加Interator接口。
class Foo {
constructor(...args) {
this.args = args;
}
* [Symbol.iterator]() {
for (let arg of this.args) {
yield arg;
}
}
}
for (let x of new Foo('hello', 'world')) {
console.log(x); // hello, world
}
5、this指向问题
我们先看代码对比示例
const method = 'getEyes'
class ManKind {
constructor() {
this.mouse = 'mouse',
this.eyes = 'eyes'
}
[method]() {
console.log(this)
return `this ${this.eyes} is two`
}
}
const manKind = new ManKind()
const { getEyes } = manKind
console.log(manKind.getEyes()) //ManKind {mouse: "mouse", eyes: "eyes"}, this eyes is two
console.log(getEyes)
// ƒ [method]() {
// console.log(this)
// return `this ${this.eyes} is two`
// }
console.log(getEyes()) // undefined, Uncaught TypeError: Cannot read property 'eyes' of undefined
我们可以看到当打印manKind.getEyes()
时,this指向的是ManKind
创建的一个实例对象【注意:变量manKind
只是一个用于储存指向实例对象new ManKind()
的地址的变量】;当我们使用manKind.getEyes()
调用方法时,则便是隐式的在实例对象new ManKind()
环境中调用内部的getEyes()
方法,这也就是为什么getEyes()
方法内部的this是指向实例对象new ManKind()
。
然而我们可以看到当打印单独的getEyes()
时,this
指向的是undefined
,这是因为我们将指向calss ManKind
中getEyes
方法的地址赋值给了class
外部的一个变量getEyes
,并且是在外部执行【我们知道一个函数内部的this指向不是看它是在哪里定义,而是看它是在哪个环境下运行】,然而getEyes
方法中本身的this
是处于严格模式中【因为class
默认严格模式,而getEyes
方法本身是定义在class
中】,在严格模式中为了安全考滤,禁止this
指向全局变量,这就是为什么单独打印单独的getEyes()
时,this
指向的是undefined
,同时也可以解释后面的找不到属性报错。
解决方法【利用bind
强制绑定this
】
const method = 'getEyes'
class ManKind {
constructor() {
this.mouse = 'mouse',
this.eyes = 'eyes',
this[method] = this[method].bind(this)
}
[method]() {
console.log(this)
return `this ${this.eyes} is two`
}
}
const manKind = new ManKind()
const { getEyes } = manKind
console.log(manKind.getEyes()) // ManKind {mouse: "mouse", eyes: "eyes", getEyes: ƒ}, this eyes is two
console.log(getEyes)
// ƒ [method]() {
// console.log(this)
// return `this ${this.eyes} is two`
// }
console.log(getEyes()) // ManKind {mouse: "mouse", eyes: "eyes", getEyes: ƒ}, this eyes is two
五、静态方法
在此我们得谈谈静态方法和实例方法【非静态方法】的各自特点和适用场景了
1、特点
静态方法
1.1、方法内部存在的this
指向的是class
本身
1.2、不会被calss
所创建的实例对象继承
1.3、可以使用类名直接调用比如className.wayName()
1.4、可以被子类继承,利用super
关键字调用
1.5、在class
定义完毕之时就被储存在堆内存中,如果存在多个静态方法,则是并列储存
1.6、静态方法名和实例方法名可以重名
实例方法
1.1、方法内部存在的this
指向的是实例对象本身
1.2、会被calss
所创建的实例对象继承
1.3、可以使用实例名直接调用比如objName.wayName()
1.4、可以被子类继承,利用super
关键字调用
1.5、在每次创建实例对象时,都会储存到内存中;在每次销毁对象时,就会释放所销毁实例对象的实例方法所占的内存空间
2、适用场景
静态方法
没有和实例对象有强烈逻辑关联且会频繁操作的某些业务逻辑,此时我们就可以写成静态方法。【切记:因为除非销毁class
,不然其内部的静态方法所占空间不会被释放,所以不要造成静态方法泛滥】
实例方法
和实例对象有强烈逻辑关联的某些业务逻辑,此时我们就可以写成实例方法。
3、在静态方法中使用实例对象及constructor
内部属性值
我们可以在外部调用静态方法时,传入实例对象所属的实例指针
const method = 'config'
class ManKind {
constructor() {
this.mouse = 'mouse',
this.eyes = 'eyes'
}
static [method](...args) {
const [obj] = args
console.log(obj.mouse) // mouse
return obj.getEyes()
}
getEyes() {
return `this manKind has two ${this.eyes}`
}
}
const manKind = new ManKind()
console.log(ManKind.config(manKind)) // this manKind has two eyes
六、实例属性新写法
可以忽略constructor
方法
const method = 'config'
class ManKind {
mouse = 'mouse'
eyes = 'eyes'
static [method](...args) {
const [obj] = args
console.log(obj.mouse) // mouse
return obj.getEyes()
}
getEyes() {
return `this manKind has two ${this.eyes}`
}
}
const manKind = new ManKind()
console.log(ManKind.config(manKind)) // this manKind has two eyes
七、new.target属性
返回实例对象对对应得calss
或则构造函数,一般用于判断实例对象是否由此calss
或构造函数构建而成
const method = 'config'
class ManKind {
mouse = 'mouse'
eyes = 'eyes'
constructor() {
console.log(new.target === ManKind) // true
}
static [method](...args) {
const [obj] = args
console.log(obj.mouse) // mouse
return obj.getEyes()
}
getEyes() {
return `this manKind has two ${this.eyes}`
}
}
const manKind = new ManKind()
console.log(ManKind.config(manKind)) // this manKind has two eyes