ECMAScript新特性

115 阅读4分钟

ECMAScript

ECMAScript 与JavaScript

  • 浏览器环境中 JavaScript —— ECMAScript + Web apis(BOM, DOM)

  • node环境中国 JavaScript —— ECMAScript + Node apis(fs, net, etc)

    JavaScript中语言本身指的就是ECMAScript

ES2015相较之前的变化

  • 对原有语法进行增幅 (解构,展开, 参数默认值, 模版字符串)
  • 解决了原有语法的一些问题或者缺陷 ( let const 块级作用域)
  • 全新的对象, 全新的方法, 全新的功能 (promise, obj.assign)
  • 全新的数据类型和数据结构( Symbol, set, map)

ESMAScript新特性

let const

  • es2015之前(全局作用域, 函数作用域)
  • 块级作用域 ( 代码中 {} 包裹起来的范围)

let

  • let声明的成员只会在所声明的块中生效
  • let在for循环中的表现——可以利用var和let作用域不同,在双for循环中区分计数器.
  • let 应用场景: 循环绑定事件, 事件处理函数中获取正确索引
  • for循环会产生两层作用域
  • let 修复了变量声明提升现象

const

  • 声明恒量/常量,相比let,特点是只读

const只读:
声明的成员不能被修改的意思是,不允许在声明过后重新指向新的内存地址,并不是不能修改属性中的恒量成员

// const name = 'zce'
// name = 'jack'
// 恒量声明过后不允许重新赋值

// const name
// name = 'zce'
// 恒量要求声明同时赋值

// const obj = {}
// obj.name = 'zce'  // success
// 恒量只是要求内层指向不允许被修改
// 对于数据成员的修改是没有问题的

// obj = {} // error  赋值会改变内存指向

最佳实践: 不用 var , 主要用 const , 配合 let

解构

数组的解构

// 数组的解构

const arr = [100, 200, 300]

// 通过下标定位解构
// const foo = arr[0]
// const bar = arr[1]
// const baz = arr[2]
// console.log(foo, bar, baz) => [100, 200, 300]

// 按照顺序填写变量名, 不需要项可以删除, 但要保留逗号
// const [foo, bar, baz] = arr
// console.log(foo, bar, baz) => [100, 200, 300]

// const [, , baz] = arr
// console.log(baz) => 300

// ... 表示提取从当前位置往后的所有成员,会放在一个数组当中(rest),但只能在解构最后一个位置使用
// const [foo, ...rest] = arr
// console.log(rest)  => [200, 300]


// const [foo, bar, baz, more] = arr
// console.log(more) => undefined

// 给解构的数据设置默认值
// const [foo, bar, baz = 123, more = 'default value'] = arr
// console.log(bar, more)

const path = '/foo/bar/baz'
// const tmp = path.split('/')
// const rootdir = tmp[1]

const [, rootdir] = path.split('/')
console.log(rootdir)

对象的解构

const obj = { name: 'zce', age: 18 }

// 通过属性名定位解构
// const { name } = obj
// console.log(name)

// 外部变量名与要解构的属性名重复的情况下可以重命名
// name : onjname  
// 冒号左边是重复的属性名, 右边是重命名之后的属性名
// const name = 'tom'
// const { name: objName } = obj
// console.log(objName)

// 设置默认值
// const name = 'tom'
// const { name: objName = 'jack' } = obj
// console.log(objName)

const { log } = console
log('foo')
log('bar')
log('123')

模版字符串 ``

  • 反引号包裹
  • 允许换行
  • 可以通过 ${} 插入表达式, 表达式的执行结果将会输出到相应位置

带标签的模版字符串

// 模版字符串的标签就是一个特殊的函数,使用这个标签就是调用这个函数 
// const str = console.log`hello world`

const name = 'tom'
const gender = false

function myTagFunc (strings, name, gender) {
  // console.log(strings, name, gender)
  // return '123'
  const sex = gender ? 'man' : 'woman'
  return strings[0] + name + strings[1] + sex + strings[2]
}

const result = myTagFunc`hey, ${name} is a ${gender}.`

console.log(result)

  • 可以实现文本的多语言转换
  • 检查文本字符串中是否存在一些不安全的字符
  • 小型的模版引擎

字符串的扩展方法

  • startsWith 是否以‘’开始
  • endsWith 是否以‘’结尾
  • includes 是否包含‘’

