Open lostvita opened 5 years ago
本文是一篇应用型文章,根据实际的项目场景总结ES6的各种使用姿势。文章不会对ES6语法中的特性或原理做过多的说明,重点从实际的应用场景去理解ES6的新语法、新特性。
为解决(或规范)JS中块级作用域的问题,ES6新增了let和const两种声明变量的方式,与var的区别在于:
let
const
var
日常我们项目开发中,会在IDE保存文件或提交Git时会借助Eslint之类的工具对代码规范进行校验,其中一条常见的校验提示是:声明的变量xxx没有被再赋值,建议使用const。 因此,一般我们用ES6声明变量时,可以遵循下面的原则:
Eslint
声明的变量xxx没有被再赋值,建议使用const
function submit(){ let loading = true const postData = this.getParamData() // ... loading = false }
解构赋值是从字符串、数组或对象中提取值,对变量进行赋值的过程。可以应用在变量定义、函数参数等场景中。
// 解构数组 let [name, age, sex='male'] = ['安歌','18'] // output -> name: '安歌', age: 18, sex='male' // 对象解构 const ange = {name: '安歌', sex: 'male'} let { name, age=18, sex } = ange // output -> name: '安歌', age: 18, sex: 'male' // 字符串解构 const hi = 'hello' let [a, b, c, d, e] = hi let { length } = hi // output -> a: 'h', b: 'e' ... length: 5
Set数据结构要求其元素不可重复,这种唯一性特性在项目开发中能带来很大便利;Map数据结构是一种键值对集合,它对键的数据类型提供了更广泛的支持,同时有一系列极好用的API。
Set
Map
这里我们以数据列表为场景:对销售订单进行对账,要求限制只能对同一产品进行对账。
// 根据Set内元素不重复的特性,判断申请对账按钮是否可用 function canSubmit() { const checkedListData = getCheckedList() // 获取选中的数据项列表 const productNos = new Set( // 取出商品ID列表 checkedListData.map(each => each.product_no) ) return productNos.size === 1 }
/* * 使用Map数据结构存储勾选数据列表 * 假设列表数据项 item={ id, order_no, product_no, product_name, isChecked } */ const checkedData = new Map() // 选中操作 checkedData.set(item.id, item) // 取消选中 checkedData.delete(item.id) // 清空 checkedData.clear() // 取出选中项ID集合 const recordIds = checkedData.keys() // 如果需要对选中的数据进行二次筛选,可以取出数据集合 const data = checkedData.values() // 从其他页码(比如第2页)返回到已访问过的页面(比如第一页),一般需要还原用户的选中状态 checkedData.has(item.id) && (item.isChecked = true)
ES6的模板字符串允许我们在字符串中嵌入变量,或者定义一个多行字符串,有更好的可读性。
const orderNo = 'ON90509000001' const msg = `订单${orderNo}对账失败!` // output -> 订单ON90509000001对账失败!
箭头函数,让你的表达更简洁。它有以下几个特点:
this
bind
call
apply
new
arguments
rest
yield
Generator
const obj = { name: '安歌', hello: function () { setTimeout(() => console.log(this.name), 1000) } } obj.hello() // output -> '安歌' obj.hello.call({name: 'Ange'}) // output -> 'Ange'
function add(x, y, fixed=2) { const result = (+x) + (+y) return result.toFixed(fixed) }
add(1,2) // output -> '3.00'
### 3. rest参数 ```javascript function add(...args) { let sum = 0 for (var val of args) { sum += val } return sum } add(2, 5, 3) // 10
function submit() { this.$post('/login').then( ({code, data}) => {} ) }
中后台系统中权限管理是必备的功能,从控制页面的访问权限到某个功能的操作权限。这里以权限管理为应用场景,看如何用ES6优雅地进行条件判断。
function hasPer(route) { const { permissions=[] } = userInfo // 获取用户权限列表 const routePer = route.meta.per // 预先为每个路由配置的访问权限 return permissions.includes(routePer) }
function checkPer(per) { const { permissions=[] } = userInfo // 获取用户权限列表 return Array.isArray(per) ? // per是个列表,permissions需要包含per里面要求的所有权限 per.every(each => permissions.includes(each)) : ( // per是个正则,permissions需要存在某个权限通过per的正则验证 per instanceof RegExp ? permissions.some(each => per.test(each)) : // per是个字符串, permissions.includes(per) ) }
假定我们有一个订单数据列表:
const orderList = [ {oid: 'ON0001', pid: 'PN0001', pname: '蘸酱短袖', clinet: '杨过', total: 100, date: '2019-07-10'}, {oid: 'ON0002', pid: 'PN0002', pname: '椒盐短袖', clinet: '小龙女', total: 122, date: '2019-07-10'}, {oid: 'ON0003', pid: 'PN0001', pname: '蘸酱短袖', clinet: '郭襄', total: 100, date: '2019-07-14'}, {oid: 'ON0004', pid: 'PN0003', pname: '炭炙短袖', clinet: '郭襄', total: 137 , date: '2019-07-18'}, {oid: 'ON0005', pid: 'PN0001', pname: '蘸酱短袖', clinet: '小龙女', total: 100, date: '2019-07-20'} ]
// 取出列表中的所有订单号 const oIds = orderList.map(each => each.oid)
// 给列表每个订单加一个isCP属性,默认值为false orderList.forEach(each => each.isCP = false)
如果购买顾客是杨过和小龙女,将其标识为一对CP(换句话说,循环列表过程,遇到郭襄就执行continue命令):
continue
orderList.forEach(each => ['杨过', '小龙女'].includes(each.clinet) && (each.isCP = true))
商家搞优惠活动,按序给每个订单总价核减5元(核减的5元返还给顾客),直到遇到第一个奇数总价的订单(换句话说,虚幻列表过程,遇到第一个奇数总价就执行break命令,终止循环)。
break
orderList.some(each => each.total % 2 ? true : each.total -= 5) // 可以验证,当循环到{oid: 'ON0004', pid: 'PN0003', pname: '炭炙短袖', clinet: '郭襄', total: 137 , date: '2019-07-18'},这条数据之后,因为结果返回true,循环将结束,即后面的数据不会再被循环。
假定场景:给每个订单的对账情况加上状态样式(success、warning、danger),有如下数据:
success
warning
danger
// 状态码说明: 100->对账成功、101->对账中、102->审核中、103->对账失败、104->取消对账 // 规定有如下样式映射表: const style2StatusMap = { success: [100], warning: [101,102], danger: [103,104] }
实现功能:将样式映射表转化为状态映射表,形如:
const status2styleMap = { 100: 'success', 101: 'warning', 102: 'warning', 103: 'danger', 104: 'danger' }
使用ES6的entries和reduce等API实现:
entries
reduce
// 实现toPairs函数取出Map的键值对,函数返回形如[[key1,val1]...]的数组 const toPairs = (obj) => Object.entries(obj) // 实现head/last函数取出列表头尾元素 const head = list => list[0] const last = list => list.reverse()[0] // 将对象转换为数组 const pairs = toPairs(style2StatusMap) // 再将数组转化为对象 const status2styleMap = pairs.reduce((acc, curr) => { const style = head(curr) const status = last(curr) return status.reduce((accer, each) => (accer[each] = style,accer), acc) }, {}) // output -> {100: "success", 101: "warning", 102: "warning", 103: "danger", 104: "danger"}
有时候我们需要在一个数据对象中取出部分的数据项,经过一定的组装再传递给某个组件或发送到服务器。
假定场景:对一个对账单进行修改,有一个form变量存储表单数据,router上还带有一些参数queryData。实现将修改的数据重新组装后发给服务器,但存在部分数据不需要发送。
form
router
queryData
// 基础数据 let form = { name: '账单名', no: '对账单编号', oNo: '订单编号', pNo: '商品编号', pName: '商品名'@, pNum: '商品数量', applicant: 'ange@gmail.com(安歌)', ... } const query= { id: '对账单ID'}
// 先覆写applicant参数 Object.assign(form, { applicant: form.applicant.split('(')[0] // ...其他可能需要覆写或追加的参数 }
// 采集出需要的数据项 const exclude = ['pName', 'pPrice'] const formData = Object.keys(form) .filter(each => !exclude.includes(each)) .reduce((acc, key) => (acc[key] = form[key], acc), {}
// 使用扩展运算符组合form和query的参数 const postData = { ...formData, ...query }
ES6提供了Promise对象和async函数用于处理异步操作,用于http请求的axios库就是基于Promise实现的。利用Promise的特性,我们可以对http请求的返回内容进行拦截而不让开发者有任何感知。
Promise
async
http
axios
应用场景:中后台系统的接口一般会有严格的权限要求以及我们需要对接口异常进行捕获。通过拦截封装,可以在业务层代码拿到数据之前,先做一层验证。
// 根据需要自定义创建Axios对象实例 const axios = new Axios.create() // 封装post函数 const $post = (url, postData) => { return axios.post(url, postData) // 在未封装的情况下,我们一般通过这种方式发起一个post请求 .then(interceptors.bind(this)) // 针对权限验证的拦截 .catch(reject.bind(this)) // 捕获接口异常 } // 业务层调用 $post('./get_order_list', postData).then(res => {})
// 预处理后端错误码 const interceptors = (res) => { const code = res.data.code if(code === 100) { // 未登录 console.log('请先登录') // or redirect to login_error_url return Promise.reject('LoginError') } else if (code === 101) { // 缺失访问/修改/删除权限 console.log('没有访问权限') // or redirect to permission_error_url return Promise.reject('PermissionError') } else { // 正常将数据返回 return response.data } }
// 针对接口异常的捕获 const reject = (error) => { if(error instanceof Error) { const errMsg = error.message let type = 'error' let message = '服务器出错了',请联系管理员 if(/timeout/.test(errMsg)) { // 服务器请求超时 message = '请求超时...' type = 'warning' } // ... console.log(message) } return Promise.reject(error) }
效果如下:
// 配合await关键字一起使用 const submit = async () => { const data = await $post(url) // ... } // 也可以用在Vue等框架的钩子函数中 async mounted () { const data = await $post(url) // ... }
系统中可能存在某种功能(比如打印)需要引入第三方库(html2canvas.js,print.js等),但有时候某些第三方库可能体量惊人,而用户访问页面又不一定会触发该功能,这时候就可以考虑动态引入。
html2canvas.js
print.js
function print () { import('html2canvas.js').then((html2pdf) => { html2pdf().outputPdf() }) }
import()返回一个Promise对象,只在运行时加载指定模块。另外一个常见的应用场景是:Vue的路由懒加载。
import()
Vue
const productListModule = () => import(/* webpackChunkName: "product" */ 'view/product/List') // 商品列表
假定场景:在一个商品列表页面,存在两个可排序的列:单价(price)和款式(style)。如果先点击了按单价排序,再点击按款式排序,要求款式基于单价的排序上再排序。
price
style
// 基础数据 const products = [ { name: "椒盐T恤", price: 3, style: 'Japanese' }, { name: "蘸酱短袖", price: 5, style: 'Chinese' }, { name: "碳炙短袖", price: 4, style: 'Chinese' }, { name: "印花短袖", price: 8, style: 'England' }, { name: "写意短袖", price: 3, style: 'Chinese' }, { name: "清蒸T恤", price: 4, style: 'Japanese' }, ] // 定义不同的排序规则 const byPrice = (a, b) => a.price - b.price const byStyle = (a, b) => a.style > b.style ? 1 : a.style === b.style ? 0 : -1 // 利用reduce组合排序 const sortByFlattened = fns => (a,b) => fns.reduce((acc, fn) => acc || fn(a,b), 0) // 组合后的排序函数,排序优先级按数组内元素位置编号 const byPriceStyle = sortByFlattened([byPrice,byStyle]) console.log(products.sort(byPriceStyle))
排序结果如下:先按价格升序,再按款式升序
结语
以上仅是个人在日常开发中的ES6使用总结,并未包含全部ES6特性,尚有许多特性较少应用,欢迎大家一起交流,补充分享你们实际项目中应用ES6的情形。
最后,如果您觉得本文对您有所启发,请勿吝啬您的点赞哈哈~
本文是一篇应用型文章,根据实际的项目场景总结ES6的各种使用姿势。文章不会对ES6语法中的特性或原理做过多的说明,重点从实际的应用场景去理解ES6的新语法、新特性。
一、变量声明
为解决(或规范)JS中块级作用域的问题,ES6新增了
let
和const
两种声明变量的方式,与var
的区别在于:var
声明的变量,作用域会提升,变量可被重赋值;let
和const
声明的变量存在块级作用域和暂时性死区,但不会变量提升;let
声明的变量可被重赋值,const
不行。日常我们项目开发中,会在IDE保存文件或提交Git时会借助
Eslint
之类的工具对代码规范进行校验,其中一条常见的校验提示是:声明的变量xxx没有被再赋值,建议使用const
。 因此,一般我们用ES6声明变量时,可以遵循下面的原则:let
命令;const
命令;var
命令。二、解构赋值
解构赋值是从字符串、数组或对象中提取值,对变量进行赋值的过程。可以应用在变量定义、函数参数等场景中。
三、数据结构Set和Map
Set
数据结构要求其元素不可重复,这种唯一性特性在项目开发中能带来很大便利;Map
数据结构是一种键值对集合,它对键的数据类型提供了更广泛的支持,同时有一系列极好用的API。这里我们以数据列表为场景:对销售订单进行对账,要求限制只能对同一产品进行对账。![image](https://user-images.githubusercontent.com/26505680/61759345-c3ce0980-adfa-11e9-9d37-f4d1ab467f11.png)
1. Set的不重复性
2. Map的数据存储
四、模板字符串
ES6的模板字符串允许我们在字符串中嵌入变量,或者定义一个多行字符串,有更好的可读性。
五、基础函数
1. 箭头函数
箭头函数,让你的表达更简洁。它有以下几个特点:
this
,是箭头函数定义时所在上下文的this
,这点不可变。但可以通过bind
、call
、apply
方法改变上下文的this
指向;new
命令;arguments
对象,但可以使用rest
参数代替;yield
命令,因此箭头函数不能用作Generator
函数。2. 默认参数
add(1,2) // output -> '3.00'
4. 参数解构
六、条件判断
中后台系统中权限管理是必备的功能,从控制页面的访问权限到某个功能的操作权限。这里以权限管理为应用场景,看如何用ES6优雅地进行条件判断。
1. 路由权限控制
2. 系统功能权限控制(比如对账操作)
七、改写for循环
假定我们有一个订单数据列表:
1. Map取出数据
2. forEach改变源数据
3. 如何continue
如果购买顾客是杨过和小龙女,将其标识为一对CP(换句话说,循环列表过程,遇到郭襄就执行
continue
命令):4. 如何break
商家搞优惠活动,按序给每个订单总价核减5元(核减的5元返还给顾客),直到遇到第一个奇数总价的订单(换句话说,虚幻列表过程,遇到第一个奇数总价就执行
break
命令,终止循环)。八、对象与数组互转
假定场景:给每个订单的对账情况加上状态样式(
success
、warning
、danger
),有如下数据:实现功能:将样式映射表转化为状态映射表,形如:
使用ES6的
entries
和reduce
等API实现:九、数据采集
有时候我们需要在一个数据对象中取出部分的数据项,经过一定的组装再传递给某个组件或发送到服务器。
假定场景:对一个对账单进行修改,有一个
form
变量存储表单数据,router
上还带有一些参数queryData
。实现将修改的数据重新组装后发给服务器,但存在部分数据不需要发送。1. Object.assign提取数据
2. filter过滤数据
3. 扩展运算符组合数据
十、异步函数
ES6提供了
Promise
对象和async
函数用于处理异步操作,用于http
请求的axios
库就是基于Promise
实现的。利用Promise
的特性,我们可以对http
请求的返回内容进行拦截而不让开发者有任何感知。应用场景:中后台系统的接口一般会有严格的权限要求以及我们需要对接口异常进行捕获。通过拦截封装,可以在业务层代码拿到数据之前,先做一层验证。
1. 基于Promise再封装post
2. interceptors拦截器
3. 异常捕获函数reject
效果如下:![image](https://user-images.githubusercontent.com/26505680/61759390-de07e780-adfa-11e9-9118-62ef0c314fb4.png)
4. Asyn函数让异步代码更优雅、简洁
十一、动态加载(import)
系统中可能存在某种功能(比如打印)需要引入第三方库(
html2canvas.js
,print.js
等),但有时候某些第三方库可能体量惊人,而用户访问页面又不一定会触发该功能,这时候就可以考虑动态引入。import()
返回一个Promise
对象,只在运行时加载指定模块。另外一个常见的应用场景是:Vue
的路由懒加载。十二、多维度组合排序
假定场景:在一个商品列表页面,存在两个可排序的列:单价(
price
)和款式(style
)。如果先点击了按单价排序,再点击按款式排序,要求款式基于单价的排序上再排序。排序结果如下:先按价格升序,再按款式升序![image](https://user-images.githubusercontent.com/26505680/61759403-e95b1300-adfa-11e9-923b-5df7110731cb.png)
结语
以上仅是个人在日常开发中的ES6使用总结,并未包含全部ES6特性,尚有许多特性较少应用,欢迎大家一起交流,补充分享你们实际项目中应用ES6的情形。
最后,如果您觉得本文对您有所启发,请勿吝啬您的点赞哈哈~