ES干货集锦
ES6
- ES6即ES2015
- 之后有相继发布了 ES2016, ES2017
- ES6是一个历史名此, 亦是一个泛指, 含义是5.1版本以后的JS的下一代标准, 它涵盖了ES2015 ES2016 ES2017等, 而ES2015则是正式名称, 特指该年发布的正式版本的语言标准
babel
-
npm init -y使用默认配置创建项目(生成默认的package.json) -
安装babel
npm i --save-dev babel-cli babel-preset-es2015- babel一般不全局安装, 而使用局部安装
- 使用全局安装会导致项目对babel产生依赖, 如果之后的不同项目使用的是babel的不同版本, 那么将难以处理
babel-cli是babel命令行工具, 它支持我们在命令行执行babel的一些命令babel-preset-es2015是babel的规则集, 即babel如何转换代码, 这条指令就是转换es2015
- babel一般不全局安装, 而使用局部安装
-
使用babel
-
在项目根目录下新建
.babelrc, 输入如下内容:{ "presets": ["es2015"] } -
在src下新建一个
test.jslet a = 2; -
因为babel是局部安装, 所以需要指定位置才能运行
.\node_modules\.bin\babel src\test.js --out-file dist\test.js解释: babel的位置 要转换的文件 输出格式 输出文件的保存位置和文件名 -
如果有多个文件需要转换呢?
.\node_modules\.bin\babel src --out-dir dist解释: 将src目录的所有文件都进行转换,转换后的文件保存在dist目录下 -
每次更改代码都要运行一个转换命令, 有些麻烦, 当然babel可以自动监听代码并实时转换
在
package.json的scripts中添加一个脚本:"dev": "babel src -w -d dist",即可自动监听并转换src目录下代码 单独监听某个文件也可以:"dev": "babel src/test.js -w -o dist/test.js"
-
命令行的快捷键
Tab键可以自动补全输入的内容- 上键和下键可以重复命令历史记录
新的变量声明关键字 let 与 const
-
作用域
-
全局作用域
-
var i = 2; for(var i = 0; i < 10; i++) { } console.log(i) // 输出10 因为var声明是全局生效
-
-
局部作用域
-
// 在函数中声明的变量具有局部作用域即只能在函数中访问 // 下面是一个自调用函数, 在前面加一个 ! 就不会 // 发生奇怪的报错了, 因为 ! 将后面的式子转换成了表达式 !(function(){ console.log(b) // 输出 undefined 这个输出很奇怪,明明还未声明变量b,为何输出undefined? var b = 10; })() // 上面的问题, 是因为js有一个默认行为,(变量声明提取) 上面的写法等价于: !(function(){ var b; console.log(b) // 这样写就清晰多了, 当然输出undefined了 b = 10; })() // 上面这种变量提取显得奇怪, 变量明明没有声明竟然也可以使用 -
块级作用域
-
// ES6就加入了块级作用域 --解决上文奇怪的写法 // 块级作用域 -- let const if(true) { let a = 4; } console.log(a); // 报错, 无法访问其他块级中的变量 // 也不会有变量提升了, 必须在声明后才能使用变量 // 在声明变量前,是无法对这个变量进行操作的 --也就是 '暂时性死区' // 只在声明的代码块中生效 // 变量无法重复声明
-
-
- let
- 用法: 声明一个变量
- 特点:
- 只在声明的代码块内有效
- 在同一作用域内不允许重复声明
- 没有变量提示
- 暂时性死区
-
const
- 用法: 声明一个只读的变量(可理解为常量)
- 特定: 与 let 相同
- 注意事项:
- 变量声明的同时必须立即赋值
- 如声明的是简单类型的数据, 变量的值不可改变
-
实质: 保证变量指向的内存地址所保存的数据不允许改动.
简单类型如字符串、数字和布尔值, 值就保存在变量指向的内存地址. 而复杂类型的数据如对象、数组和函数, 变量指向的内存地址, 实际上是保存了指向实际数据的指针. 所以const只能保证指针是固定的, 至于指针指向的数据结构变不变就无法控制了, 所以使用const声明复杂类型的对象是要慎重.
JS的第七种数据类型 Symbol
- ES6之前的JS数据类型
- Number(数字)
- String(字符串)
- Boolean(布尔值)
- Object(对象)
- Null(空对象指针)
- Undefined(声明的变量未被初始化时)
- 引入的背景
- 对象的属性名容易产生命名冲突, 为保证键名的唯一性, 故es6引入Symbol这种新的原始数据类型, 确保创建的每个变量都是读一无二的
-
特点
-
Symbol类型的数据是类似字符串的数据类型, 由于Symbol函数返回的是原始类型的数据, 不是对象, 故Symbol函数前不能使用new命令, 否则会报错
-
可选参数. 由于控制台输出不同的Symbol变量时都是Symbol(), 故为了区分, 可在创建Symbol变量时传入参数进行区分. 如:
// 这里的a1 , a2 的作用可以说是为了备注, 以至于我们输出Symbol变量时能够区分不同的变量 let a1 = Symbol('a1') let a2 = Symbol('a2')
-
-
用法
-
定义对象的唯一属性名
//在对象里用Symbol作为属性名的三种写法 let name = Symbol() // 第一种方式: 借助数组读取name变量, 此时不能用点运算符, 点运算符默认后面的参数是字符串 let a = {} a[name] = 'Nick' //第二种方式: 构造时声明 let a = { [name]: 'Nick' } //第三种 Object.defineProperty let a = {} Object.defineProperty(a, name, {value: 'Nick'}); -
定义常量
// 定义字符串常量 const name = Symbol("Nick");
-
解构赋值
-
介绍
- 解构赋值可以理解为赋值操作的语法糖, 它是针对数组或者对象进行模式匹配, 然后对其中的变量进行赋值. 代码书写上言简意赅, 语义明确, 也便于对对象数据进行读取操作.
-
实质
- ES6中只要某种数据有Iterator接口(也就是可以循环迭代), 都可以进行数组的解构赋值.
-
使用场景
//数组解构 { let a, b, c [a, b, c] = [1, 2] console.log(a, b, c) //1, 2, undefined } { let a, b, c [a, b, c = 6] = [1, 2] console.log(a, b, c) //1, 2, 6 } { let a, other [a, ...other] = [1, 2, 3] console.log(a, other) // 1, [2, 3] } { let a, b [a,,b] = [1, 2, 3] console.log(a, b) //1, 3 } //解构对象 { let a, b ( {a, b} = {a:2, b:3} ) console.log(a, b) // 2, 3 } { let num, total ( {a: num, b: total} = {a:2, b:3} ) console.log(num, total) //2, 3 } // 常见的使用场景 { function fn() { return { name: 'Nick', nameList: [{ name: 'KK' }] } } let b = fn(); let {name: person, nameList: [{name: otherPerson}]} = b; console.log(person, otherPerson) //Nick KK }
字符串的扩展方法及模板字符串
// es5
{
const str1 = 'a'
const str2 = '\u20bb7'
console.log(str2) //乱码, 超出了es5可以处理的范围
}
// es6
{
const str3 = '\u{20bb7}'
console.log('str3', str3)
}
// for of ---适用范围更广的for循环
{
const str3 = '\u{20bb7}'
// 传统的for循环
for(let i = 0; i < str3.length; i++) {
console.log('for', str3[i]) //乱码
}
for(let word of str3) {
console.log('for-of', word)
}
}
// 判断字符串中是否包含指定字符串的几个方法
{
let str = '123Nick'
// 判断字符串中是否包含指定字符串
console.log('includes', str.includes('Nick')) // true
// 判断字符串是否以指定字符串开头
console.log('startsWith', str.startsWith('Nick')) // false
// 判断字符串第n个字符串开始是否包含指定字符串
console.log('startsWith', str.startsWith('Nick', 3)) // true
// 判断字符串自开头开始往后多少位前是否含有指定字符串
console.log('endsWith', str.endsWith('Nick', 7)) // true
}
// 将字符串重复指定次数
{
let str = 'Nick123'
str = str.repeat(3)
console.log('repeat', str)
}
// 字符串补全
{
let str = 'Apple'
// 头部补全----第一个参数是补全后的字符串长度,第二个参数是用来补全的字符串
str = str.padStart(8, 'asdad')
console.log('padStart', str) //asdApple
// 尾部补全函数是: padEnd(), 用法同上
}
// 模板字符串
{
const name = 'nick'
const age = 18
const str = `我叫${name},我今年${age}岁.`
console.log(str)
}
- 使用模板字符串的注意事项
- 在模板字符串中如需使用反引号, 反引号前要用反斜杠转义
- 使用模板字符串表示多行字符串时, 所有的空格和缩进都会被保留在输出中
- 模板字符串中引入变量, 要用
${变量名}这样的形式引入才可以 - 大括号中的值不少字符串时, 将按照一般的规则转为字符串. 比如, 大括号中是一个对象, 将默认调用对象的toString方法
- 模板字符串中的
${......}大括号内部可以放入任意的JavaScript表达式, 可以进行运算、可以引用对象属性、可以调用函数、可以甚至还能嵌套, 甚至还能调用自己本身
数组的扩展方法及扩展运算符的使用
-
…
- 复制数组
let list2 = [...list]
- 分割数组
let [, ...strList] = totalList
- 给函数传参
add(...addList)
- 复制数组
-
新api
-
fill
- 按指定方式填充数组
let list2 = [...list].fill(3)let list3 = [...list].fill(3, 1, 4)
- 适用于数据初始化
- 按指定方式填充数组
-
find 和 findIndex
-
返回符合条件的第一个元素 返回符合条件的第一个元素的下标
-
let result = list.find(function(item){ return item.title === 'webpack' })
-
-
includes —-比indexOf更好用
- 用于判断数组中是否含有某个简单类型的数据, 有则返回 ture
let result = list.includes(2)
-
flat
- 展开数组, 默认只展开数组内的第一层数组, 可传入参数表示展开几层
let flatList2 = list2.flat(2)- concat ES5展开数组的方法
- 如果当前参是单独的元素则执行push操作, 如果是数组则执行合并
let flatList = [].concat(...list)
-
map
-
数据映射, 创建一个新数组, 其结果是该数组中的每个元素执行一次cb函数后的返回值
-
let video = json.map(function(item) { return { name: item.title, statusTxt: item.status ? '已上线' : '未上线' } }) // 其他的用法/写法见代码
-
-
reduce
-
数组操作高级妙用, 用法见代码注释
-
常见用法
-
统计符号出现次数
-
const result = letterList.split('').reduce(function(acc, cur){ acc[cur] ? acc[cur]++ : acc[cur] = 1 return acc }, {})
-
-
在不确定数组层数的情况下展开多层数组
-
const deepFlat = function(list) { return list.reduce(function(acc, cur) { return acc.concat(Array.isArray(cur) ? deepFlat(cur) : cur) }, []) } let flatList = deepFlat(list)
-
-
-
-
Array.from
- 将类数组对象转换为真正的数组对象
- 类数组对象: 有length属性且可遍历的对象
- 比如: ‘hello’
-
-
示例代码:
-
{ // 扩展运算符 ... 一次可以展开一层数组 // 复制数组 -- 浅拷贝 const list = [1, 2, 3, 4, 5] let list2 = [...list] console.log(list2) // ['a', 'b', 'c'] // 分割数组 const totalList = [1, 'a', 'b', 'c'] let [, ...strList] = totalList console.log(strList) // ['a', 'b', 'c'] // 给函数传递参数 function add(x, y) { return x + y } let addList = [1, 2] console.log(add(...addList)); // 3 } // 新的api { // fill ---填充数组--多用于数据初始化 const list = [1, 2, 3, 4, 5] // 如果按照如下操作, list和list2都将被 3 填满 // let list2 = list.fill(3) // console.log("list", list, "list2", list2) // 正确示范(先复制再填充) ---只有list2被 3 填满, list不会被影响 let list2 = [...list].fill(3) console.log("list", list, "list2", list2) // 如果我们希望得到这样一个数组[1, 3, 3, 3, 5] let list3 = [...list].fill(3, 1, 4) console.log(list3) // fill(3, 1, 4) 要填充的数字3 第一个被填充的数字的下标 最后一个被填充的数字在数组的位置(不是下标!) } { // find --返回符合条件的第一个元素 // findIndex -- 返回符合条件的第一个元素的下标 const list = [{title: 'es6'}, {title: 'webpack', id: 2}, {title: 'vue'}, {title: 'webpack', id: 3}] let result = list.find(function(item) { return item.title === 'webpack' }) let resultIndex = list.findIndex(function(item) { return item.title === 'webpack' }) console.log(result, resultIndex) // {title: 'webpack', id: 2} 1 } { // includes 和 indexOf // includes更加好用, 用于判断数组中是否含有某个简单类型的数据, 有则返回true const list = [1, 2, 3, 4, 5, 6] let result = list.includes(2) console.log('includes', result) // includes true } { // flat 展开数组的操作 const list = [1, 2, 3, ['2nd', 4, 5, 6]] // concat(ES5) --如果当前参数是单独的数据则执行push操作, 如果是数组则执行合并操作 let flatList = [].concat(...list) console.log(flatList) // [1, 2, 3, '2nd', 4, 5, 6] // 如果是两层以上的数组, 就需要结合遍历和上述方法来展开, 因此es6提供了flat! const list2 = [1, 2, 3, ['2nd', 4, 5, 6, ['3rd', 7, 8]]] // flat默认展开只展开数组内的第一层数组, 可传入参数表示展开几层 let flatList2 = list2.flat(2) console.log('flat', flatList2) // [1, 2, 3, '2nd', 4, 5, 6, '3rd', 7, 8] } { // map 数据映射 --对数据进行处理 const json = [{title: 'es6', status: 1}, {title: 'react', status: 0}, {title: 'webpack', status: 1}, {title: 'vue', status: 0}] let video = json.map(function(item) { return { name: item.title, statusTxt: item.status ? '已上线' : '未上线' } // 有时我们只需要修改其中的一项数据就好了, 可以这么写: (有坑) //! 这种写法会导致 json和video中的数据都被修改了 // item.status = item.status ? '已上线' : '未上线' // return item //* 好的写法是这样的: (先复制对象) // let obj = {} // Object.assign(obj, item) // obj.status = item.status ? '已上线' : '未上线' // return obj }) console.log('json', json) console.log('video', video) } { /** * reduce 对数组中的每个元素进行一次回调, 升序执行然后将回调值汇总成一个返回值 * @params cb(acc, currentValue, currentIndex, Array) * ? acc: 累计器累计回调的返回值, 它是上一次调用回调时返回的累计值, 如果是第一次调用且设置了initialValue则它是initialValue * ? currentValue: 数组中正在处理的元素 * ? currentIndex: 数组中正在处理的元素的索引 * ? Array: 源数组即调用reduce()的数组 * @params initialValue * ? 作为第一次调用cb函数时的第一个参数acc的值, 如果没有提供初始值initialValue,则将使用数组第一个元素的值作为acc的值 * !important 如果没有提供initialValue, reduce会从索引为 1 的位置开始执行cb方法(跳过了第一个索引),如果提供了initialValue则从索引 0 开始执行 * !important 如果数组为空且没有提供initialValue会抛出TypeError */ const letterList = 'abcadefrd' // 统计符号出现的次数, 现将字符串分割成数组,再将空对象设为cb初始值,根据对象是否有对应的元素执行不同计数方式 const result = letterList.split('').reduce(function(acc, cur){ acc[cur] ? acc[cur]++ : acc[cur] = 1 return acc }, {}) console.log(result) // {a: 2, b: 1, c: 1, d: 2, e: 1, …} //todo 在不确定数组层数的情况下展开多层数组 const list = [1, ['2nd', 2, 3, ['3rd', 4, 5]], ['2nd', 6, 7]] const deepFlat = function(list) { return list.reduce(function(acc, cur) { return acc.concat(Array.isArray(cur) ? deepFlat(cur) : cur) }, []) } let flatList = deepFlat(list) console.log('reduce-flat', flatList) // [1, '2nd', 2, 3, '3rd', 4, 5, '2nd', 6, 7] } { // Array.from 将类数组对象转换为真正的数组对象 // 类数组对象: 有length属性且可遍历 const str = 'hello' const strList = Array.from(str) console.log(strList) // ['h', 'e', 'l', 'l', 'o'] }
-
对象的新特性及新增方法
-
对象中扩展运算符的使用
简单类型的时候, 使用扩展运算符是没问题的, 但是如果扩展运算符展开对象以后, 还是一个对象的话, 我们复制的只是一个指针
- 复制对象
let videoObj = {...obj}
- 设置对象默认值
let obj2 = {...obj, name: 'Jack'}
- 合并对象
let obj3 = {...obj, ...initObj}
- 复制对象
-
属性初始化的简写
-
es5
let name = '小明' let obj = { name: name } -
es6
let name = '小明' let obj = { name }
-
-
对象方法的简写
-
es5
let obj = { sayHello: function() {} } -
es6
let obj = { sayHello() {} }
-
-
可计算的属性名
-
es5
let key = 'name' let obj = {} obj[key] = '小明' -
es6
let key = 'name' let obj = { [key]: '小红' }
-
-
新增方法
-
Object.is()
- 与
===类似不同处在于NaN是否等于NaNObject.is()返回true===返回false
- 与
-
Object.assign()
-
复制对象, 实质是浅拷贝, 适用于只有一层的对象, 对于多层对象仍然只能复制地址
-
const person = {name: '小米'} let person2 = {} Object.assign(person2, person)
-
-
Object.keys()
- 遍历对象里的属性名
-
Object.values()
- 遍历对象里的键值
-
Object.entries()
- 遍历对象里的键值对
-
案例: 利用Object.keys从json对象中拿取数据
const json = {name: 'Nick', video: 'es6', date: 2019} let obj = {} // 也是浅拷贝 ---如果对象内有第二层对象则会拷贝此对象的引用 for (const key of Object.keys(json)) { obj[key] = json[key] } console.log(obj) // {name: 'Nick', video: 'es6', date: 2019}
-
-
案例代码:
{ // 对象中扩展运算符的使用 // 复制对象 const obj = {name: 'Nick', video: 'es6'} const initObj = {color: 'red'} // 需要安装并在.babelrc中配置插件: npm install babel-plugin-transform-object-rest-spread let videoObj = {...obj} console.log(videoObj) // {name: 'Nick', video: 'es6'} // 设置对象默认值 --如果将...obj放在后面, obj里的name则会覆盖掉前面的name let obj2 = {...obj, name: 'Jack'} console.log(obj2) // {name: 'Jack', video: 'es6'} // 合并对象 let obj3 = {...obj, ...initObj} console.log(obj3) // {name: 'Nick', video: 'es6', color: 'red'} // 坑点 //! 简单类型的时候, 使用扩展运算符是没问题的, 但是如果扩展运算符展开对象以后, 还是一个对象的话, 我们复制的只是一个指针 } { // 属性初始化的简写和对象方法的简写 // 分别使用es5和es6创建对象 let name = '小明' let age = 18 let es5Obj = { name: name, age: age, sayHello: function() { console.log('this is es5Obj') } } let es6Obj = { name, age, sayHello() { console.log('this is es6Obj') } } console.log('es5', es5Obj) console.log('es6', es6Obj) es5Obj.sayHello() es6Obj.sayHello() } { // 可计算的属性名 let key = 'name' // es5写法 let es5Obj = {} es5Obj[key] = '小明' // es6写法 let es6Obj = { [key]: '小红' } console.log(es5Obj, es6Obj) // {name: '小明'} {name: '小红'} } { // 新增方法 // Object.is() 和 '===' 很相似, 用于判断二者是否相同,但在判断NaN是否等于NaN时,Object.is()返回true而'==='返回false let result = Object.is(NaN, NaN) console.log(result, NaN === NaN) // true false // Object.assign() 复制对象,实质也是浅拷贝, 适用于只有一层的对象, 对于多层对象显得有些无力(比如下面的例子) const person = {name: '小米', age: 18, info: {height: 180}} let person2 = {} Object.assign(person2, person) person.info.height = 160 console.log(person2) // 修改person.info.height, person2.info.height也会改变 } { // Object.keys() 遍历对象里的属性名 // Object.values() 遍历对象里的键值 // Object.entries() 遍历对象里的键值对 //? 场景: 取出json对象中的数据 const json = {name: 'Nick', video: 'es6', date: 2019} let obj = {} // 也是浅拷贝 ---如果对象内有第二层对象则会拷贝此对象的引用 for (const key of Object.keys(json)) { obj[key] = json[key] } console.log(obj) // {name: 'Nick', video: 'es6', date: 2019} }
Map与WeakMap结构的特点
-
Map
-
初始化
let map2 = new Map([['name', 'Nick'], ['sex', 'male']]) -
set
-
添加元素或更改现有元素
-
可链式调用
map2.set('name', 'lalala').set('hobbies', ['swimming', 'running'])
-
-
get
-
读取属性值
console.log('get', map2.get('hobbies')); -
视图读取不存在的属性值会返回undefined
-
-
delete
-
删除某一元素
map2.delete('hobbies');
-
-
clear
-
清空键值对
map2.clear()
-
-
遍历
- 三个遍历器生成函数
- keys()
- 返回键名
- values()
- 返回属性值
- entries()
- 返回键值对
- 如果使用for..of方法遍历,则默认遍历entries
- keys()
- 一个遍历方法
- forEach
- 三个遍历器生成函数
-
案例
// Map { // 添加元素 let map = new Map(); map.set([1,2,3], 'number') console.log(map); // {Array(3) => 'number'} let map2 = new Map([['name', 'Nick'], ['sex', 'male']]) console.log(map2); // {'name' => 'Nick', 'sex' => 'male'} // 返回map2里有多少对键值对 console.log(map2.size); // 2 // Map的set方法不仅可以添加元素也可以直接对现有元素进行更改 map2.set('name', 'Jack'); console.log('set', map2); // {'name' => 'Jack', 'sex' => 'male'} // set方法返回当前的Map对象,因此可以链式调用 map2.set('name', 'lalala').set('hobbies', ['swimming', 'running']) console.log('链式', map2); // {'name' => 'lalala', 'sex' => 'male', 'hobbies' => Array(2)} // 如何读取map2的值呢? console.log('get', map2.get('hobbies')); // ['swimming', 'running'] // 如果试图读取map2中不存在的值,则会返回undefined console.log('get2', map2.get('age')); // undefined // 用has方法来判断map2中是否存在 age console.log('has', map2.has('age')); // false // 用delete方法来删除map2中的hobbies map2.delete('hobbies'); console.log(map2); // {'name' => 'lalala', 'sex' => 'male'} // 用clear方法来清空键值对 map2.clear() console.log('clear', map2); // {size: 0} } { // 如何遍历Map? /** * 提供了三个遍历器生成函数: * keys() 返回键名 * values() 返回属性值 * entries() 返回键值对 * 提供了一个遍历方法: * forEach */ const map = new Map([ ['name', '小明'], ['age', 20] ]) // for...of 循环默认遍历entries for(let key of map.keys()) { console.log(key) } // name age } -
-
WeakMap
/** * 1. 只接受对象作为一个键名, 不接受其他类型的数据作为键名 * 2. 因为键名所指的对象不触发垃圾回收机制, 即WeakMap持有的是每个键对象的"弱引用",在没有其他引用存在时,垃圾回收能正确进行,WeakMap中用于映射的key只有在其没有被回收时才是有效的 * 3. 没有clear, 没有size, 无法遍历 * 当你要往对象上添加数据, 又不想干扰垃圾回收机制, 就可以使用WeakMap */-
案例
{ let weakmap = new WeakMap([ [{name: 'haha'}, 'jack'] ]) console.log(weakmap) // 一个实战例子 const ulObj = document.getElementById('test') let dom = new WeakMap([ [ulObj, 'lalala'] ]); // 这样一来, 如果dom元素被销毁了, WeakMap里的ulObj引用也会自行销毁 let obj = {name: 'jack'} // 这种写法必须手动设置array[0] = null 才能正确地垃圾回收 let array = [obj, 'person'] array[0] = null; }
-
Set与WeakSet
-
Set
-
初始化
let set = new Set([1,2,3,4,5]) -
添加元素
-
add
set.add(1) -
内部有去重规则
- 使用Object.is()去重, 即同值相等
- 1与’1’是不同的
-
-
判断属性是否存在 has
-
如果Set中存储对象或数组这种引用类型的数据时,在去重判断时判断的是对象所指向的内存地址
let set = new Set() set.add({fruit: 'apple'}) console.log('has', set.has({fruit: 'apple'})) // false // 如果避免上述情况? 只要让对象地址一致即可,先把对象存起来,再add let set2 = new Set() const item = {fruit: 'apple'} set2.add(item) console.log('has2', set2.has(item)) // true
-
-
Set 遍历
-
三个遍历器方式
-
keys()
-
values()
-
entries()
-
注意: set里key和value的值是相等的, for…of默认遍历values
const set = new Set([1, 2, 3, 4, 5]) for(const item of set.entries()) { console.log(item) }
-
-
-
使用场景—-数组去重
const array = [1,2,3,4,4,5,1,2,9,6,4,7,] let unique = new Set(array) // Array.from将类数组转换成数组 let uniqueArray = Array.from(unique) console.log(uniqueArray) // [1, 2, 3, 4, 5, 9, 6, 7] -
完整案例代码
{ // Set // 初始化时传入值 let set = new Set([1,2,3,4,5]) console.log(set) // {1, 2, 3, 4, 5} // 添加元素 set.add(1) console.log(set) // {1, 2, 3, 4, 5} // 内部使用Object.is()去重, 即同值相等 // 如果添加 '1' 就能添加成功 set.add('1') console.log(set) // {1, 2, 3, 4, 5, '1'} } { //判断属性是否存在 has, 删除属性 delete, 清空 clear // 如果Set中存储对象或数组这种引用类型的数据时, // 在去重判断时判断的是对象所指向的内存地址 let set = new Set() set.add({fruit: 'apple'}) console.log('has', set.has({fruit: 'apple'})) // false // 如果避免上述情况? 只要让对象地址一致即可,先把对象存起来,再add let set2 = new Set() const item = {fruit: 'apple'} set2.add(item) console.log('has2', set2.has(item)) // true } { // Set遍历 // keys(), values(), entries() // 需注意的是 set里key和value的值相等, for...of默认遍历values const set = new Set([1, 2, 3, 4, 5]) for(const item of set.entries()) { console.log(item) } // [1, 1] // [2, 2] // [3, 3] // [4, 4] // [5, 5] } { // 使用场景--数组去重 const array = [1,2,3,4,4,5,1,2,9,6,4,7,] let unique = new Set(array) // Array.from将类数组转换成数组 let uniqueArray = Array.from(unique) console.log(uniqueArray) // [1, 2, 3, 4, 5, 9, 6, 7] }
-
-
WeakSet
-
并不常用
/** * WeakSet 和 Set的区别 * 1. 元素只能是对象, 对象也是弱引用 * 2. 无法遍历, 没有size, 也没有clear * 3. 不干扰垃圾回收 */ let obj = {} let weakset = new WeakSet() weakset.add(obj) console.log(weakset)
-
Map、Set、Array、Object间的区别
-
增删改查
{ let array = [] let obj = {} let map = new Map() let set = new Set() const gooditem = {fruit: 'apple'} // 增加 array.push(gooditem) obj['fruit'] = 'apple' //或者 obj.fruit = 'apple' map.set('fruit', 'apple') set.add(gooditem) console.log('add', array, obj, map, set) // 查询 const resultArray = array.includes(gooditem) const resultObj = 'fruit' in obj const resultMap = map.has('fruit') const resultSet = set.has(gooditem) console.log('search', resultArray, resultObj, resultMap, resultSet) // 修改 array.forEach(function(item) { item.fruit = item.fruit ? 'orange' : '' }) obj['fruit'] = 'orange' map.set('fruit', 'orange') set.forEach(function(item) { item.fruit = item.fruit ? 'orange' : '' }) console.log('update', array, obj, map, set) // 删除 const index = array.findIndex(function(item) { return item.fruit }) // 找到要删除元素的下标 array.splice(index, 1) // 根据下标切割 delete obj.fruit map.delete('fruit') set.delete(gooditem) console.log('delete', array, obj, map, set) /** * 总结: * map的所有操作都很简单 * set可以去重 */ } -
类型转换
{ // 类型转换 map和对象间的转换 let obj = { name: 'Nick', hobbies: 'swimming' } let map = new Map(Object.entries(obj)) // Object.entries方法返回对象的键值对数组 console.log('map', map) let obj2 = Object.fromEntries(map) // Object.fromEntries方法将键值对数组转换为对象 console.log('obj2', obj2) // 同样, keys()和values()也有上述操作 // 数组和set间的转换 let array = [1, 2, 3, 4, 5] let set = new Set(array) console.log('set', set) let array2 = Array.from(set) console.log('array', array2) }
代理Proxy和反射Reflect
-
Proxy
-
概述
正如Proxy的英译”代理”所示, Proxy是ES6位了操作对象引入的API. 它不直接作用在对象上, 而是作为一种媒介,如果需要操作对象的话, 需要经过这个媒介的同意.
-
使用方式
/** * @params * target: 用Proxy包装的目标对象 * handler: 一个对象, 对代理对象进行拦截操作的函数, 如set, get等 */ let accountProxy = new Proxy(account, handler) -
常用方法
- get : 拦截读取操作
- set : 拦截设置操作
- has : 拦截 key in obj
- delete : 拦截删除操作
- ownKeys : 拦截 Object.keys()
{ // Proxy, 代理的就是对象的一些操作 let account = { id: 9923, name: 'admin', _private: 'test', phone: '13812345678', create_time: '2019' } /** * @params * target: 用Proxy包装的目标对象 * handler: 一个对象, 对代理对象进行拦截操作的函数, 如set, get等 */ let accountProxy = new Proxy(account, { // 拦截(代理)读取和设置的操作 get: function(target, key) { // target是目标对象, key是键名 switch (key) { case 'phone': return target[key].substring(0, 3) + '****' + target[key].substring(7) case 'create_time': return target[key].replace('2019', 2020) default: return target[key] } }, set: function(target, key, value) { // value是设置的值 if(key === 'id') { return target[key] } else { return target[key] = value } }, // 拦截 key in obj has: function(target, key) { if(key in target) { console.log(`${key}: `, target[key]) return true } else { console.log('并无此属性') return false } }, // 拦截delete deleteProperty: function(target, key) { if(key.indexOf('_') === 0) { // 以 _ 开头的属性表示是私有属性,不允许用户删除 console.warn('私有属性不能被删除') return false } else { delete target[key] return true } }, // 拦截 Object.keys() ownKeys(target) { // 过滤掉id属性和私有属性 return Object.keys(target).filter(function(item) { return item !== 'id' && item.indexOf('_') !== 0 }) } }) console.log('拦截读取', accountProxy.phone, accountProxy.create_time) // 拦截读取 138****5678 2020 accountProxy.id = 1234 accountProxy.name = 'guest' console.log('拦截设置', accountProxy.id, accountProxy.name) // 拦截设置 9923 guest console.log('拦截in', 'name' in accountProxy) // name: guest // 拦截in true console.log('拦截删除', delete accountProxy['_private']) // 给出警告---并提醒私有属性不能被删除 // 删除 false // 从dist目录引入本文件,将采用严格模式,在严格模式下会报错而导致看不到 删除 false 的输出 // 将从dist目录引入改为从src目录引入即可 console.log('拦截Object.keys()', Object.keys(accountProxy)) // 拦截Object.keys() ['name', 'phone', 'create_time'] }
-
-
Reflect
-
概述
与Proxy相同, ES6引入Reflect也是用来操作对象的, 它将对象里一下明显属于语音内部的方法移植到Reflect对象上, 它对某些方法的返回结果进行了修改, 使其更合理, 并且使用函数的方式实现了Object的命令式操作
-
使用方法
{ // Reflect --更好的操作对象的方法 // 推荐使用Reflect来操作对象,而不是直接操作对象 let obj = { name: 'Nick', age: '32', sex: 'male', hobbies: 'swimming' } console.log(Reflect.get(obj, 'name')) // Nick Reflect.ownKeys(obj) // 获取对象自身的属性 Reflect.set(obj, 'name', 'Jack') // 为对象添加新属性 console.log(obj.name) // Jack // 如果要判断对象中是否存在name属性 // 对象的写法 'name' in obj // Reflect的写法 Reflect.has(obj, 'name') } // 在操作对象时优先考虑是否可以用Reflect呢? 查阅MDN文档
-
使用Proxy和Reflect实现简单的双向数据绑定
-
index.html
<body> <script src="./polyfill.js"></script> <!-- <script src="dist/chapter3-1.js"></script> --> <h1>使用Proxy和Reflect实现双向数据绑定</h1> <input type="text" id="input"> <h2>您输入的是: <i id="txt"></i></h2> <!-- script标签要放在上面内容之下 --> <script src="src/4-8-使用Proxy和Reflect实现双向数据绑定.js"></script> </body> -
获取dom对象
const inputObj = document.getElementById('input') const txtObj = document.getElementById('txt') -
设置代理对象
const obj = {} -
配置代理选项
const handler = { get: function(target, key) { return Reflect.get(target, key) }, set: function(target, key, value) { if(key === 'text') { // Start 实现 i 标签内容同步到input inputObj.value = inputObj.value === value ? inputObj.value : value // END 实现 i 标签内容同步到input txtObj.innerHTML = value } return Reflect.set(target, key, value) } } -
添加事件
let objProxy = new Proxy(obj, handler) // 给input添加键盘键入事件 inputObj.addEventListener('keyup', function(e) { objProxy.text = e.target.value console.log(objProxy) }) -
完整代码:
{ // 获取dom元素 const inputObj = document.getElementById('input') const txtObj = document.getElementById('txt') // 初始化代理对象 const obj = {} // 配置代理选项 const handler = { get: function(target, key) { return Reflect.get(target, key) }, set: function(target, key, value) { if(key === 'text') { // Start 实现 i 标签内容同步到input inputObj.value = inputObj.value === value ? inputObj.value : value // END 实现 i 标签内容同步到input txtObj.innerHTML = value } return Reflect.set(target, key, value) } } let objProxy = new Proxy(obj, handler) // 给input添加键盘键入事件 inputObj.addEventListener('keyup', function(e) { objProxy.text = e.target.value console.log(objProxy) }) // 现在当你在输入框中输入内容时会自动同步到 i 标签 // 但是 i 标签内容的修改还无法同步到input // 修改 i 标签内容,发现不会同步到input objProxy.text = '1234' // 要真正实现双向数据绑定还需要在拦截set中对输入框进行处理(见set里的代码注释) }
函数的扩展
-
函数参数可设置默认值
{ // 为函数设置默认参数 function es5Print(x, y) { y = y || 'world' console.log('es5', x+y) } es5Print('hello', '') // 期望输出 hello // 实际输出 helloworld // 因为js的||运算符判断规则是非空 function es6Print(x, y = 'world') { console.log('es6', x + y) } es6Print('hello', '') // 输出: hello } -
rest参数
{ // rest // 常用与不定参数 function add(...rest) { let sum = 0 console.log(rest) // 可以看到, rest是一个数组 for(let value of rest) { sum += value } console.log(sum) } add(1,2,3,4,5) // 15 // 在es5中, 不定参数依靠与arguments,而arguments并不是数组,它类似于Array,但除了length属性和索引元素外,没有任何Array的属性 // 要想使用数组方法需要更改其this指向,然后将它转换为真正的Array, 例如 // var args = Array.prototype.slice.call(arguments); // var args = [].slice.call(arguments) // 也有es6的转换方法 // const args = Array.from(arguments) // const args = [...arguments] } -
扩展运算符
{ // 扩展运算符 console.log(...[1, 2, 3, 4, 5]) //输出 1 2 3 4 5 而不是数组 } -
尾调用
{ // 尾调用 function step2(x) { console.log('尾调用', x) } function step1(x) { return step2(x) } // 当一个函数的最后一步是另一个函数时(return step2(x) + 1 不能算是尾调用), 就被称为尾调用 // 尾调用可以提供性能, 可以用来改善递归 }
箭头函数
-
见面一个坑
function fn(x) { let x //报错 var x //正常 } -
声明函数
{ // 声明函数 const arrow = (x) => { console.log('箭头函数') } arrow() } -
超级简写
{ // 超级简写 const arrow = x => x * 2 const result = arrow(4) console.log(result) // 8 } -
不绑定this
{ // 不绑定this const fruit = { name: 'apple', price: 18, num: 3, sum() { window.setTimeout(() => { // 必须使用箭头函数才能取到值,使用function则不能 // vscode鼠标悬浮在this上可以看到this的指向内容 console.log(this.num * this.price) }, 1000) } } fruit.sum() // 54 } -
总结
/** * js中什么时候使用箭头函数? * Object.method()调用的话就用普通函数进行声明, 其他情况用箭头函数 */
ES6中类的概念
-
ES5如何实现类?
{ // es5如何实现类? // 通过构造函数实现类的功能 function Person(name, age) { // 函数名首字母大写, 参数用this指向 this.name = name this.age = age } // 方法写在构造函数的原型链上 Person.prototype.sayHello = function() { console.log(`大家好,我叫${this.name}, 我今年${this.age}岁了`) } const p = new Person('小明', 17) console.log(p) } -
ES6改写上述类
{ // ES6改造ES5实现类的方法 class Person { constructor(name, age) { this.name = name this.age = age } sayHello() { console.log(`大家好,我叫${this.name}, 我今年${this.age}岁了`) } } const p = new Person('小红', 17) console.log('class', p) // 这里的输出可以看到, 类的输出结果和构造函数的输出结果是一样的 console.log(typeof Person) // function // 这里的输出我们可以看到类的本质是function // 也就是说, class可以看做是构造函数的语法糖,它更直观 } -
类的继承
{ // 类的继承 class Parent { constructor(name = 'Nick') { this.name = name } } class Child extends Parent { } console.log('继承', new Child()) // {name: 'Nick'} // 子类如何改写父类的属性? class Child1 extends Parent { constructor(name = 'Jack') { // super要放在构造函数最前面 // 在super中写明子类要改写的属性 super(name) this.name = name } } console.log('继承2', new Child()) // {name: 'Nick'} } -
get 和 set
{ // get 和 set class Person { constructor(name = 'Nick') { this.name = name } get fullName() { return this.name + '\xa0' + 'Liu' } set fullName(value) { this.name = value } } const p = new Person() console.log('get', p.fullName) // get Nick Liu p.fullName = 'Jack' console.log('set', p.name) // set Jack } -
定义静态方法
{ // 如何为类定义静态方法(只能在类上使用,不能在其子集使用) class Person { constructor(name = 'Nick') { this.name = name } static sayHello(obj) { console.log('my name is ' + obj.name) } } const p = new Person('小花') Person.sayHello(p) // my name is 小花 } -
定义静态属性
{ // 如何书写静态属性? // 安装 npm install babel-preset-es2017 ---用于babel转义 class Person { static prop = 'test' constructor(name = 'Nick') { this.name = name } static sayHello(obj) { console.log('my name is ' + obj.name) } } // es2017前, 静态属性需要在外面挂载: // Person.prop = 'test' console.log(Person.prop) // test } -
更多详细内容见 JavaScript类
JS中的模块化开发(import 和 export)
-
背景
ES6之前没有类的概念, 也就没有模块化这一说法. 理想情况下, 开发者应该只注重核心业务的开发, 对于其他有一定通用性的业务可以直接引入别人的代码, 也就是模块. 多人开发, 本应如此.
-
提出的方案
- CommonJS
- 它是作为Node中模块化规范以及原生模块面世的, 但它用于客户端会导致模块同步加载,影响体验.
- AMD和RequireJs
- AMD的全称翻译过来就是”异步模块定义”. 它采用异步方式加载模块, 模块的加载不影响它后面语句的运行. 所有依赖这个模块的语句, 都定义在一个回调函数中, 等到所有依赖加载完成之后(前置依赖), 这个回调函数才会运行.
- CommonJS
-
Import 和 export
-
第一种写法
-
module.js
// 导出变量 export let a = 3; // 导出方法(函数) export function sayHello() { console.log('hello') } // 导出类 export class test { say() { console.log('test') } } -
main.js
// 引用 -- 需要引用哪些就写哪些 // import { a, sayHello, test } from './5-4-2-js模块化开发(import和export).js' // console.log(a, sayHello, test) // 引入模块导出的全部内容,并赋予别名 import * as test from './5-4-2-js模块化开发(import和export).js' console.log(test) // test是一个module, 里面存储有模块导出的全部内容
-
-
更好更安全的写法
-
module.js
// 更好更安全的写法---先声明后导出 let a = 3 function sayHello() { console.log('default', 'hello') } export default { a, sayHello } -
main.js
// 引入通过export default导出的模块 --可以随意取名,通过 . 运算符访问导出的内容 import test from './5-4-2-js模块化开发(import和export).js' console.log(test.a) // 3
-
-
异步编程及JS的异步实现
-
同步
- “调用者”要主动等待代码的执行结果, 得到返回结果后, 程序才会进行运行
-
异步
- “调用”发出的时候, 就直接返回了, 对应的结果会通过状态、通知来告诉”调用者”或通过回调函数处理这个调用. 异步调用发出后, 不会阻塞后面的代码.
-
js为何引入异步
- js是单线程的
- 同步代码会阻塞后面的代码
- 异步不会阻塞程序的运行
-
js中异步的实现
-
回调函数
// 一层的回调函数 $.get('http://api.xxx.com/xxx', callback) /** * 请求后台类型列表, 根据类型名称获取要请求的列表id, 在根据id获取列表详细内容 * 这种方式难以维护, 容易形成回调地域 **/ $.ajax({ url: 'type', data: 1, success: function(a) { $.ajax({ url: 'list', data: a, success: function(b) { $.ajax({ url: 'content', data: b, success: function(c) { console.log(c) } }) } }) } }) -
setInterval和setTimeout
-
Promise
-
Generator
-
async
-
Promise
-
解决”回调地域”
- Promise
- Promise是一个对象, 也可以说是一种编程思想, 应用的场景就是”当xxx执行完毕的时候, then执行xxx动作.” Promise里不仅可以存放这异步的代码, 也可以放同步的代码.
- Promise的使用
- 封装一个Promise
- 捕获异常
- Promise.all方法
- 当所有的的Promsie都执行完毕后,才会执行其then方法
- Promise.race
- 只要有一个Promise状态改变, 就会执行其then方法
- Promise
-
{ // 回调地域 // * 一层回调 // 模拟ajax请求, 接收一个回调函数作为参数 function ajax(cb) { setTimeout(() => { cb && cb(); }, 1000); } // 执行ajax函数, 并传入一个回调函数 ajax(() => { console.log("任务1"); }); // 输出: 任务1 // * 两层回调 function ajax2(cb) { setTimeout(() => { cb && cb(() => { console.log("任务2"); }); }, 1000); } ajax2((cb) => { console.log("任务1"); cb && cb(); }); // 输出: 任务1 任务2 } { // Promise改造回调函数 // ? 当执行到resolve后,才会执行then() //* 一次Promise function ajax() { // resolve是程序下一步要执行的内容, reject是程序出错时执行的内容 return new Promise((resolve, reject) => { setTimeout(() => resolve(), 1000); }); } ajax().then(() => { console.log("任务1"); }); //* 两次Promise function ajax2() { // resolve是程序下一步要执行的内容, reject是程序出错时执行的内容 return new Promise((resolve, reject) => { setTimeout(() => resolve(), 1000); }); } ajax2() .then(() => { console.log("任务1"); return new Promise((resolve, reject) => { setTimeout(() => resolve(), 1000); }); }) .then(() => { console.log("任务2"); }); } { // 使用catch方法捕获错误 function judgeNumber(num) { // 判断输入的数据是否是number return new Promise((resolve, reject) => { if(typeof (num) === 'number') { resolve(num) } else { const err = new Error('请输入数字') reject(err) } }) } judgeNumber('2') .then(num => console.log(num)) .catch(err => console.log(err)) // 抛出错误 } { //* Promise.all // 只有当所有的Promise状态都改变了才会执行then方法 const imgUrl1 = 'http://...' const imgUrl2 = 'http://...' const imgUrl3 = 'http://...' function getImage(url) { return new Promise((resolve, reject) => { const img = document.createElement('img') img.src = url // onload方法可以判断图片已加载完成 img.onload = () => resolve(img) // onerror方法可以捕获异常 img.onerror = (err) => reject(err) }) } function showImage(imgs) { imgs.forEach(item => { document.body.appendChild(item) }) } Promise.all([getImage(imgUrl1), getImage(imgUrl2), getImage(imgUrl3)]).then(showImage) //* Promise.race // 只要有一个Promise状态发生改变就执行then方法 function showFirstImage(img) { document.body.appendChild(img) } Promise.race([getImage(imgUrl1), getImage(imgUrl2), getImage(imgUrl3)]).then(showFirstImage) }
Iterator和for…of
-
Iterator以及Iterator与for…of的关系
- Iterator
- Iterator(遍历器)是一种接口, 目的是为了给不同的数据结构提供统一的循环方式, 任何数据结构如果部署了Iterator接口, 就能够实现遍历的操作.
- Iterator的作用
- 为不同的数据结构, 提供一个统一的、简便的访问接口
- 将数据成员按照一定的顺序输出
- 提供给ES6中的for…of的这个循环语句进行使用
- 什么结构具备原生的Iterator接口
- Array
- String
- Set
- Map
- 函数的argument对象
- 默认的Iterator接口
- Symbol.iterator
- 本质
- 是一个函数, 就是当前的数据集合默认的遍历器生成函数, 执行这个函数, 就会返回一个遍历器
- 返回值
- 返回一个遍历器对象. 这个对象里的显著特点就是有一个next()方法. 每次调用next都会返回一个描述当前成员的信息对象, 具有value和done两个属性.
- 本质
- Symbol.iterator
- Iterator
-
{ // 什么是Iterator(遍历器) // 取出数据集合里的数据, 通过遍历, Iterator提供了一个统一的接口, // 通过for...of调用这个接口, 输出数据集合中的数据 // 任何数据结构如果部署了iterator接口,就能实现遍历的操作 const arr = [1, 2, 3] const fn = arr[Symbol.iterator]() console.log(fn.next()) // {value: 1, done: false} console.log(fn.next()) // {value: 2, done: false} console.log(fn.next()) // {value: 3, done: false} console.log(fn.next()) // {value: undefined, done: true} } { // 应该场景 // 具备原生的iterator接口的数据结构: Array String Set Map 函数的argument对象 // 原生的iterator并不支持Object, 但可以自定义 const obj = { color: 'red', price: 18, size: 'small', // 自定义iterator后,即可通过for...of遍历输出 [Symbol.iterator]() { let index = 0 const values = Object.values(this) return { next() { if(index < values.length) { // 只可以有 value和done, 否则报错 return { value: values[index ++], done: false } } else { return { done: true } } } } } } for(const value of obj) { console.log(value) } // red 18 small }
更直观的异步编程写法__Generator
-
安装支持插件
-
npm install --save babel-polyfill -
在项目的node_modules目录下找到:

-
将polyfill.js复制到根目录下

-
在index.html下引入

-
也可以使用webpack来进行引用, 之后再了解
-
-
Generator
- 用于生成一个迭代器Iterator
- next()
- yield
- 应用场景 (用法丰富, 仔细阅读如下代码)
{
// 本质仍然是Iterator
const say = function* () {
yield 'a'
yield 'b'
yield 'c'
}
const fn = say();
console.log(fn.next()) // {value: 'a', done: false}
// next始终是用来启动yield生成器的, 所以, next始终要比yield多一个
// 最后一个的done为true
}
{
// 更简洁地为对象定义迭代器
let obj = {
a: 1,
b: 2,
c: 3
}
obj[Symbol.iterator] = function* () {
for(const key of Object.keys(obj)) {
yield obj[key]
}
}
for(const value of obj) {
console.log(value)
}
// 1 2 3
}
{
// 可用来定义状态机
// 状态机: 任何时候都只有一定数量种状态
const state = function* () {
while(1) {
yield 'success'
yield 'fail'
yield 'pending'
}
}
const stateData = state()
console.log(stateData.next()) // {value: 'success', done: false}
console.log(stateData.next()) // {value: 'fail', done: false}
console.log(stateData.next()) // {value: 'pending', done: false}
console.log(stateData.next()) // {value: 'success', done: false}
console.log(stateData.next()) // {value: 'fail', done: false}
}
{
// 可用来定义长轮询
// 以下代码会一直查询用户付款状态, 直到付款code为0
function fn1() {
return new Promise(resolve => {
// 模拟查询操作
setTimeout( () => {
console.log('查询中')
resolve({code: 0})
},1000)
})
}
const getStatus = function* () {
yield fn1()
}
function autoGetStatus() {
const gen = getStatus()
const status = gen.next()
// 还记得吗? iterator的next()返回中有value和done两个值, 这里的status.value就是fn1返回的Promise
status.value.then(res => {
if(res.code === 0) {
console.log('用户付款成功')
} else {
console.log('暂未付款')
// 继续查询
setTimeout( () => autoGetStatus(), 500)
}
})
}
autoGetStatus()
}
{
//todo 如何用同步的写法来写异步编程
// generator本身不是用来异步编程, 但大神们把他的特性用在了异步编程里, nice!
const ajax = function* () {
console.log('start')
yield function(cb) {
setTimeout(() => {
console.log('异步任务结束')
cb && cb()
}, 1000)
}
console.log('end')
}
// const runAjax = ajax()
// console.log(runAjax.next())
// console.log(runAjax.next())
const runAjax = ajax() // 生成一个generator实例
const first = runAjax.next() // 将generator状态(next)保存起来 ---当前输出 'start'
first.value(() => runAjax.next()) // 为next.value传入回调函数, 这里的回调函数就是通过generator执行next
//输出: start 异步任务结束 end
}
更优雅的异步编程__async
-
async
- async是异步的简写, 用于声明一个函数是异步函数
-
await到底在等什么?
- await等待的是一个表达式, 这个表达式的计算结果是Promise对象或其他值(换言之, 就是没有特殊限定)
-
{ // async // generator的语法糖 async function fn1() { // await 后可以是同步任务也可以是异步任务, 无任何限制 await console.log(1); await console.log(2); await console.log(3) } fn1() } { // 优雅的异步 function fn1() { return new Promise(resolve => { setTimeout(() => { console.log('任务1') resolve() }, 1000) }) } function fn2() { return new Promise(resolve => { setTimeout(() => { console.log('任务2') resolve() }, 1000) }) } function fn3() { return new Promise(resolve => { setTimeout(() => { console.log('任务3') resolve() }, 1000) }) } async function init(fn1, fn2, fn3) { // 当await见到resolve()后才会执行喜爱异步 await fn1() await fn2() await fn3() } init(fn1, fn2, fn3) }
总结
- 常用知识
- 解构赋值
- 对象的解构
- 数组的解构
- set、map 方法
- set用于数组排序, map用于数据映射(后端返回数据的字段通常与前端的字段不同,可以使用map来自定义字段)
- Object.assign()
- 对对象进行浅拷贝, 当对象只有一层时非常好用
- for of 循环
- es6提供的统一的遍历接口
- Object的for of 通过[Symbol.iterator]自定义返回value和down来实现
- Promise函数
- 结合async函数, 异步编程同步化
- 解构赋值
-
遇到的一些问题
-
babel无法转椅一些api
babel默认只转译新标准引入的语法, 比如ES6的箭头函数、不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象, 以及一些定义在全局对象上的方法(比如Object.assign)都不会转码.
解决方法:
使用babel-polyfill, 为当前环境提供一个扩展包
npm install --save babel-polyfill // 找到node_modules目录下的babel-polyfill下dist目录下的polyfill.js文件 // 用script标签引用 -
无法引入模块
- 在script标签下添加
type="module"属性
- 在script标签下添加
-