函数参数的默认值

  • 设置默认值使用 形参 = 默认值 方式设置
  • 多个形参, 默认值要写在形参列表最后
  • 默认值只在函数调用没有传实参的情况下生效
function foo (enable = true) {
  console.log('foo invoked - enable: ')
  console.log(enable)
}

foo(false)

剩余参数


// function foo () {
//   console.log(arguments)
// }

// es2015
function foo (first, ...args) {
  console.log(args)
}

foo(1, 2, 3, 4) => [2, 3, 4]

展开数组参数

const arr = ['foo', 'bar', 'baz']
console.log(...arr)

箭头函数

// function inc (number) {
//   return number + 1
// }

// 最简方式
// const inc = n => n + 1

// 完整参数列表,函数体多条语句,返回值仍需 return
const inc = (n, m) => {
  console.log('inc invoked')
  return n + 1
}

console.log(inc(100))

const arr = [1, 2, 3, 4, 5, 6, 7]

// arr.filter(function (item) {
//   return item % 2
// })

// 常用场景,回调函数
arr.filter(i => i % 2)
  • 箭头函数不会改变 this 指向
const person = {
  name: 'tom',
  // sayHi: function () {
  //   console.log(`hi, my name is ${this.name}`)
  // }
  sayHi: () => {
    console.log(`hi, my name is ${this.name}`)
    // name is undefined
  },
  sayHiAsync: function () {
    // const _this = this
    // setTimeout(function () {
    //   console.log(_this.name)  // 取不到
    // }, 1000)

    console.log(this)
    setTimeout(() => {
      // console.log(this.name)
      console.log(this)
    }, 1000)
  }
}

person.sayHiAsync()

对象字面量

  • 属性名与变量名相同, 可以省略 // bar: bar, => bar,
  • 方法可以省略 method1: function () {} => method1 () {}
  • 给对象添加动态属性名
// Math.random() :  123   不被允许
// 通过 [] 让表达式的返回值作为属性名
const obj = {
  bar: 123
}
obj[Math.random()] = 456

Object.assign

  • 将多个源对象中的属性复制到一个目标对象中
const data1 = {
  a: 123,
  b: 123
}
const data2 = {
  a: 4567,
  b: 4567
}
const data = {
  b: 087
  c: 824,
  d: 789
}

const reslut = Object.assign(data, data1, data2)
const reslut2 = Object.assign({}, data1, data2)

console.log(data)
console.log(reslut === data)

// 合并的结果和目标对象完全相同, 指向同一内存地址.改一个另一个同时改
// 可以通过设置目标对象为空对象实现一个全新的对象, 修改就不会影响外部数据

Object.is

  • == 会默认转换类型进行比较
  • === 严格对比数值是否相同

=== 的两个缺陷

  • -0 === +0 => true
  • NaN === NaN => false
// Object.is(+0, -0)       // => false
// Object.is(NaN, NaN)     // => true

Proxy 对象

// Proxy 对象

// const person = {
//   name: 'zce',
//   age: 20
// }

// target 代理对象
// property 监听变化的属性名
// const personProxy = new Proxy(person, {
//   // 监视属性读取
//   get (target, property) {
//     return property in target ? target[property] : 'default'
//     // console.log(target, property)
//     // return 100
//   },
//   // 监视属性设置
//   set (target, property, value) {
//     if (property === 'age') {
//       if (!Number.isInteger(value)) {
//         throw new TypeError(`${value} is not an int`)
//       }
//     }

//     target[property] = value
//     // console.log(target, property, value)
//   }
// })

// personProxy.age = 100

// personProxy.gender = true

// console.log(personProxy.name)
// console.log(personProxy.xxx)

Proxy 对比 Object.defineProperty() ===============

// 优势1:Proxy 可以监视读写以外的操作 --------------------------

// const person = {
//   name: 'zce',
//   age: 20
// }

// const personProxy = new Proxy(person, {
//   deleteProperty (target, property) {
//     console.log('delete', property)
//     delete target[property]
//   }
// })

// delete personProxy.age
// console.log(person)

// 优势2:Proxy 可以很方便的监视数组操作 --------------------------

// const list = []

// const listProxy = new Proxy(list, {
//   set (target, property, value) {
//     console.log('set', property, value)
//     target[property] = value
//     return true // 表示设置成功
//   }
// })

// listProxy.push(100)
// listProxy.push(100)

