ECMAScript新特性(二)

Set数据结构

Set与Array是十分相似的,不过Set不允许值重复

const s = new Set()
s.add(1).add(2).add(3).add(4).add(1) // add返回的还是set类型,所以可链式调用
console.log(s) // => Set [1,2,3,4] 重复的1会被忽略掉

// 依然可以使用forEach等数组循环方法
s.forEach(i => console.log(i))

// 一些其他的set方法,某些与Reflect较相似
console.log(s.size)
console.log(s.has(100))
console.log(s.delete(3))
s.clear() // 清除全部内容

// 应用场景:数组去重
const arr = [1, 2, 1, 3, 4, 1]
// Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。
// const result = Array.from(new Set(arr)) 
const result = [...new Set(arr)] // 这两种方法都可
console.log(result)

Map数据结构

Map与对象是十分相似的,本质都是健值对集合,Map类型可以映射两个任意类型的数据类型

const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'

console.log(Object.keys(obj)) // => ["123", "true", "[object Object]"]
// obj的key都会被强制toString转化成string形式

// Map类型可以映射两个任意类型的数据类型
const m = new Map()
const tom = { name: 'tom' }
m.set(tom, 90)
console.log(m) // => { { name: 'tom' } => 90}
// 如果m[tom] 还是会"[object Object]"

console.log(m.get(tom))
// m.has()
// m.delete()
// m.clear()
// 依然可以遍历
m.forEach((value, key) => {
  console.log(value, key)
})

Symbol

一种全新的数据类型

// 场景:扩展对象,属性名冲突问题
// main.js ====================================
const cache = {};
// a.js =========================================
cache['foo'] = Math.random();
// b.js =========================================
cache['foo'] = '123';
console.log(cache); // => {foo: 123}

比如在main 中有一个缓存对象,a.js与b.js都会向其中添加属性,较容易就造成覆盖,从前的解决办法就是约定a文件就以 a_foo开头,但是人为约定并不一定都遵守,所以出现了Symbol

let a = Symbol('foo') // 声明一个Symbol类型,foo只是给他加的名字,方便辨认
console.log(Symbol('foo') === Symbol('foo')) // 每次通过Symbol创建的值一定是唯一的,不会重复
// obj除了字符串外,还有Symbol类型
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj) // => {Symbol(): "123", Symbol(): "456"}

Symbol 模拟实现私有成员

// a.js ======================================
const name = Symbol()
const person = {
    [name]: 'zce',
    say () {
        console.log(this[name])
    }
}
// 只对外暴露 person

// b.js =======================================

// 由于无法创建出一样的 Symbol 值,
// 所以无法直接访问到 person 中的「私有」成员
// person[Symbol()]
person.say()

Symbol最主要的作用就是为对象添加一个独一无二的属性名
如果想在全局复用一个相同的Symbol

// 全局注册表
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
const s3 = Symbol('foo')
console.log(s1 === s2) // => true
console.log(s2 === s3)
// 通过Symbol.for 可以复用相同的Symbol
const obj1 = {}
console.log(obj1.toString()) // =>[object Object]

const obj2 = {
  [Symbol.toStringTag]: 'XXObject'
};
console.log(obj2.toString()) // =>[object XXObject]
// 这种Symbol,在后面我们为对象实现迭代器中会经常使用
const obj = {
    [Symbol()]: 'symbol value',
    foo: 'normal value'
}
for (var key in obj) {
  console.log(key) // 通过这两种方式都无法拿到Symbol类型的属性名
}
console.log(Object.keys(obj)) // 通过这两种方式都无法拿到Symbol类型的属性名
console.log(JSON.stringify(obj)) // Symbol也会被忽略掉

所以Symbol很适合用来实现私有属性名

// 当然也有方法获取Symbol属性
console.log(Object.getOwnPropertySymbols(obj))
// => [Symbol()]

for...of循环

for 适合遍历普通数组(array,set)
for in 适合遍历健值对(object,map)
还有一些对象方法,例如forEach
这些遍历方式都有些局限性,于是ES2015引入了for...of循环,它以后会成为遍历所有数据结构的统一方式

const arr = [100, 200]

for (const item of arr) {
  console.log(item) // => 100 200  不同于for in拿到下标
}

// for...of 循环可以替代 数组对象的 forEach 方法
arr.forEach(item => {
  console.log(item)
})
for (const item of arr) {
  console.log(item)
  if (item > 100) {
    break
  }
}
// forEach 无法跳出循环,必须使用 some 或者 every 方法
arr.forEach() // 不能跳出循环
// 遍历 Set 与遍历数组相同
const s = new Set(['foo', 'bar'])
for (const item of s) {
  console.log(item) // =>'foo' 'bar'
}
// 遍历 Map 可以配合数组结构语法,直接获取键值
const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
for (const [key, value] of m) {
  console.log(key, value)
}
// 普通对象不能被直接 for...of 遍历
const obj = { foo: 123, bar: 456 }
for (const item of obj) { // => obj is not iterable (obj对象不可被迭代)
   console.log(item)
}

上面明明说了所有数据结构,那普通对象为啥会报错,所不可被迭代呢?

可迭代接口

为了给各种各样的数据结构,统一遍历方式,ES2015提供了一个Iterable接口。

接口,可以把它理解为一种规格标准,比如任意一种数据类型都有toString方法,那就是因为他们都实现了统一的规格标准,那在编程中,更专业的说法就是,他们都实现了统一的接口。



Symbol.Iterator是Symbol类型提供的一个常量

