- ECMAScript
- ECMAScript 与JavaScript
- ESMAScript新特性
- ECMAScript 2016
- ECMAScript 2017
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 } = consolelog('foo')log('bar')log('123')
模版字符串 ``
- 反引号包裹
- 允许换行
- 可以通过 ${} 插入表达式, 表达式的执行结果将会输出到相应位置
带标签的模版字符串
// 模版字符串的标签就是一个特殊的函数,使用这个标签就是调用这个函数// const str = console.log`hello world`const name = 'tom'const gender = falsefunction 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)// }// es2015function 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// 完整参数列表,函数体多条语句,返回值仍需 returnconst 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: 087c: 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 指向当前类 personreturn 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 = 0const self = thisreturn {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 = 0return {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 100console.log('2222')yield 200console.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 = 1while (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// ]