// 优势3:Proxy 不需要侵入对象 --------------------------

// const person = {}

// Object.defineProperty(person, 'name', {
//   get () {
//     console.log('name 被访问')
//     return person._name
//   },
//   set (value) {
//     console.log('name 被设置')
//     person._name = value
//   }
// })
// Object.defineProperty(person, 'age', {
//   get () {
//     console.log('age 被访问')
//     return person._age
//   },
//   set (value) {
//     console.log('age 被设置')
//     person._age = value
//   }
// })

// person.name = 'jack'

// console.log(person.name)

// Proxy 方式更为合理
const person2 = {
  name: 'zce',
  age: 20
}

const personProxy = new Proxy(person2, {
  get (target, property) {
    console.log('get', property)
    return target[property]
  },
  set (target, property, value) {
    console.log('set', property, value)
    target[property] = value
  }
})

personProxy.name = 'jack'

console.log(personProxy.name)

Reflect 对象

  • 统一提供一套用于操作对象的api
  • proxy 内部get方法不设置默认用reflect.get
// const proxy = new Proxy(obj, {
//   get (target, property) {
//     return Reflect.get(target, property)
//   }
// })

// console.log('name' in obj)
// console.log(delete obj['age'])
// console.log(Object.keys(obj))

// 统一方法, 避免方法和操作符混用带来的不便
// console.log(Reflect.has(obj, 'name'))
// console.log(Reflect.deleteProperty(obj, 'age'))
// console.log(Reflect.ownKeys(obj))

promise

class 关键字

// class 关键词

// function Person (name) {
//   this.name = name
// }

// 创建共享方法
// Person.prototype.say = function () {
//   console.log(`hi, my name is ${this.name}`)
// }

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${this.name}`)
  }
}

const p = new Person('tom')
p.say()

static

实例方法 - 通过这个类型构造的实例对象去调用

静态方法 - 直接通过类型本身调用

  • 以前实现静态方法: 直接在构造函数对象上去挂载方法
  • ES2015中新增添加静态成员static的关键词

this问题: 因为static构建的静态方法是挂载在类上面的,所以静态方法的this就不会指向实例对象,而是指向当前的类型

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${this.name}`)
  }

  static create (name) {
    // this 指向当前类 person
    return new Person(name)
  }
}

const tom = Person.create('tom')
tom.say()