实现Iterable接口,就是for...of的前提
所有可以直接被for...of遍历的数据类型,它都必须要实现Symbol.Iterator的接口,也就是在他的原型对象上必须要挂载一个Iterator,Iterator对象上有一个next方法,调用next方法,就可以对数据内部所有数据的遍历,这也就是for...of循环的工作原理

// 迭代器(Iterator)
const set = new Set(['foo', 'bar', 'baz'])
const iterator = set[Symbol.iterator]()
console.log(iterator.next()) // {value: "foo", done: false}
console.log(iterator.next()) // {value: "bar", done: false}
console.log(iterator.next()) // {value: "baz", done: false}
console.log(iterator.next()) // {value: undefined, done: true}

实现可迭代接口

所以对象上也实现了Iterator接口,那么就可以遍历

const obj = {
  [Symbol.iterator]: function () { // 用计算属性给obj挂一个Symbol.iterator,它是一个函数
    return { // 这个函数返回一个对象(iterator)
      next: function () { // 该对象有一个next方法
        return { // 调用这个方法,会返回 {value: xxx, done: xxx} 格式的对象(iterationResult)
          value: 'zh',
          done: true // 代表迭代是否结束
        }
      }
    }
  }
}
// 试试迭代store
const obj = {
  store: ['foo', 'bar', 'baz'],

  [Symbol.iterator]: function () {
    let index = 0
    const self = this

    return {
      next: function () {
        const result = {
          value: self.store[index],
          done: index >= self.store.length
        }
        index++
        return result
      }
    }
  }
}
for (const item of obj) {
  console.log('循环体', item)
}

迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

// 迭代器设计模式

// 场景:你我协同开发一个任务清单应用

// 我的代码 ===============================

const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '外语'],
  work: ['喝茶'],

  // 提供统一遍历访问接口
  each: function (callback) {
    const all = [].concat(this.life, this.learn, this.work)
    for (const item of all) {
      callback(item)
    }
  },

  // 提供迭代器(ES2015 统一遍历访问接口)
  [Symbol.iterator]: function () {
    const all = [...this.life, ...this.learn, ...this.work]
    let index = 0
    return {
      next: function () {
        return {
          value: all[index],
          done: index++ >= all.length
        }
      }
    }
  }
}

// 你的代码 ===============================

// for (const item of todos.life) {
//   console.log(item)
// }
// for (const item of todos.learn) {
//   console.log(item)
// }
// for (const item of todos.work) {
//   console.log(item)
// }

todos.each(function (item) {
  console.log(item)
})

console.log('-------------------------------')

for (const item of todos) {
  console.log(item)
}

生成器

避免异步编程中回调嵌套过深,提供更好的异步编程方案

// Generator
function * foo () {  // 加*代表生成器函数
  console.log('zh')
  return 100
}

const result = foo()
console.log(result) // =>foo {<suspended>} foo并没有执行
console.log(result.next()) // => 调用他的next方法才执行 输出{value: 100, done: true}

惰性执行,抽一下动一下

function * foo () {
    console.log('1111')
    yield 100 // 类似return
    console.log('2222')
    yield 200
    console.log('3333')
    yield 300
}

const generator = foo()

console.log(generator.next()) // 第一次调用,函数体开始执行,遇到第一个 yield 暂停
console.log(generator.next()) // 第二次调用,从暂停位置继续,直到遇到下一个 yield 再次暂停
console.log(generator.next()) // 。。。
console.log(generator.next()) // 第四次调用,已经没有需要执行的内容了,所以直接得到 undefined

Generator 应用

// 案例1:发号器(自增)
function * createIdMaker () {
  let id = 1
  while (true) {
    yield id++
  }
}
const idMaker = createIdMaker()

console.log(idMaker.next().value)
console.log(idMaker.next().value)

// 案例2:使用 Generator 函数实现 iterator 方法
const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '外语'],
  work: ['喝茶'],
  [Symbol.iterator]: function * () {
    const all = [...this.life, ...this.learn, ...this.work]
    for (const item of all) {  // 这里有个疑问,为什么没有next
      yield item
    }
  }
}

for (const item of todos) {
  console.log(item)
}

ES Modules

语言层面的模块化标准 (import、export)

ES2016

// ECMAScript 2016

// Array.prototype.includes -----------------------------------

const arr = ['foo', 1, NaN, false]

// 找到返回元素下标
console.log(arr.indexOf('foo'))
// 找不到返回 -1
console.log(arr.indexOf('bar'))
// 无法找到数组中的 NaN
console.log(arr.indexOf(NaN))

// 直接返回是否存在指定元素
console.log(arr.includes('foo'))
// 能够查找 NaN
console.log(arr.includes(NaN))

// 指数运算符 ---------------------------------------------------

// console.log(Math.pow(2, 10))
console.log(2 ** 10)

ES2017

// ECMAScript 2017

const obj = {
   foo: 'value1',
   bar: 'value2'
}

// Object.values -----------------------------------------------------------
// keys返回 健的数组,values返回 值的数组
console.log(Object.values(obj)) // => ["value1", "value2"]

// Object.entries ----------------------------------------------------------
// entries返回 健值对的数组
console.log(Object.entries(obj))
// 这样就可以直接使用for of遍历普通对象
for (const [key, value] of Object.entries(obj)) {
    console.log(key, value)
}
// 普通对象转化成Map类型对象
console.log(new Map(Object.entries(obj)))

// String.prototype.padStart / String.prototype.padEnd  --------------------
// 向前补充,向后补充
const books = {
  html: 5,
  css: 16,
  javascript: 128
}

// for (const [name, count] of Object.entries(books)) {
//   console.log(name, count)
// }

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