一、前语
最近准备记录自己从0到1学习
React
的过程的一些知识点和心得总结。第一章便是了解ES6
的Class
。如有过错,请及时指出。
二、ES6之前的仿类
JS
在ES5
及更早版别中都不存在类。与类最接近的是:创立一个结构函数,然后将办法增加到该结构器的原型上。如下面代码:
function Person} 7 e h()/ ( k Y Q ` . - ^ {
this.name = name
}
Person.prototype.sayName = function () {
console.logS , { +(this.name)
}
let person = new Pev u x } u | W ,rson('张三')
person.sayName() //'张三'
cof O Cnsole.log(person instanceof Person) //true
console.log(person ins& E & F . 9tanceof Object) //true
上面这种基本形式在许多对类进行模仿的JS
库中都存在,而这也是ES6
类要处理的问题。ES6
的类起初是作为ES5
传统承继模型的语法糖,但增加了许( / _ ? Z k + m多特性来q l 5 Y k w E $ –削减过错。咱们要总是运用class
,防止直接操作pro_ h V _ Ltotype
。因为class
语法更简练也更易读。
三、基本的类声明
class PersonClass {
// 等价于上j y n ! N面的Person结构器
constructor(name) {
this.na6 - - # e 3 j 6me/ B v & ! m y = name
}
// 等价于上面的Person.prototype.sayName
sayName() {a | { * S t
console.lot { v _ TgV ~ Z D B % y m(this.name)
}
}
let& - ) % M _ y person = new PersonClass('zhangsan')
person.sayName() //"zhangsan"
conso~ N ` Q 7 # _le.log(persl I y Von instanceof* D 3 PersonClass) //true
console.log(person instanceof Object) //true
console.log(typeof PersonClas} k P -s) //"function"
console.log(type, G L Kof PersonClass.prototype.sayName) //V v H"function"M * Y 1
运用class
的时分需求注意的几点:
-
类声明以 class
关键字开始,其后是类的称号。 -
办法之间不需求运用逗号。 -
在其中运用特别的 construcY Q y C M ! B 9tor
办法称号直接界说一个结构器。 -
自有特点( Own properties
)特点出现在实例上而不 I m是原型上,只能在类的结构器或办法内部进行创立。上面name
便是一个自有特点。 -
上面的 sayName()
办法终究也成为PersonClass.prototype
上的一个办法。
因为类的办法运用了简写语法,就不再需求运用
function
关键字。PersonClass
声明实际上创立了一个具有constructor
办法和其他行为的函数,这也是typeof PersonClass
会得到"function"
成果9 # [ o : + 7 _的原因。
3.1、 类声明和函数声明的差异
尽管类与自界说函| . f q T X o t数类型之间有相似性,但还是有一些重要的差异:
-
类声明9 d . 1不会被提高,这与函数界说不同。类声明的行为与 let
相似,因而在程序履行+ ` 9 u –到声明处9 d ` A G 7 – 2 X之前,类# ? 0 T y都会坐落暂时性死区内。
// 类声明不会被提高:Uncaughx $ F k ,t ReferenceError: Cannot access 'Person$ h t ; & & B' before initializatig / ^ ~ o [ Aon
const person = new Persont b k q N - ~ z m('zhangsan')
class Person {
constructor(name) {
this.name = name
}
}
-
类声明中的一切代码会主动运转并锁定在严厉形式下。 -
类的一切办法都是不可枚举的,这是关于函数类型的显著改变,函数类型必须用 O6 9 c y E Qbject.defineProperty()
才能将办法改变为不可枚举。
// 类的一切办法都是不可枚举的
clas4 . S C L q J 3 Ss Person {
constructor(name) {
this.name = name
}
getName(), h Z ; ) 6 ~ {
console.B 9log('getName')
}
}
for (prop in new Person()) {
// name
console.log(prop)
}
function Animal(naZ L x d Yme) {
thij t z T ^ E m s.name = name
}
Animal.prototype = {
getNo B 6 0 b hame() {
consolv s P }e.log(: ` + a'getName')
}
}- e q k z P V V q
for (prop in new Animal()) {
// name
// getName
conso & ] X le.( B { /log(propc X 1)
}
-
类的一切办法内部都没有 [[Construct]]
,因而运用new
来调用它们会抛出过错。 -
调用类结构器时不运用 new
,会抛出过错。 -
在类的办法内部重写类名,会抛出过错。 -
只要在类的内部,类名才被视为是运用 const
声明的,外部的Foo
就像是用let
声明的,但不能在类的7 ~ w h w 0 3 z 办法内部这么做。
class Foo {
constructor() {
//履行时抛出过错
Foo; L : F = 'bar'
}
}
//在类声明之后没问题
Foo = 'baz'
四、类表达式
类与函数相似的点在: l ? ]于都有两种形式:声明与表达式。类和函数相似,也有不需求标识符的表达式形 式。类表达式可以用于变量声明,也可以作为参数传递给函数。如下面代码:
let PersonClass = class {
constructor(nk % 3 e O u `ame) {
this.name = n^ ! 2 came
}
sayName() {
console.log(this.name)
}
}
let person = new PersonClass('zhangsan^ ? W d')
peX n prson.sayName() // "zhangsan"
console.lo[ + P i p : xg(person instanceof PersonClass) // true
console.log(person instanceof Object) // true
consolQ I ^ y l Z f . %e.log(typeof PersonClass) // "functionp x R a C J"
consf ) w u c Tole.log(typeof PersonClass.prototype.sayName) // "function"
运用类声明还是类表达式,主要是代码风格问题。相关于函数声明与函数表达式之间的差异,类声明与类表达式都不会被提高。
4.1、签字表达式
类表达式可以为类表达式命名。假如需求在class
关键字后增加标识符,如下面代码:
let PersonClass = class PersonClaX 7 : Z h Jss2 {
constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}
console.log(typeof PersonClass) // "function"
console.log(typ[ , Ieof PersonClass2) // "undefined"
上面比如中的类表达式被命名为PersonClass2
。PersonClass2
标识符只在类界说内部存在,只能用在类办法内部(如sayName()
内)。在类的外部,typeof PersonClass2
的成果为"undefined"
,是因为外部不存在Perr c @ RsonClass2
绑定。要了解为什么,请查 看未运用类语法的等价声明,如下面代码:
// 等价于 PersX 4 = q # A U y onClass 签字的类表达式
let PersonClass = (function () {
'@ w X Cuse script'
const PersonClass2 = function (name) {
if (type7 + K P yof new.target ===- F V * % 'undefined') {
throw new ErrG { 2 _ V # [or('Constructor must be called with new.')
}
thisa ] ,.name = name
}
Obju @ & _ J 3 d S gect.defineProperty(PersonClass2% c I.prototype, 'sayName', {
value: fun d . [ S e D N &ction () {
if (typeof new.target === 'undefined') {
throw new Error('Method cannot be called with new.')
}
console.log(this.name)
},
enumerable: false,
writable: t@ C D _ 5 *rue,
configurable: true
})
return PersonClass2
}())
-
关于类声明来说,用 let
界说的外部绑定与用const
界说的内部绑定有着{ _ d i %相同的称号。如下:
claV . U ^ H Q Uss Home {
}
console.log(typeof Home) // "function"
-
关于类表达式可在内部运用 const
来界说它的不同称号,所以此处的Perso[ * b G = X & *nClass2
就只能在类的内部运用。
l] & H let PersonClass = class PersonClass2 {
constructor(namp i K + K u C 9e) {
this.namQ / S @ fe = name
}
sayName() {
console.log(this.name)
}
}
console.log(typeofb d K PersonClass) // . m c = 2 } } q z"function"
console.log(typeof PersonClass2) // "undefined"
五、P ! ) Z 4 ~ w W一等公民的类
一等公民意味着着它能作为参数传给函数、能作为函数回来值、能用来给变量赋值。h B X { z VES6
类相同成为一等公民。这就使得类可以| P x $ ; ] ^ 2被多种办法所运用。
5.1、类作为参数传入函数
function createObject(? 3 @classDef) {
return new classDef()
}
const obj = createObject(class {
sayHi() {
console.log('Hi!')
}
})
obj.sayHi() // "Hi!"
5.2、类作为匿名表达式
可以运用匿名类表达式,当即调用类结构器,用于创立单例(Singleton
)。
let person = new class {
constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}('zhangsan')
person.sayN2 g ` ; 2 aame(9 _ ; M W) // "zhangsan"
六、类的拜访y g B D器特点
自有特点需求在类结构器中创立,还可以在原型上界说拜访器特点。创立一个getter
,要运用get
关键字,并要与后方标识符之间留出空格。创立setter
仅仅要改用set
关键字就可以w # G ^ v了。
class customHTMLElement {
constructoh @ e A { | tr(element) {
this.element = element
}
get html() {
return this.element.innerHTML
}
set html(value) {
this.element.innerHTML = value
}
}
// 取得指定目标上一个自有特点(非承继特点)的描述符
const descriptor = Object.getOwnPropz = o ` ( l k tertyDescriptor(customHTMLElement.prototypeb I w n + b = $, 'html')
console.log("get" in descriptS _ b 6 v uow b q J G = W ~ er) // true
console.log("set" inE d H K / % descriptor) // true
consoK E Nle.log(descriptor.enumerable) // false
上面代码中customHTMLElement
类用于包装一个已存在的DOM
元素。它的特点html
具有getter@ . a s d
与setter
,委托了元素本身的innerHTML
办法。拜访器特点被创立在CustomHTMk m j G W F 2 & [LElement.prototype
上,而且像其他类特点那样被创立为不可枚举特点。非类的等价表示如下代码:
let CustomHTMLElement = (function () {
const Cu% U E fstomHTMLElement = function (z 1 pelement) {
if (new.target === 'undefined') {
throw new Errz l h Zor("Constr3 f 2 e D M guctoy { 1r must be called with new.");
}
this.element = element
}
Object.defiE W z + t S g mneProp- $ 1 g ; # serty(CustomHTMLElement.prototype, 'html', {
enumerable: false,
configurab1 L ] kle: true,
get() {
retu) x @rn this.elG - d 4ement.innerHTML
},
set(value) {
this.elemen+ % 0 ~ , P ht.innerHTML = value
}
})
rec F } & Wturn Custo D ( 4 1 ? ] ; LmHTMLEl m u y # O r p Nem| Y D 4 E X N H nent
}())
上面这个比如说明晰运用类语法可以少写大量的代b o W u ? 6 n 0 h码。
七、类的可计算成员名
类办法与类拜访器特点也都能运用可计算的称号。如下面代码:
let methodName = 'sayName'
class Person {
constructor(D # G } jname) {
thisl : #.name = name
}
[methodName]() {
console.log(this.name)
}
}
const me = new Person('zhangsan')
me.sayNama C ; -e() // "zhangsan"
拜访器特点能以相同办法运用可计算的称号。如下面代码:
let propertyName = 'html'
class CustomHTMLElement {
constructor(element) {
this.element =E 6 r U X x - / e5 L A = h 5 Glement
}
get [propertyName]() {
return this.element.inf T P 7 6 H rnerHTML
}
seM x ,t [propertyName](value) {
thiZ y [ C /s.element.innerHTML = value
}
}
八、生成器办法
类允许将任何办法变为一个生成器。如下面代码:
class MyClass {
*createIterator(q 9 a @ d) {
yield 1
yield 2
yield 3
}
}
const instance = new MZ P ! C YyClass()
const iterator = instance.createIterator()
上面代码创立了一个具有createIter; h Qator()
生成器的MyClass
类。该办法回来了一个迭代器目标。也可以界说类的默许迭代器,如下面代码:
c# T 2 p J u }lass Collection {
c0 T E d v Wonstructor() {
this.items = []
}
*[Symbol.iterator]() {
yield* this.items.values()
}
}
var collection = new Collection()
c~ t R % e W b kollz ! : G P ~ + }ection.items.push(1)
collection.items.push(2)
collection.items.push(3)
for (let item of collection) {
// 1
// 2
// 3
console.loY ( eg(item)
}
九、静态成员
直接在结构器上增加额外办法来模仿静态成员,ES5
及更早版别中的形式如下:
function Person(name) {
this.{ u X x W Xname = name
}
// 静态办法
Person.crR F i ( Yeate = function (name) {
return new1 - a h o Person(name)
}
// 实例办法
Person.prototype.sayName = funcM B % X Ntion () {
console.log(this.name)
}
const person = Person.create('zhangsan')
person.sayName() // "zhangsan"
ESJ G g v 16
的类简化] O O S t了静态成员的创立,只要在办法与拜访器特点的称号前增加正式的staH T Qtic
标示。下面有个与上个, c M W 6比如等价的类:
class Person {
constructor(name) {
this.n- 4 h e Tame = name
}
sayName() {
console.log(this.name)
}
static create(name) {] : L 2 k -
return new Person(name)
}
}
const person = Person.create('zhangsan')
person.sayName() // "2 _ u Lzhangsan"
能在类中的任何办法与拜访器特点上运用static
关键字。可是不能将它用于constructor
办法的e s } e ? { k界说。静态成员不能用实例来拜访,一直需求直接用类本身来拜访。
十、类承继
ES6之前,实现自界说类型的承继很麻烦。例如下面的代码:
function FatherL k 8 W(house, knowledge)P 3 r / {
this.house = house
this.knowledge = knowledgeQ Z s ( { 9
}
Father.p, 2 arototype.getKnowledge = function () {
return this.knowlF j !edge
}
function Son(house, knowledge) { $ 8 R _ g h { =
Father.call(this, house, knowledge)
}
// 指定原型目标为Father
Son.prototype = Object.create(Father.prototype, {
constructor:
{
value: Son,
enumerable: true,
wrif o + * [ ) j _ Mtable: true,
configurable: true
}
})
console.log(new Son(- [ 6 ! z S'别墅', '写代码'))
Son
承继了Father
,G n O $ q :Son
必须运用Fac Y 6 ; d V ^ther.prototype
所创立的一个新目标来重写Son.prototype
,而且还要调用Father.I w N k wcall()
办法。上面的图片可以看到承继联系。
类让承继工作变得更容易,运用了解的extends
关键字来指定当前类所需求承继的函数。生成的类的原型会被主动调整,而w A Q R /你还能调用super()
办法来拜访基类的结构器。此处是与上个比如等价的`ES6代码:
class Father {
coK @ w #nstructor(house, knowlj n s p ?edge) {
this.house = house
this.knowledge = knowledg{ z b 5 F 8e
}
getKnowledge() {
return this.knowledge
}
}
class Son extends Father {
// 与 Father.call(this, house, knowledge)
coo a K o F 3 m Enstructor(house, knowledge) {
super(house, knowledge)
}
}
console.log(new Son('别墅', ) R J o f y k Z'写代码'))
Son
运用了extends
关键字承继了Father
。Son
结构器运用了super()
配合指定参数调用了Father
的结构器。
承继了其他类的类| w P F u u R被称为派生类(其实个人认为也可以成为子类)。假如派生类指定了结构器,就需求 运用super()
,否: P F ~ ) ! : 7 _则会形成过错。若选择不运用结构器v J P F ( c 5 V,super()
办法会被主动调用,并会运用创立新实例时提供的一/ n H p @切参数。例如下面两个类是完全相同( p I = .的:
class sonx _ extends FatherA - R ? R r m U ) {
// 没有结构n ; C器
}
// 等价于:
class son extends Father {
constructor(..w F E j w [ L.args) {
super(.2 ( O..args)
}
}
第二个类展示了与一切子类默许结构器等价的写法,一切的参数都按次序传递给了基类的结x j n 6构器。这种做法并不完全准确,最好手动界说结构器。
运用
super()
时需牢记以下几点:
你只能在派生类中运用 super()
。若尝试在非派生的类(运用extends
关键字的类)或Y Z 9 M ~ e函数中运用它,就会抛出过错。在结构器中,你必须在拜访 this
之前调用super()
。因为super()
负责初始化this
,因而企图先拜访this
就J [ W I e会形成过错。若在类的结构器N ; R ~ : K k O T中不调用 super()
,仅有防止出错的办法是在结构器中回来一个目标。@ g # W @ n
十一、屏蔽类办法
派生类中的办法总是会屏蔽基类的同名办法。例如将getKnowledge()
办法增加到Son
类,以便重界说它的功能:
class Son extends Father {( $ ` 1 m p ?
construe 9 f w 2ctor(house, knowledge) {
super(house, knowledge)
}
// 重写并屏蔽 Father.prototype.getKnowledge(u ) k H `)
getKa ~ snowledge() {
return '儿子自己的知识!'
}
}
console.log(new Son('别墅', '写代码'))
因为getKnowledge()
现已被界说为Son
的一部分,Father.protoj ? $ 8 N type.getB Z Q @ k CKnow{ * G t T Q ? -lep { 7 H B # W g Edge()
办法就不能在Son
的任何实例上被调$ | w ) B 2用。可是可以通过运用super.getKnowledge()
办法来调用父类中的该办法,如下面代码:
class Father {
construH P % K C G W #ctor(T o j ] R M f nhouse, knowledge) {
this.house = house
t# E b y X - vhis.knowledge = knowledge
}
getKnowledge() {
return this.knowlI ? c ^edge
}
}
class Son extends Father {
constructor(house, knowledgek V %) {
super(house, knowledge)
}
// 重写、屏& Q o蔽并调用了 Father.prototype.getArea()
getKnowl2 _ medge() {
return super.getKnowlw e j M 3 (edge()
}
}
console.log(new Son('别墅', '写代码'))
十二、承继静态成员
假如父类包含静态成员,那么这些静态成员m O ) q $ q 7 D在派生类中也是可用的。如下面代码:
class Father {
constructor(house, knowledge) {
this.house = housee N C ( 6 c K I
this.knowlr s B 7 T Aedge =9 A A F V Q ; L knowledge
}
static create(house, knowledge) {
return new Father(ho( 1 4 , - n 4 tuse, knowledge)X ~ n H W W
}
}
class Son extend: P } _ Z * Ps Father {
constructor(house, knowledge) {
super(house, knowledge)
}
}
const father = Son.create('别墅& F M', '写代码')
console.log(father instanceof Fathew o b cr) // true
console.log(father instanceof Son) // false
在n F h此代码中,一个新的静态办法create()
被增加到Father
类中。通过承继,该办法会以Son.create()
的形式存在,而且其行为办法与Father.create()
相同。
十三、从表达式中派生类
在ES6中派生类的最强大才能或许便是可以从表达式中派生类。只要一个表达式可以回来一个具有[H 6 _[Construct]]
特点以及原型的函数,就可以对其运用extends
。如下面代码:
function Father(house, knowledge) {
this.house =J # 8 6 _ house
this.know& ] 8ledge = knowledge
}
Father.prototype.getKnowledge = function () {
return thiB ; Q & % ? c !s.knowl% ^ vedge
}
class Son extends Father {
constructor(house, knowledge) {
super(house, know7 ^ N . X Z H 8 /ledge)
}
}
const son = new Son('别墅', '写代码')
console.log(son.getKnowledge()); // 写代码
console.log(son instanceof Father) // true
Father
被界说为ES5
风格的结构器,而Son
则是一个类。因为Father
具有[] l : 5 a & r X[Construct]]
以及原型,Son
类就能直接承继它L a M z k g 9 * %。
extends
后面能承受任意类型的表达式,如动态地决议所要承继的类:
function Father(house, knowledge) {
this.house = house
this.knowledge = knowledge
}
Father.prototype.getKnowledge = function () {
retW C murn this.knowledge
}
function getBase() {
return Father
}
clasp l u u _ # ,s Son extends getBase() {
constructor(house, knowledge) {
super(house, knowledge)
}
}
const son = new Son('别墅', '写代码')
console.log(son instanceof Father) // tr8 s 1 @ L q y ^ue
参考链接
https://book.douban.com/subject/27072230/
https://wizardforcel.gitbooks.io/exploring-es6/md/3/3.2.html
https://es6.7 r ] 0 ^ K Y nruanyife= ` ] % 3 f d : gng.com/#docs/class-extends
https:* y a y [ z U F S//segmentfault.com/a/1190000015424508
https://www.stefanjudiz ! h A ~ z V c Xs.com/today-i-learned/not-every-javascript-function-is-constructable/
本5 Y 5 8 2 w文运用 mdnice 排版