extends 继承

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${this.name}`)
  }
}

class Student extends Person {
  constructor (name, number) {
    super(name) // 通过super调用父类构造函数
    this.number = number
  }

  hello () {
    super.say() // 通过super调用父类构造函数
    console.log(`my school number is ${this.number}`)
  }
}

const s = new Student('jack', '100')
s.hello()

Set 数据结构

const newSet = new Set();

  • newSet.forEach(i => console.log(i))
  • for (let i of newSet) { console.log(i) }
  • newSet.has(value) // 集合中是否有特定值
  • newSet.size // 集合的长度
  • newSet.delete(value) // 删除特定值, 删除成功返回true
  • newSet.clear() // 清除集合中的全部内容

数组去重

const arr = [1, 2, 1, 4, 5, 2, 5]

  • const result = Array.from(new Set(arr))
  • const result = [...new Set(arr)]

map 数据结构

  • 可以存储任意类型作为键
  • object - 字符串 / 值
  • map - 值 / 值
const m = new Map()

const tom = { name: 'tom' }

m.set(tom, 90)

console.log(m)

console.log(m.get(tom))

// m.has()
// m.delete()
// m.clear()

m.forEach((value, key) => {
  console.log(value, key)
})

Symbol 数据类型

  • 两个 Symbol 永远不会相等
  • Symbol 可以传入字符串作为描述文本
  • 使用 Symbol 为对象添加用不重复的键
    • object键类型 ( 字符串、 Symbol )
  • 也可以在计算属性名中使用
  • Symbol 模拟实现私有成员
// 两个 Symbol 永远不会相等

// console.log(
//   Symbol() === Symbol()
// )

// Symbol 描述文本

// console.log(Symbol('foo'))
// console.log(Symbol('bar'))
// console.log(Symbol('baz'))

// 使用 Symbol 为对象添加用不重复的键

// const obj = {}
// obj[Symbol()] = '123'
// obj[Symbol()] = '456'
// console.log(obj)

// 也可以在计算属性名中使用

// const obj = {
//   [Symbol()]: 123
// }
// console.log(obj)

// =========================================================

// 案例2: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 补充

// console.log(
//   // Symbol() === Symbol()
//   Symbol('foo') === Symbol('foo')
// )

// Symbol 全局注册表 ----------------------------------------------------

// const s1 = Symbol.for('foo')
// const s2 = Symbol.for('foo')
// console.log(s1 === s2)

// Symbol 内部维护了一个字符串对应Symbol的一个注册表, 是一一对应的
// 对传入的值会默认转换成字符串类型
// console.log(
//   Symbol.for(true) === Symbol.for('true')
// )

// 内置 Symbol 常量 ---------------------------------------------------

// console.log(Symbol.iterator)
// console.log(Symbol.hasInstance)

// const obj = {
//   [Symbol.toStringTag]: 'XObject'
// }
// console.log(obj.toString())

// Symbol 属性名获取 ---------------------------------------------------

const obj = {
  [Symbol()]: 'symbol value',
  foo: 'normal value'
}

// 获取不到
// for (var key in obj) {
//   console.log(key)
// }
// console.log(Object.keys(obj))
// console.log(JSON.stringify(obj))

// 获取的正确姿势
console.log(Object.getOwnPropertySymbols(obj))

for...of 循环

  • for 适合遍历普通的数组
  • for...in 适合遍历键/值对
  • 数组对象的forEach 无法跳出循环, for...of 可以使用 break 关键字
// 遍历 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) {
//   console.log(item)
// }

迭代器 ( Iterator )

总结

  • 所有可以直接被for...of遍历的数据类型,都必须实现了统一的Iterable接口( 内部必须挂载iterator方法, 这个方法会返回一个带有next方法的对象,不断调用这个next方法就可以实现遍历, next方法返回的对象内部的value 为当前遍历的键值对. done表示循环是否结束 )

const set = new Set(['foo', 'bar', 'baz'])

const iterator = set[Symbol.iterator]()

// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())

while (true) {
  const current = iterator.next()
  if (current.done) {
    break // 迭代已经结束了,没必要继续了
  }
  console.log(current.value)
}

实现可迭代接口(Iterable)

// const obj = {
//   [Symbol.iterator]: function () {
//     return {
//       next: function () {
//         return {
//           value: 'zce',
//           done: true
//         }
//       }
//     }
//   }
// }

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 函数

  • 在函数方法名前加星号( * )
  • yield会暂停执行, 后面的值会作为构造器next()方法返回

// function * foo () {
//   console.log('zce')
//   return 100
// }

// const result = foo()
// console.log(result.next())

function * foo () {
  console.log('1111')
  yield 100
  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)
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) {
      yield item
    }
  }
}

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

ECMAScript 2016

  • Array.prototype.includes
  • 指数运算符

// 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)

ECMAScript 2017

  • Object.values (值数组)
  • Object.entries (键值对数组)
  • Object.getOwnPropertyDescriptors
  • String.prototype.padStart / String.prototype.padEnd
  • 在函数参数中添加尾逗号
  • async/await

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

// Object.values -----------------------------------------------------------

// console.log(Object.values(obj))

// Object.entries ----------------------------------------------------------

// console.log(Object.entries(obj))

// for (const [key, value] of Object.entries(obj)) {
//   console.log(key, value)
// }

// console.log(new Map(Object.entries(obj)))

// Object.getOwnPropertyDescriptors ----------------------------------------

// const p1 = {
//   firstName: 'Lei',
//   lastName: 'Wang',
//   get fullName () {
//     return this.firstName + ' ' + this.lastName
//   }
// }

// // console.log(p1.fullName)

// // const p2 = Object.assign({}, p1)
// // p2.firstName = 'zce'
// // console.log(p2)

// const descriptors = Object.getOwnPropertyDescriptors(p1)
// // console.log(descriptors)
// const p2 = Object.defineProperties({}, descriptors)
// p2.firstName = 'zce'
// console.log(p2.fullName)

// 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')}`)
// }

// 在函数参数中添加尾逗号  -----------------------------------------------------

// function foo (
//   bar,
//   baz,
// ) {

// }

// const arr = [
//   100,
//   200,
//   300,
// ]
// const arr = [
//   100,
//   200,
//   300,
//   400,
// ]
// const arr = [
//   100,
//   200,
//   300
// ]
// const arr = [
//   100,
//   200,
//   300,
//   400
// ]