当前位置 博文首页 > 前端开发博客:90% 前端都会的 ES6 简化代码技巧,你用过哪些?
点击下方“前端开发博客”关注公众号
回复“2”加入前端群
最初?JavaScript
?语言有 2 份标准:
ECMA-262
:主标准,由 ECMA 国际组织(Ecma International
)负责管理(为了让最初的JavaScript
?与最初的?JScript
?能遵循同一套标准发展而诞生的?ECMAScript
?,正好排到了作为?Ecma
?的?262
?号标准,所以得到?ECMA-262
?编号。)
ISO/IEC 16262
:第二标准,由国际标准化组织?ISO
(International Standard Organization
)和国际电子技术委员会?IEC
(International Electrotechnical Commission
)负责管理
出于商标版权的原因,规范标准中将这门语言称为?ECMAScript
?,所以原则上?JavaScript
?与ECMAScript
?指的是同一个东西,但有时也会加以区分:
JavaScript
:指语言及其实现
ECMAScript
:指语言标准及语言版本,比如 ES6 表示语言(标准)的第 6 版
ECMAScript 1(1997 年 6 月):规范第一版
ECMAScript 2(1998 年 6 月):为了同步 ISO 标准,引入了一些小更新
ECMAScript 3(1999 年 12 月):增加了正则表达式、字符串处理、控制语句(do-while、switch)、异常处理(try-catch)等众多核心特性
ECMAScript 4(2008 年 7 月废除):本来是一次大规模升级(静态类型、模块、命名空间等),但跨度过大,出现了分歧,最终没能推广使用
ECMAScript 5(2009 年 12 月):变化不大,加了一些标准库特性和严格模式
ECMAScript-5.1(2011 年 6 月):又一次小更新,为了同步 ISO 标准
ECMAScript 6(2015 年 6 月):一大波更新,实现了当年 ES4 的许多设想,并正式改为按年份命名规范版本
ECMAScript 2016(2016 年 6 月):第一个年度版本,与 ES6 相比,发布周期较短,新特性也相对少些
ECMAScript 2017(2017 年 6 月):第二个年度版本...
以后的 ECMAScript 版本(ES2018、ES2019、ES2020 等)都在 6 月正式获准生效
这里引用?阮一峰
?老师的?ES6标准入门
?一书中的总结:ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版本以后的?JavaScript
?的下一代标准,涵盖了?ES2015、ES2016、ES2017
等,而?ES2015
?则是正式名称,特指当年发布的正式版本的语言标准 市面上提到的 ES6 一般是指?ES2015
?标准,但有时也是泛指?下一代 JavaScript
本文主要讲解以下内容:
块级作用域(Block scoping,ES2015)
解构(Destructuring,ES2015)
箭头函数(Arrow Functions,ES2015)
模板字符串(template string,ES2015)
剩余参数 / 展开语法(Rest and spread parameters,ES2015)
对象字面量简写语法(Object shorthand,ES2015)
数组实例的 includes() (ES2016)
Async/await 异步语法 (ES2017)
为什么需要块级作用域?
ES5 只有全局作用域和函数作用域,没有块级作用域,这导致很多场景不合理。
第一种场景,内层变量可能会覆盖外层变量。
var?tmp?=?new?Date()
function?fn()?{
??console.log(tmp)
??if?(false)?{
????var?tmp?=? hello?world
??}
}
fn()?//?undefined
以上代码的原意是, if 代码块的外部使用外层的 tmp 变量,内部使用内层的 tmp 变量。但是,函数?fn
?执行后,输出结果为?undefined
?,原因在于变量提升导致内层的 tmp 变量覆盖了外层的 tmp 变量。
第二种场景,用来计数的循环变量泄露为全局变量。
var?s?=? hello
for?(var?i?=?O;?i?<?s.length;?i++)?{
??console.log(s[i])
}
console.log(i)?//?5
上面的代码中,变量?i
?只用来控制循环,但是循环结束后,它并没有消失,而是泄露成了全局变量。
let
?实际上为?JavaScript
?新增了块级作用域。
function?fl()?{
??let?n?=?5
??if?(true)?{
????let?n?=?10
??}
??console.log(n)?//?5
}
上面的函数有两个代码块,都声明了变量?n
,运行后输出?5
?。这表示外层代码块不受内层代码块的影响。如果使用?var
?定义变量 ,最后输出的值就是?10
那么我们能利用块级作用域
做什么呢?
我们先来做道面试题
for?(var?i?=?0;?i?<?5;?i++)?{
??setTimeout(()?=>?{
????console.log(i)
??},?1000)
}
//?5?5?5?5?5
改成?ES6
?中的 let
for?(let?i?=?0;?i?<?5;?i++)?{
??setTimeout(()?=>?{
????console.log(i)
??},?1000)
}
//?0?1?2?3?4
看到这,相信聪明的你已经理解块级作用域的好处了 O(∩_∩)O
那么?ES5
?能不能实现?块级作用域
?的效果呢? 可以的,我们可以利用闭包
for?(var?i?=?0;?i?<?5;?i++)?{
??;(function?(index)?{
????setTimeout(()?=>?{
??????console.log(index)
????},?1000)
??})(i)
}
//?0?1?2?3?4
解构 :是将一个数据结构分解为更小的部分的过程。ES6 中,从数组和对象中提取值,对变量进行赋值。
关于解构推荐:汇总JS语法 ES6、ES7、ES8、ES9、ES10、ES11、ES12新特性
那么解构有什么用处呢?
可以大大的简化变量声明操作。
//?ES5
var?foo?=?1
var?bar?=?2
var?baz?=?3
//?ES6
let?[foo,?bar,?baz]?=?[1,?2,?3]
变量交换:看起来如同镜像。赋值语句的左侧的解构模式,右侧是临时创建的数组字面量。x 被赋值为数组中的 y,y 被赋值为数组中的 x。
let?x?=?1
let?y?=?2
;[x,?y]?=?[y,?x]
//?x?=?2,?y?=?1
对象解构
var?obj?=?{?x:?1,?y:?2,?c:?1?}
let?{?x,?y?}?=?obj
//?x?=?1
//?y?=?2
字符串解构
const?[a,?b,?c,?d,?e]?=? hello
//?a?=>?h
//?b?=>?e
//?c?=>?l
//?d?=>?l
//?e?=>?o
函数参数解构
const?xueyue?=?{
??name:? 雪月 ,
??age:?18,
}
function?getAge({?name,?age?})?{
??return?`${name}今年${age}岁`
}
getAge(xueyue)?//?雪月今年18岁
ES6
?允许使用箭头?=>
?定义函数
var?f?=?v?=>?v
//?等同于?ES5?的
var?f?=?function?(v)?{
??return?v
}
如果箭头函数不需要参数或需要多个参数,就使用圆括号代表参数部分。
var?f?=?()?=>?5
//?等同于?ES5?的
var?f?=?function?()?{
??return?5
}
var?sum?=?(numl,?num2)?=>?numl?+?num2
//?等同于?ES5?的
var?sum?=?function?(numl,?num2)?{
??return?numl?+?num2
}
箭头函数可以与解构结合使用。
const?full?=?({?first?,?last?})?=>?first?+? ? ?+?last;
//?等同于?ES5?的
function?full(person)?{
??return?person.first?+? ? ?+?person.last;
}
箭头函数使得表达更加简洁
const?isEven?=?n?=>?n?%?2?===?0
const?square?=?n?=>?n?*?n
var?result?=?values.sort((a,?b)?=>?a?-?b)
//?等同于?ES5?的
var?result?=?values.sort(function?(a,?b)?{
??return?a?-?b
})
上面代码只用了两行,就定义了两个简单的工具函数。如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。
箭头函数使用注意点
函数体内的?this
?对象,就是定义时所在的对象,而不是使用时所在的对象。
不可以当作构造函数,也就是说,不可以使用?new
?命令,否则会抛出一个错误。
不可以使用?arguments
?对象,该对象在函数体内不存在。如果要用,可以用?rest
?参数代替。
不可以使用?yield
?命令,因此箭头函数不能用作?Generator
?函数。
上面四点中,第一点尤其值得注意。this
?对象的指向是可变的,但是在箭头函数中,它是固定的。
//?ES6
function?foo()?{
??setTimeout(()?=>?{
????console.log( id: ,?this.id)
??},?100)
}
//?转换成ES5
function?foo()?{
??var?_this?=?this
??setTimeout(function?()?{
????console.log( id: ,?_this.id)
??},?100)
}
上面代码中,转换后的?ES5
?版本清楚地说明了,箭头函数里面根本没有自己的?this
,而是引用外层的?this
。
模板字符串( template string )是增强版的字符串 ,用反引号?
(``)
?标识 。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
const?{?log?}?=?console
const?name?=? 雪月
const?age?=?18
//?普通字符串拼接
const?result?=?name?+? 今年 ?+?age?+? 岁
//?使用模板字符串
const?result2?=?`${name}今年${age}岁`
log(result)?//?雪月今年18岁
log(result2)?//?雪月今年18岁
//?${}?大括号可以放入任意的?JavaScript?表达式,可以进行运算
const?result3?=?`${name}今年${age?*?2}岁`
log(result3)?//?雪月今年36岁
ES6 引入了 rest 参数(形式为...变量名
),用于获取函数的多余参数,这样就不需要使用?arguments
?对象了。rest
?参数搭配的变量是一个数组,该变量将多余的参数放入其中。
function?sortNumbers()?{
??return?Array.prototype.slice.call(arguments).sort()
}
//?使用?rest
const?sortNumbers?=?(...numbers)?=>?numbers.sort()
比较上面的两种写法可以发现,?rest
?参数的写法更自然也更简洁。
扩展运算符(?spread
?)是三个点(...) 如同?rest
?参数的逆运算 将一个数组转为用逗号分隔的参数序列
console.log(...[1,?2,?3])
//?1?2?3
console.log(1,?...[2,?3,?4],?5)
//?1?2?3?4?5
下面是扩展运算符取代?apply
?方法的一个实际例子 应用?Math.max
?方法简化求出数组中的最大元素。
//?ESS?的写法
Math.max.apply(null,?[14,?3,?77])
//?ES6?的写法
Math.max(...[14,?3,?77])
//?等同于
Math.max(14,?3,?77)
扩展运算符提供了数组合并的新写法。
//??ESS
;[1,?2].concat(more)
//?ES6
;[1,?2,?...more]
对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let?z?=?{?a:?3,?b:? bb ?}
let?n?=?{?...z?}
n?//?{?a:?3,?b:? bb ?}
n?===?z?//?false
特别注意:?...
扩展对象,只能做到当对象属性是?基本数据类型
?才是?深拷贝
,如果是?引用数据类型
,那就是浅拷贝
。
let?z?=?{?a:?3,?b:? bb ,?c:?{?name:? ccc ?}?}
let?n?=?{?...z?}
n?//?{?a:?3,?b:? bb ,?c:?{?name:? ccc ?}?}
n?===?z?//?false
n.c?===?z.c?//?true
//?n.c?跟?z.c?是同一个引用地址
const?name?=? 雪月
//?ES5写法
const?obj?=?{
??name:?name,
??f:?function?()?{
????console.log(this.name)
??},
}
//?ES6简写
const?obj2?=?{
??name,
??f()?{
????console.log(this.name)
??},
}
obj.f()?//?雪月
obj2.f()?//?雪月
使用?vue
?的同学是不是感到很熟悉
new?Vue({
??el:? #app ,
??data()?{
????return?{
??????list:?[],
????}
??},
})
Array.prototype.includes 方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似。ES2016 引入了该方法。
;[1,?2,?3].includes(2)?//?true
;[1,?2,?3].includes(4)?//?false
;[1,?2,?NaN].includes(NaN)?//?true
没有该方法之前,我们通常使用数组的 indexOf 方法,检查是否包含某个值。
//?ES5
if?(arr.indexOf(el)?!==?-1)?{
??//?...
}
//?ES6
if?(arr.includes(el))?{
??//?...
}
//?那么 indexOf 能不能做到类似于 includes 的写法呢??我们可以利用?~?位运算符
if?(~arr.indexOf(el))?{
??//?...
}
indexOf
?方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对?NaN
?的误判。
;[NaN].indexOf(NaN)
//?-1
includes
?使用的是不一样的判断算法,就没有这个问题
;[NaN].includes(NaN)
//?true
ES2017
?标准引入了?async
?函数,使得异步操作变得更加方便。
async
?函数是什么?一句话,它就是?Generator
?函数的语法糖。
async?function?getTitle(url)?{
??let?response?=?await?fetch(url)
??let?html?=?await?response.text()
??return?html.match(/<title>([sS]+)</title>/i)[1]
}
getTitle( https://tc39.github.io/ecma262/ ).then((res)?=>?console.log(res))
更多推荐:手写async await的最简实现(20行搞定)!阿里字节面试必考
上面代码中,函数?getTitle
?内部有三个操作:抓取网页
、取出文本
、匹配页面标题
。只有这三个操作全部完成,才会执行?then
?方法里面的?console.log
文章介绍了?ES6
?常用的一些语法以及使用场景; 但是?ES6
?内容远不止于此,感兴趣的同学可以去?阮一峰老师的
?ES6 入门教程 一书中查看详细内容。如果您认可这本书,也可以去正版渠道购买书籍。这样可以使出版社不因出版开源书籍而亏钱,进而鼓励更多的作者开源自己的书籍。
还有很多?ES6
?实用的?API
?我就简单提及一下,朋友们看看平时是否有用到
;[1,?4,?-5,?10].find(n?=>?n?<?0)
//?-5
;[1,?5,?10,?15].findIndex((value,?index,?arr)?=>?value?>?9)?
//?2
;[1,?2,?[3,?[4,?5]]].flat()
//?[1,?2,?3,?[4,?5]]
;[1,?2,?[3,?[4,?5]]].flat(2)
//?[1,?2,?3,?4,?5]
;[3,?8,?54,?8,?3,?NaN,?NaN,? NaN ,? NaN ].filter((number,?index,?arr)?=>?arr.indexOf(number)?===?index)
//?[3,?8,?54,?"NaN"]?利用filter过滤去重,注意会漏掉NaN
;[1,?2,?3,?4].map((item)?=>?item?*?2)
//?[2,?4,?6,?8]?利用map返回一个新数组,不改变原数组
//?使用?reduce?求和;?reduce功能极其强大?!?yyds
;[0,?1,?2,?3,?4].reduce(function(accumulator,?currentValue,?currentIndex,?array){
??return?accumulator?+?currentValue;
});?
//?10
//?ES2017?引入了跟?Object.keys?配套的?Object.values?和?Object.entries,作为遍历一个对象的补充手段,
//?供 for...of 循环使用。
let?{?keys,?values,?entries?}?=?Object;
let?obj?=?{?a:?1,?b:?2,?c:?3?};
for?(let?key?of?keys(obj))?{
??console.log(key);?//? a ,? b ,? c
}
for?(let?value?of?values(obj))?{
??console.log(value);?//?1,?2,?3
}
for?(let?[key,?value]?of?entries(obj))?{
??console.log([key,?value]);?//?[ a ,?1],?[ b ,?2],?[ c ,?3]
}
关于本文
https://juejin.cn/post/6960868793140641799
推荐阅读
九个超级实用的 ES6 特性
END
关注下方「前端开发博客」,回复?“电子书”
领取27本精选电子书
如果你觉得这篇内容对你挺有启发,我想邀请你帮我两个小忙:
点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)
关注公众号「前端开发博客」,每周重点攻克一个前端面试重难点
点个在看支持我吧,转发就更好了
cs