React理解ES6 Class
一、前言
最近准备记录自己从0到1学习React
的过程的一些知识点和心得总结。第一章就是理解ES6
的Class
。如有错误,请及时指出。
二、ES6之前的仿类
JS
在ES5
及更早版本中都不存在类。与类最接近的是:创建一个构造函数,然后将方法添加到该构造器的原型上。如下面代码:
functionPerson() {this.name = name
}
Person.prototype.sayName = function () {
console.log(this.name)
}
let person = new Person('张三')
person.sayName() //'张三'
console.log(person instanceof Person) //true
console.log(person instanceofObject) //true
上面这种基本模式在许多对类进行模拟的JS
库中都存在,而这也是ES6
类要解决的问题。ES6
的类起初是作为ES5
传统继承模型的语法糖,但添加了许多特性来减少错误。我们要总是使用class
,避免直接操作prototype
。因为class
语法更简洁也更易读。
三、基本的类声明
classPersonClass{// 等价于上面的Person构造器
constructor(name) {
this.name = name
}
// 等价于上面的Person.prototype.sayName
sayName() {
console.log(this.name)
}
}
let person = new PersonClass('zhangsan')
person.sayName() //"zhangsan"
console.log(person instanceof PersonClass) //true
console.log(person instanceofObject) //true
console.log(typeof PersonClass) //"function"
console.log(typeof PersonClass.prototype.sayName) //"function"
使用class
的时候需要注意的几点:
类声明以 class
关键字开始,其后是类的名称。方法之间不需要使用逗号。 在其中使用特殊的 constructor
方法名称直接定义一个构造器。自有属性( Own properties
)属性出现在实例上而不是原型上,只能在类的构造器或方法内部进行创建。上面name
就是一个自有属性。上面的 sayName()
方法最终也成为PersonClass.prototype
上的一个方法。
由于类的方法使用了简写语法,就不再需要使用function
关键字。PersonClass
声明实际上创建了一个拥有constructor
方法和其他行为的函数,这也是typeof PersonClass
会得到"function"
结果的原因。
3.1、 类声明和函数声明的区别
尽管类与自定义函数类型之间有相似性,但还是有一些重要的区别:
类声明不会被提升,这与函数定义不同。类声明的行为与 let
相似,因此在程序执行到声明处之前,类都会位于暂时性死区内。
// 类声明不会被提升:Uncaught ReferenceError: Cannot access 'Person' before initializationconst person = new Person('zhangsan')
classPerson{
constructor(name) {
this.name = name
}
}
类声明中的所有代码会自动运行并锁定在严格模式下。 类的所有方法都是不可枚举的,这是对于函数类型的显著变化,函数类型必须用 Object.defineProperty()
才能将方法改变为不可枚举。
// 类的所有方法都是不可枚举的classPerson{
constructor(name) {
this.name = name
}
getName() {
console.log('getName')
}
}
for (prop innew Person()) {
// name
console.log(prop)
}
functionAnimal(name) {
this.name = name
}
Animal.prototype = {
getName() {
console.log('getName')
}
}
for (prop innew Animal()) {
// name
// getName
console.log(prop)
}
类的所有方法内部都没有 [[Construct]]
,因此使用new
来调用它们会抛出错误。调用类构造器时不使用 new
,会抛出错误。在类的方法内部重写类名,会抛出错误。 只有在类的内部,类名才被视为是使用 const
声明的,外部的Foo
就像是用let
声明的,但不能在类的方法内部这么做。
classFoo{constructor() {
//执行时抛出错误
Foo = 'bar'
}
}
//在类声明之后没问题
Foo = 'baz'
四、类表达式
类与函数相似的点在于都有两种形式:声明与表达式。类和函数类似,也有不需要标识符的表达式形 式。类表达式可以用于变量声明,也可以作为参数传递给函数。如下面代码:
let PersonClass = class{constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}
let person = new PersonClass('zhangsan')
person.sayName() // "zhangsan"
console.log(person instanceof PersonClass) // true
console.log(person instanceofObject) // true
console.log(typeof PersonClass) // "function"
console.log(typeof PersonClass.prototype.sayName) // "function"
使用类声明还是类表达式,主要是代码风格问题。相对于函数声明与函数表达式之间的区别,类声明与类表达式都不会被提升。
4.1、具名表达式
类表达式可以为类表达式命名。如果需要在class
关键字后添加标识符,如下面代码:
let PersonClass = classPersonClass2{constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}
console.log(typeof PersonClass) // "function"
console.log(typeof PersonClass2) // "undefined"
上面例子中的类表达式被命名为PersonClass2
。PersonClass2
标识符只在类定义内部存在,只能用在类方法内部(如sayName()
内)。在类的外部,typeof PersonClass2
的结果为"undefined"
,是因为外部不存在PersonClass2
绑定。要理解为什么,请查 看未使用类语法的等价声明,如下面代码:
// 等价于 PersonClass 具名的类表达式let PersonClass = (function () {
'use script'
const PersonClass2 = function (name) {
if (typeofnew.target === 'undefined') {
thrownewError('Constructor must be called with new.')
}
this.name = name
}
Object.defineProperty(PersonClass2.prototype, 'sayName', {
value: function () {
if (typeofnew.target === 'undefined') {
thrownewError('Method cannot be called with new.')
}
console.log(this.name)
},
enumerable: false,
writable: true,
configurable: true
})
return PersonClass2
}())
对于类声明来说,用 let
定义的外部绑定与用const
定义的内部绑定有着相同的名称。如下:
classHome{}
console.log(typeof Home) // "function"
对于类表达式可在内部使用 const
来定义它的不同名称,于是此处的PersonClass2
就只能在类的内部使用。
let PersonClass = classPersonClass2{constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}
console.log(typeof PersonClass) // "function"
console.log(typeof PersonClass2) // "undefined"
五、一等公民的类
一等公民意味着着它能作为参数传给函数、能作为函数返回值、能用来给变量赋值。ES6
类同样成为一等公民。这就使得类可以被多种方式所使用。
5.1、类作为参数传入函数
functioncreateObject(classDef) {returnnew classDef()
}
const obj = createObject(class{
sayHi() {
console.log('Hi!')
}
})
obj.sayHi() // "Hi!"
5.2、类作为匿名表达式
可以使用匿名类表达式,立即调用类构造器,用于创建单例(Singleton
)。
let person = newclass{constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}('zhangsan')
person.sayName() // "zhangsan"
六、类的访问器属性
自有属性需要在类构造器中创建,还可以在原型上定义访问器属性。创建一个getter
,要使用get
关键字,并要与后方标识符之间留出空格。创建setter
只是要改用set
关键字就可以了。
classcustomHTMLElement{constructor(element) {
this.element = element
}
get html() {
returnthis.element.innerHTML
}
set html(value) {
this.element.innerHTML = value
}
}
// 取得指定对象上一个自有属性(非继承属性)的描述符
const descriptor = Object.getOwnPropertyDescriptor(customHTMLElement.prototype, 'html')
console.log("get"in descriptor) // true
console.log("set"in descriptor) // true
console.log(descriptor.enumerable) // false
上面代码中customHTMLElement
类用于包装一个已存在的DOM
元素。它的属性html
拥有getter
与setter
,委托了元素自身的innerHTML
方法。访问器属性被创建在CustomHTMLElement.prototype
上,并且像其他类属性那样被创建为不可枚举属性。非类的等价表示如下代码:
let CustomHTMLElement = (function () {const CustomHTMLElement = function (element) {
if (new.target === 'undefined') {
thrownewError("Constructor must be called with new.");
}
this.element = element
}
Object.defineProperty(CustomHTMLElement.prototype, 'html', {
enumerable: false,
configurable: true,
get() {
returnthis.element.innerHTML
},
set(value) {
this.element.innerHTML = value
}
})
return CustomHTMLElement
}())
上面这个例子说明了使用类语法能够少写大量的代码。
七、类的可计算成员名
类方法与类访问器属性也都能使用可计算的名称。如下面代码:
let methodName = 'sayName'classPerson{
constructor(name) {
this.name = name
}
[methodName]() {
console.log(this.name)
}
}
const me = new Person('zhangsan')
me.sayName() // "zhangsan"
访问器属性能以相同方式使用可计算的名称。如下面代码:
let propertyName = 'html'classCustomHTMLElement{
constructor(element) {
this.element = element
}
get [propertyName]() {
returnthis.element.innerHTML
}
set [propertyName](value) {
this.element.innerHTML = value
}
}
八、生成器方法
类允许将任何方法变为一个生成器。如下面代码:
classMyClass{ *createIterator() {
yield1
yield2
yield3
}
}
const instance = new MyClass()
const iterator = instance.createIterator()
上面代码创建了一个拥有createIterator()
生成器的MyClass
类。该方法返回了一个迭代器对象。也可以定义类的默认迭代器,如下面代码:
classCollection{constructor() {
this.items = []
}
*[Symbol.iterator]() {
yield* this.items.values()
}
}
var collection = new Collection()
collection.items.push(1)
collection.items.push(2)
collection.items.push(3)
for (let item of collection) {
// 1
// 2
// 3
console.log(item)
}
九、静态成员
直接在构造器上添加额外方法来模拟静态成员,ES5
及更早版本中的模式如下:
functionPerson(name) {this.name = name
}
// 静态方法
Person.create = function (name) {
returnnew Person(name)
}
// 实例方法
Person.prototype.sayName = function () {
console.log(this.name)
}
const person = Person.create('zhangsan')
person.sayName() // "zhangsan"
ES6
的类简化了静态成员的创建,只要在方法与访问器属性的名称前添加正式的static
标注。下面有个与上个例子等价的类:
classPerson{constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
static create(name) {
returnnew Person(name)
}
}
const person = Person.create('zhangsan')
person.sayName() // "zhangsan"
能在类中的任何方法与访问器属性上使用static
关键字。但是不能将它用于constructor
方法的定义。静态成员不能用实例来访问,始终需要直接用类自身来访问。
十、类继承
ES6之前,实现自定义类型的继承很麻烦。例如下面的代码:
functionFather(house, knowledge) {this.house = house
this.knowledge = knowledge
}
Father.prototype.getKnowledge = function () {
returnthis.knowledge
}
functionSon(house, knowledge) {
Father.call(this, house, knowledge)
}
// 指定原型对象为Father
Son.prototype = Object.create(Father.prototype, {
constructor:
{
value: Son,
enumerable: true,
writable: true,
configurable: true
}
})
console.log(new Son('别墅', '写代码'))
Son
继承了Father
,Son
必须使用Father.prototype
所创建的一个新对象来重写Son.prototype
,并且还要调用Father.call()
方法。上面的图片可以看到继承关系。
类让继承工作变得更轻易,使用熟悉的extends
关键字来指定当前类所需要继承的函数。生成的类的原型会被自动调整,而你还能调用super()
方法来访问基类的构造器。此处是与上个例子等价的`ES6v代码:
classFather{constructor(house, knowledge) {
this.house = house
this.knowledge = knowledge
}
getKnowledge() {
returnthis.knowledge
}
}
classSonextendsFather{
// 与 Father.call(this, house, knowledge)
constructor(house, knowledge) {
super(house, knowledge)
}
}
console.log(new Son('别墅', '写代码'))
Son
使用了extends
关键字继承了Father
。Son
构造器使用了super()
配合指定参数调用了Father
的构造器。
继承了其他类的类被称为派生类(其实个人认为也可以成为子类)。如果派生类指定了构造器,就需要 使用super()
,否则会造成错误。若选择不使用构造器,super()
方法会被自动调用,并会使用创建新实例时提供的所有参数。例如下面两个类是完全相同的:
classsonextendsFather{// 没有构造器
}
// 等价于:
classsonextendsFather{
constructor(...args) {
super(...args)
}
}
第二个类展示了与所有子类默认构造器等价的写法,所有的参数都按顺序传递给了基类的构造器。这种做法并不完全准确,最好手动定义构造器。
使用super()
时需牢记以下几点:
你只能在派生类中使用 super()
。若尝试在非派生的类(使用extends
关键字的类)或函数中使用它,就会抛出错误。在构造器中,你必须在访问 this
之前调用super()
。由于super()
负责初始化this
,因此试图先访问this
就会造成错误。若在类的构造器中不调用 super()
,唯一避免出错的办法是在构造器中返回一个对象。
十一、屏蔽类方法
派生类中的方法总是会屏蔽基类的同名方法。例如将getKnowledge()
方法添加到Son
类,以便重定义它的功能:
classSonextendsFather{constructor(house, knowledge) {
super(house, knowledge)
}
// 重写并屏蔽 Father.prototype.getKnowledge()
getKnowledge() {
return'儿子自己的知识!'
}
}
console.log(new Son('别墅', '写代码'))
由于getKnowledge()
已经被定义为Son
的一部分,Father.prototype.getKnowledge()
方法就不能在Son
的任何实例上被调用。但是可以通过使用super.getKnowledge()
方法来调用父类中的该方法,如下面代码:
classFather{constructor(house, knowledge) {
this.house = house
this.knowledge = knowledge
}
getKnowledge() {
returnthis.knowledge
}
}
classSonextendsFather{
constructor(house, knowledge) {
super(house, knowledge)
}
// 重写、屏蔽并调用了 Father.prototype.getArea()
getKnowledge() {
returnsuper.getKnowledge()
}
}
console.log(new Son('别墅', '写代码'))
十二、继承静态成员
如果父类包含静态成员,那么这些静态成员在派生类中也是可用的。如下面代码:
classFather{constructor(house, knowledge) {
this.house = house
this.knowledge = knowledge
}
static create(house, knowledge) {
returnnew Father(house, knowledge)
}
}
classSonextendsFather{
constructor(house, knowledge) {
super(house, knowledge)
}
}
const father = Son.create('别墅', '写代码')
console.log(father instanceof Father) // true
console.log(father instanceof Son) // false
在此代码中,一个新的静态方法create()
被添加到Father
类中。通过继承,该方法会以Son.create()
的形式存在,并且其行为方式与Father.create()
一样。
十三、从表达式中派生类
在ES6中派生类的最强大能力或许就是能够从表达式中派生类。只要一个表达式能够返回一个具有[[Construct]]
属性以及原型的函数,就可以对其使用extends
。如下面代码:
functionFather(house, knowledge) {this.house = house
this.knowledge = knowledge
}
Father.prototype.getKnowledge = function () {
returnthis.knowledge
}
classSonextendsFather{
constructor(house, knowledge) {
super(house, knowledge)
}
}
const son = new Son('别墅', '写代码')
console.log(son.getKnowledge()); // 写代码
console.log(son instanceof Father) // true
Father
被定义为ES5
风格的构造器,而Son
则是一个类。由于Father
具有[[Construct]]
以及原型,Son
类就能直接继承它。
extends
后面能接受任意类型的表达式,如动态地决定所要继承的类:
functionFather(house, knowledge) {this.house = house
this.knowledge = knowledge
}
Father.prototype.getKnowledge = function () {
returnthis.knowledge
}
functiongetBase() {
return Father
}
classSonextendsgetBase() {
constructor(house, knowledge) {
super(house, knowledge)
}
}
const son = new Son('别墅', '写代码')
console.log(son instanceof Father) // true
参考链接
https://book.douban.com/subject/27072230/
https://wizardforcel.gitbooks.io/exploring-es6/md/3/3.2.html
https://es6.ruanyifeng.com/#docs/class-extends
https://segmentfault.com/a/1190000015424508
https://www.stefanjudis.com/today-i-learned/not-every-javascript-function-is-constructable/
本文使用 mdnice 排版
以上是 React理解ES6 Class 的全部内容, 来源链接: utcz.com/a/22550.html