ES 6 class 相关用法

上篇笔记简单对比了,从 ES 5 到 ES 6 类的写法的变化:1、增加了 class 语法糖,而不必再繁琐地先写构造函数然后再在构造函数的 prototype 上添加属性或方法;2、通过 static 关键字增加了静态方法的写法。以及留下一个疑问:那些介绍 ES 6 的书上写的「ES 6 的继承机制与 ES5 完全不同,ES 6 的机制是先由父类创建 this 值,然后再由子类的 contructor 去修改这个值」,这个机制提现在代码实现上究竟是怎样的。我去试了下 Babel,然而看了半天发现 Babel 对 ES 6 的继承的转码实现本质上也依然是上篇笔记里提到的「寄生组合式继承」的加强版:将一些可重用的过程抽象成 _createClassdefineProperties_possibleConstructorReturn_inherits_classCallCheck 等方法,但本质上还是 ES 5 的继承机制。所以如果有谁知道 ES 6 的继承机制体现在代码上究竟是怎样,或者说能这种机制通过 ES 5 的代码没法实现,求告知。

下面稍微详细地介绍一下 ES 6 中类的相关用法,在此之前先了解下 classfunction 的不同:

  • 函数声明可以被提升,而 class 声明生效的机制与 let 一致:
console.log(A) // ReferenceError: A is not defined
class A {}
  • 类声明中所有代码都默认运行在严格模式下,且无法「关闭」这种「开关」
  • 类中所有方法都是不可枚举的:
class A {
  method1() {
    console.log('I am not enumerable')
  }
}
console.log(Object.keys(A.prototype)) // []

function B() {}
B.prototype.method1 = function() {
  console.log('I am enumerable')
}
console.log(Object.keys(B.prototype)) // ['method1']
  • 使用除关键字 new 以外的方式调用类都会报错:
class A {}
A() // Uncaught TypeError: Class constructor A cannot be invoked without 'new'

Babel 中通过 _classCallCheck 这个方法实现此功能。

  • 在类的内部修改类名会报错:
class A {
  constructor() {
    A = 'B'  // Uncaught SyntaxError: Identifier 'A' has already been declared
  }
}
A = B // 但在声明结束时可以修改

类表达式

我们知道函数除了可以声明生成以外还可以作为表达式,同样地,类也可以作为表达式,匿名或命名:

let Person = class {
  constructor(name) {
    this.name = name
  }
  
  sayName() {
    console.log(this.name)
  }
}
let person = new Person('chongErFei')
person.sayName // chongErFei

作为参数和返回值:

function extendsFunc(SubClass)  {
  return class SuperClass extends SubClass {
    // 省略 constructor 等价于 constructor(...args) { super(...args) }
    equippedFunc() {
      console.log('I am equipped by extendsFunc')
    }
  }
}

let EquippedPerson = extendsFunc(Person)
let ep = new EquippedPerson('mjmjxihrni')

ep.sayName() // mjmjxihrni
ep.equippedFunc() // I am equipped by extendsFunc

访问器属性、可计算成员名称

ES 5 中访问器属性需要 Object.defineProperty 来定义,需要写一串很繁琐的代码,而 ES 6 中可以通过直接在属性名前加 get 或是 set 来定义 gettersetter;其实访问器属性在普通的对象字面量就支持,可计算成员以及生成器方法也是,在类里就更不用说了:

let propertyName = 'name'
let methodName  = 'tellAge'
class Person {
  constructor(name, age) {
    this[propertyName] = name
    this.age = age
  }

  get upperCaseName() {
    return this.name.toUpperCase()
  }
  [methodName]() {
    console.log(this.age)
  }
}

let p = Person {name: "lll", age: 24}

p.upperCaseName // LLL
p[methodName] // 24

super

  • super 只能在派生类中使用,否则会报错
  • 派生类的 constructor 中使用 this 前,一定要先调用 super()
  • super 在派生类中有两种含义,在派生类的 constructor 中出现时代表父类的 constructor;而在实例方法中出现时表示父类的 prototype

Symbol.species 属性

Symbol.species 是众多内部 Symbol 的一个,它是一个静态访问器属性,返回值是一个构造函数,ES 6 中 ArrayArrayBufferMapPromiseRegExpSetTyped arrays 等内建类型都有这个属性,如果在自定义的类中实现这个属性,那么它看起来可能是

class MyClass {
  static get [Symbol.species]() {
    return this
  }

  constructor(value) {
    this.value = value
  }

  clone() {
    return new this.constructor[Symbol.species](this.value)
  }
}

,这个属性在我们需要继承内建对象时有用,假如我们现在想构建一个以 Array 为基类的特殊数组:

class MyArray extends Array {
  //  ...
}
let items = new MyArray(1, 2, 3, 4),
    subItems = items.slices(1, 3)

console.log(items instanceof MyArray) // true
console.log(subItems instanceof MyArray) // true

正常情况下,Array.slice() 的返回值应该是一个 Array 类型的,现在它成了 MyArray 类型,这里我们就可以通过重写派生类的 Symbol.species 属性来使凡是调用基类的方法都使用 Array 的实例而不用 MyArray

class MyArray extends Array {
  static get [Symbol.species]() {
    return Array
  }
}
let items = new MyArray(1, 2, 3, 4),
    subItems = items.slices(1, 3)

console.log(items instanceof MyArray) // true
console.log(subItems instanceof MyArray) // false
console.log(subItems instanceof Array) // true

一般说来,只要想在类方法中调用 this.constructor,都应该用 this.constructor[Symbol.species] 从而可以让以此类为基类的派生类改写 [Symbol.species] 属性。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容

  • 原文es6 class class基本声明 在说class之前,想必大家肯定会想到constructor func...
    xiaohesong阅读 503评论 0 3
  • 一、let 和 constlet:变量声明, const:只读常量声明(声明的时候赋值)。 let 与 var 的...
    dadage456阅读 762评论 0 0
  • 第一章:块级作用域绑定 块级声明 1.var声明及变量提升机制:在函数作用域或者全局作用域中通过关键字var声明的...
    BeADre_wang阅读 838评论 0 0
  • 今天和几个同事聊天,谈到一个有业务往来的人,都说她性格很好,从不发脾气,就是很急的资料或数据,她在催你的时候也...
    芳草悠悠_44a1阅读 117评论 0 0
  • 心躁动着 独自望着窗外 那天边的残云 伴着春风 把我对你丝丝的挂念 一同带去远方 是你搅乱了我那颗静谧的心 我的影...
    从心活过阅读 308评论 2 6