一、前语

最近准备记录自己从0到1学习React的过程的一些知识点和心得总结。第一章便是了解ES6Class。如有过错,请及时指出。

二、ES6之前的仿类

JSES5及更早版别中都不存在类。与类最接近的是:创立一个结构函数,然后将办法增加到该结构器的原型上。如下面代码:

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的时分需求注意的几点:

  1. 类声明以class关键字开始,其后是类的称号。
  2. 办法之间不需求运用逗号。
  3. 在其中运用特别的construcY Q y C M ! B 9tor办法称号直接界说一个结构器。
  4. 自有特点(Own properties)特点出现在实例上而不 I m是原型上,只能在类的结构器或办法内部进行创立。上面name便是一个自有特点。
  5. 上面的sayName()办法终究也成为PersonClass.prototype上的一个办法。

因为类的办法运用了简写语法,就不再需求运用function关键字。PersonClass声明实际上创立了一个具有constructor办法和其他行为的函数,这也是typeof PersonClass会得到"function"成果9 # [ o : + 7 _的原因。

3.1、 类声明和函数声明的差异

尽管类与自界说函| . f q T X o t数类型之间有相似性,但还是有一些重要的差异:

  1. 类声明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
  }
}
  1. 类声明中的一切代码会主动运转并锁定在严厉形式下。
  2. 类的一切办法都是不可枚举的,这是关于函数类型的显著改变,函数类型必须用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)
}
  1. 类的一切办法内部都没有[[Construct]],因而运用new来调用它们会抛出过错。
  2. 调用类结构器时不运用new,会抛出过错。
  3. 在类的办法内部重写类名,会抛出过错。
  4. 只要在类的内部,类名才被视为是运用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"

上面比如中的类表达式被命名为PersonClass2PersonClass2标识符只在类界说内部存在,只能用在类办法内部(如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
}())
  1. 关于类声明来说,用let界说的外部绑定与用const界说的内部绑定有着{ _ d i %相同的称号。如下:
claV . U ^ H Q Uss Home {

}
console.log(typeof Home) // "function"
  1. 关于类表达式可在内部运用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 dsetter,委托了元素本身的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'别墅', '写代码'))

React从0到1系列第一章——理解ES6 ClassSon承继了FatherG 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'写代码'))
React从0到1系列第一章——理解ES6 Class

Son运用了extends关键字承继了FatherSon结构器运用了super()配合指定参数调用了Father的结构器。

承继了其他类的类| w P F u u R被称为派生类(其实个人认为也可以成为子类)。假如派生类指定了结构器,就需求 运用super(),否: P F ~ ) ! : 7 _则会形成过错。若选择不运用结构器v J P F ( c 5 Vsuper()办法会被主动调用,并会运用创立新实例时提供的一/ 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() 时需牢记以下几点:

  1. 你只能在派生类中运用super()。若尝试在非派生的类(运用extends关键字的类)或Y Z 9 M ~ e函数中运用它,就会抛出过错。
  2. 在结构器中,你必须在拜访this之前调用super()。因为super()负责初始化 this,因而企图先拜访thisJ [ W I e会形成过错。
  3. 若在类的结构器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('别墅', '写代码'))
React从0到1系列第一章——理解ES6 Class

因为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 排版