ES6
知识补充
概念
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。
它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。
提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。
ECMAScript是JavaScript的规格、规范
JavaScript是其的一种实现
Babel
1、概述
Babel 是一个工具链,主要用于在旧的浏览器或环境中将ESCMAScript 2015+ 代码转换为向后兼容版本的 JavaScript代码。
通俗说:实现es5之前版本向es6之后版本的转换
------------------
2、安装
npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill
--------------------------
3、配置
打开package.json文件,将以下内容添加:
{
//预设置版本
"presets" : [
//最后版本
"latest"
],
// 插件
"plugins" : [],
// 开发描述
"devDependencies" : {
// babel 命令提示符
"babel-cli" : "^6.0.0",
"babel-preset-latest" : "^6.24.1"
},
"scripts" : {
// 构造
// 将src(es6之后版本)目录和lib(es5之前版本)目录,语法版本转换
"build" : "babel src -d lib"
}
}
---------------------------------
4、运行(实现转码转换)
npm run build
-------------------
5、导入使用,已转好的lib目录下生成的js文件
块级作用域
1、在 ES5版本没有块级作用域
例如:
if (true) {
var v = 'jine'
}
console.log(v)
-------------------------------------------------------
2、ES6 提供了块级作用域
if (true) {
let es6 = 'jine'
}
console.log(es6)
============================================
for 循环中用var声明的变量,在全局可以访问到,因此可以使用let关键字声明变量,成为块级作用域
基础语法
let
let 与 var 区别:
let 特点:
1、没有变量提升
例如:
console.log(age);
let age=38;
-----------------------------------------------------------------------------------------
2、有块级作用域
例如:
for(let i=0;i<10;i++){
}
console.log(i)
ES6 允许块级作用域的任意嵌套,例如:
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};
-----------------------------------------------------------------------------------------
3、不能重复声明
let num=10;
let num=20;
console.log(num)
function fun(a){
let a = 10
console.log(a)
}
fun(100)
"注意":"在ES6中函数的参数相当于使用let关键字定义的局部变量"
----------------------------------------------------------------------------------------=
4、可以重新赋值
let num=10;
num=20;
console.log(num)
-----------------------------------------------------------------------------------------
5、暂时性死区
只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc';
let tmp;
}
上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。
这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
=========================================================================================
var 特点:
1、有变量提升
例如:
console.log(es5)
var es5 = 'jine'
--------------------------
2、没有块级作用域,是函数作用域
3、可以重复声明
4、可以重新赋值
const
const 关键字声明的常量(一旦声明就无法更改)
1、没有变量提升
2、有块级作用域
3、不能重复声明
4、不能重新赋值(声明必须要有初始值)
5、同样存在暂时性死区
const foo = {};
foo.prop = 123;
foo.prop
foo = {};
上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。
不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
解构赋值
对象解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
对象可以不按顺序赋值,只要找到对应的属性便可以
1、ES6写法
let obj={
name:"jine",
age:22,
mvp:"hha"
}
let {name:name1,age:age1}=obj
-----------------------------------------------------------------------------------------
变量名可与对象属性名一致
let {name:name,age:age}=obj
-----------------------------------------------------------------------------------------
如果声明的变量名和对象属性名一致,可以简写,例如:
let{name,age}=obj
-----------------------------------------------------------------------------------------
若出现变量名没有和对象属性名一致,只会声明一个空变量,例如:
let{name,age,other}=obj
console.log(name,age,other)
-----------------------------------------------------------------------------------------
若出现变量名和对象属性名有的相同,有的不同,可以一下写法,例如:
let{name,age,mvp:other}=obj
console.log(name,age,other)
-----------------------------------------------------------------------------------------
若对象属性值已有,在声明相同的变量来进行赋值并不会覆盖(如下age)
若声明的变量没有对应的对象属性,可以给这个变量初始化值,便不会出现undefined(如下height),例如:
let{name,age=18,other,height=175}=obj
console.log(name,age,height)
-----------------------------------------------------
let{name,...obj2}=obj
console.log(obj2)
=========================================================================================
2、ES5写法
var obj={
name:"jine",
age:22
}
var name1=obj.name
var age1=obj.age
数组解构赋值
数组顺序进行依次赋值
1、ES6写法
let arr=[10,20,30]
let [num1,num2,num3]=arr
console.log(num1,num2,num3)
let [num1,num2,num3,num4]=arr
console.log(num1,num2,num3,num4)
let [num1,num2,num3,num4=40]=arr
console.log(num1,num2,num3,num4)
let [num1=100,num2,num3,num4=40]=arr
console.log(num1,num2,num3,num4)
let [num1,num2=100] = [10,undefined]
console.log(num1,num2)
=========================================================================================
2、ES5写法
var arr=[1,2,3]
var a=arr[0]
var b=arr[1]
var c=arr[2]
字符串解构赋值
变量的数量与字符串中字符的数量进行一一对应
例如:
let [a,b,c,d,e] = "Hello"
console.log(a,b,c,d,e)
解构赋值结合函数声明
function test1({name,age}){
console.log(name,age)
}
test1({
name:"jine",
age:20,
})
模板字符串
语法: ``(使用反引号标识)
作用:类似于python的多行字符串(ES5是没有多行字符串)
特点:1、可以在模板字符串中调用变量或函数。2、模板字符串中的所有的空格和缩进也都会保留。
可以在模板字符串中调用变量,例如:
let author="----jine"
let strs=
`
我要开始输入啦,
这是第二段的字符串输入,
这便是多行字符串的输入. Noproblem~
${author}
`
console.log(strs)
-----------------------------------------------------------------------------------------
也可以在模板字符串中写调用函数,例如
let fun=()=>"我是一个小函数~"
console.log(`我也可以调用函数哟:${fun()}`)
字符串的扩展
1、includes()
用来判断一个字符串是否包含在另一个字符串中,返回布尔值
'注意:是区分大小写的'
let str = 'woshihhaoee'
console.log(str.includes('o',8))
console.log(str.includes('o',9))
------------------------------------------------------------
2、startsWith()
用来判断当前字符串的指定索引值(默认为 0)是否以另外一个给定的字符串 '开头' 的 , 返回布尔值
'注意:是区分大小写的'
例如:
let str = 'woshihhaoee'
console.log(str.startsWith('wo'))
console.log(str.startsWith('hi',3))
----------------------------------------------------------
3、endsWith()
用来判断当前字符串的指定索引值(默认为 0)是否以另外一个给定的字符串 '结尾' 的 , 返回布尔值
-----------------------------------------------------------
4、repeat()
用于将原字符串重复n次,返回一个新字符串
返回的是一个新字符串,并不会改变原有字符串内容
str.repeat(number)
number 参数有以下几种情况:
若number参数为小数,则会向下取整
若number参数为负数或无穷大,则会报错
若number参数为NaN,则为0(不报错,不会有任何输出)
若number参数为字符串,则会先转换为数字值
例如:
let str = 'woshihhaoee'
res=str.repeat(3)
console.log(str,res)
------------------------------------------------------------
5、原始字符串
let str = 'woshihhaoee'
function fun(arg){
console.log(arg)
}
fun `this is ${str} ok`
fun(`this is ${str} ok `)
-----------------------------------------------------------------------------
6、raw()
ES6 还为原生的 String 对象,提供了一个`raw()`方法。该方法返回每一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。
例如:
console.log(`hello\n${2+1}`)
console.log(String.raw`hello\n${2+1}`)
展开运算符
对象展开
let Person={
name:"jine",
age:22,
printf(){
console.log(this.name,this.age)
}
}
let Fun={
song:"歌唱",
jump:"跳跳"
}
let Jine={
...Person,
...Fun,
jump:"不跳了"
}
console.log(Jine)
数组展开
数组展开做的是深复制(修改一个内容,不会改变另外一个内容)
1、可以做数组拼接
let arr1=[1,2,3]
let arr2=[4,5,6]
let arr3=[...arr1,...arr2]
console.log(arr3)
------------------------------------------------------------
2、也可求max值等
let max1=Math.max(arr3)
console.log(max1)
let max2=Math.max(...arr3)
console.log(max2)
----------------------------------------------------------
3、 解构赋值和展开运算符配合使用
let num = [1,2,3,4,5,6]
let [arr1,...arr2] = num
console.log('arr1:'+arr1,'arr2:'+arr2)
----------------------------------------------------------------
4、字符串配合数组展开
let sttr = 'Hello World'
console.log([...sttr])
函数新特性
箭头函数
匿名函数的简写,但是这样写法的this指向和以前写法的this指向不一样
简写规则:
a.function改成=> => 可以读成goesto
b.如果只有一个形参,那就可以省略形参小括号.
C.如果不是一个形参,0个或者多个形参,那就不能省略这个形参小括号了
d.如果函数体只有一句话, 那就可以省略函数体的大括号
e.如果函数体只有一句话,并且这一句话是return返回值,那return也要省略
f.如果函数体不是一句话,那就不能省略这个大括号.
-----------------------------------------------------------------------------------------
由规则b,d,例如:
let fun=function(name){
console.log("name:"+name)
}
let fun=name=>console.log("name:"+name)
fun("jine")
-----------------------------------------------------------------------------------------
由规则c,d,例如:
let fun=function(name,age){
console.log(name,age)
}
let fun=(name,age)=>console.log(name,age)
fun("jine",22)
-----------------------------------------------------------------------------------------
由规则a,d,e,例如:
let fun=function(name){return name}
let fun=name=>name
console.log(fun("jine"))
-----------------------------------------------------------------------------------------
由规则c,f,例如:
let fun=function(name,age){
console.log(name)
console.log(age)
}
let fun=(name,age)=>{
console.log(name)
console.log(age)
}
fun("jine",22)
-----------------------------------------------------------------------------------------
"需要注意的是:
let fun=name=>
console.log(name)
"这样也是可以的,虽然换行了,但是因为有箭头,所以不是结束,所以还会输出name"
箭头函数 this
ES5 定义函数时,使用的this,指向'调用函数时'的上下文对象
ES6 定义箭头函数时 ,使用的this,指向'定义箭头函数时'的上下文对象
* 不要用new 关键字去调用箭头函数
* 不可使用argument对象,该对象在函数体内不存在。若用,可以使用rest参数代替
例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>箭头函数this</title>
</head>
<body>
<button id="btn">点我呀~</button>
<script>
document.getElementById('btn').onclick=()=>{
console.log(this)
console.log(this.textContent)
}
</script>
</body>
</html>
函数参数指定默认值
ES5标准不允许对函数的参数设置其默认值。若定义了形成,而不传递实参时,会导致形成的值为undefined。
例如;
function fun(arg){
console.log(arg)
}
fun()
/*undefined */
function fu(arg){
//通过其他方式设置默认值
arg = arg || 0
console.log(arg)
}
fu()
/*0 */
-------------------------------------------------------------------
Es6 新增可以给形参设置默认值
let funny = (arg=1) => console.log(arg)
funny()
/* 1 */
===============================================================
let v = 100
let func = (arg=v) => {
let v = 200
console.log(arg)
}
func()
/*
结果:100
结论:加载当前函数的形参,并且设置其默认值时,形成一个独立的作用域
当前独立作用域既不是全局作用域,而不是函数作用域,也不是块级作用域
当为形参设置默认值时,当前只能访问全局变量v
当前独立作用域,会在函数声明结束后,自动消失
*/
rest 参数
ES6 新增rest 参数,用来获取函数多余的参数,替代arguments对象,与rest参数配合的变量是一个数组,该变量将多余的参数放入数组中。
'注意:rest参数之后不能再有其他参数(只能是最后一个参数),否则会报错
函数的length属性,不包含rest参数。
'
例如:
let fun = (arg,...rest) =>{
console.log(arg,rest)
}
fun(1,2,3,4,5,6,7,8)
对象新特性
对象成员的简写
let name="jine"
let age=20
let obj={
name,
age,
printf(){
console.loe(name,age)
}
}
obj.printf()
-----------------------------------------------------------------------------------------
以下写法会报错,例如:
let obj={
name,
age,
score,
printf(){
console.log(name,age,score)
}
}
obj.printf()
-----------------------------------------------------------------------------------------
let obj={
name:1,
age,
score:age+58,
printf(){
console.log(this.name,age,this.score)
}
}
obj.printf()
Object.is()
Object.is() 就是对同值相等算法的具体实现
'ES5'
console.log( +0 === -0 )
console.log(NaN === NaN )
'ES6'
console.log(Object.is(+0 ,-0 ))
console.log(Object.is(NaN,NaN))
Object.assign()
用于将所有可枚举属性的值从一个或多个源对象复制到目标对象
Object.assign(target,...sources)
* target : 目标对像
* sources : 源对象
* 返回值 : 目标对象
是一个深复制操作
super
ES6 新增关键字用于指定向当前对象的原型对象
'注意:super 关键字表示原型对象时,只能在对象的方法之中,用在其他地方都会报错'
let pro = {
age : 22
}
let obj = {
name : 'jine',
printf(){
// 这里等价于 this.age ,只不过用Es6新增的super关键字,更明了清晰,表达了原型对象
console.log(super.age)
}
}
// 将pro对象作为obj的原型对象
Object.setPrototypeOf(obj,pro)
obj.printf()
数组新特性
—引用类型—
Array.from()
可以从一个伪数组或可迭代对象中创建一个新的数组实例
应用:伪数组对象和对象不可直接用展开运算符展开,可以借助Array.from() 构建一个新伪数组对象,再使用展开运算符
当然对象展开也可以以下这样操作:
let obj = {
0 : 'jine',
1 : 22,
2: 'code',
length : 3
}
let obj2 = {}
console.log(obj2 = {...obj})
Array.from() 应用如下,例如:
let obj = {
0 : 'jine',
1 : 22,
2: 'code',
length : 3
}
for (let i=0 ; i<obj.length; i++) {
console.log(obj[i])
}
let arry = Array.from(obj)
console.log(typeof arry)
console.log(arry instanceof Array)
console.log(...arry)
Array.of()
用于创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型
可以测试代码,得结果,如下:
console.log(new Array(1))
console.log(new Array(1,2))
console.log(Array.of(1))
console.log(Array.of(1,2))
"注意:若没有传递参数,则返回一个空数组"
—实例方法—
forEach()
forEach() 可以替换以前用的for循环
作用:循环遍历,把遍历出来的每一项交给回调函数,无返回值
例如:
let arr=[1,2,3]
let ret=arr.forEach(function(item,index){
console.log(index,item)
return "返回值"
})
console.log(ret)
-----------------------------------------------------------------------------------------
map()
map() 循环遍历,与forEach()类似,唯一不同的是map()有返回值
let arr=[1,2,3]
let ret=arr.map(function(item,index){
console.log(index,item)
return item*item
})
console.log(ret)
返回属性的值,例如:
let arr=[{name:"jine",age:18},{name:"ren",age:22},{other:"其他",age:23}]
let ret=arr.map((item,index)=>item.name)
console.log(ret)
-----------------------------------------------------------------------------------------
filter
filter() 过滤器
作用:返回一个新的数组,新的数组中的元素是通过检查后符合条件的元素
过滤偶数,例如:
let arr=[1,2,3,4,5,6]
let ret=arr.filter((item,index)=>item%2==0)
console.log(ret)
过滤指定元素,例如:
let arr=[{name:"jine",age:18},{name:"ren",age:22},{other:"其他",age:23}]
let ret=arr.filter((item,index)=>item.name)
console.log(ret)
copyWithin()
用于浅复制数组的一部分到统一数组中的另一个位置,并返回它,而不修改其大小
copyWithin(target,start,end)
* target:必需。复制到指定目标索引位置。
* start : 可选。元素复制的起始位置。若省略,则复制默认从开始位置进行
* end: 可选。停止复制的索引位置 (默认为 array.length)。如果为负值,表示倒数。若省略,则复制到最后 (注意:不包含结束索引位置)
例如:
let arry = [0,1,2,3,4,5]
console.log(arry.copyWithin(1,0,3))
/*
* 第一个参数,也就是target=1,指定索引目标复制并替换元素
* 第二个参数,也就是start=0 , 开始索引位置
* 第三个参数,也是就是end=3, 结束索引位置
*
* 结果:[ 0, 0, 1, 2, 4, 5 ]
*
* 结果可看出,target=1,索引1的位置开始替换,而start是复制元素的开始索引位置,end是复制元素结束的索引位置(不包含结束索引位置)
*
*/
'注意:不能改变数组的长度,而且修改了原有数组'
find() 与findIndex()
1、find()
返回数组中满足提供的测试函数的第一个元素的值,否则返回undefined。
arr.find(callback[,thisArg])
callback: 回调函数,有以下三个参数:
* element : 当前遍历到的元素
* index : 当前遍历到的索引值
* array : 数组本身
* thisArg : 可选,指定callback的this参数
例如:
let arry = [1,2,3,4,5]
let fun = arry.find((element,index,array) => {
console.log('element:'+ element)
})
console.log('fun: '+ fun)
/*
结果: element:1
element:2
element:3
element:4
element:5
fun: undefined
*/
let res = arry.find((element,index,arry) => {
return element < 3;
})
console.log('res: '+ res);
/*
结果:res: 1
结论: 因为element < 3 ,分别有:1,2 ,但是返回数组中满足提供的测试函数的第一个元素的值,所以res结果为1
*/
--------------------------------------------------------------
2、findIndex()
返回数组中满足提供的测试函数的第一个元素的索引值,否则返回-1
因其用法与find()基本类似,只是作用不同,所以不进行代码测试
fill()
用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引值。(浅复制填充)
fill(value[,start[,end]])
* value : 用来填充数组元素的值
* start : 可选项,起始索引,默认值为0
* end : 可选项,终止索引,默认值为 this.length (不包含结束索引)
* 返回值 :修改后的数组
let arry = [1,2,3,4,5]
let results = arry.fill(6)
console.log(arry,results)
results[0] = 1
console.log(results,arry)
let newArry = [1,2,3,4,5]
let fillArry = newArry.fill(6,1,3)
console.log(fillArry)
includes()
用来判断一个数组是否包含一个指定的值。
根据情况,若包含则返回true,否则返回false
includes(searchElement,fromIndex)
* searchElement : 需要查找的元素值
* fromIndex : 可选项,从该索引处查找searchElement。若为负值,则按升序从 array.length - fromIndex 的索引开始搜索。默认为0
例如:
let arr = [1,2,3,4,5]
console.log(arr.includes(3))
console.log(arr.includes(3,4))
数组应用
数组降维
let arr=[1,2,3,[4,5,6],7,8]
let arrNew=[]
arr.forEach(v=>{
if(Array.isArray(v)){
arrNew.push(...v)
}else{
arrNew.push(v)
}
})
console.log(arrNew)
数组去重
1、排序后判断法
let arr=[10,20,30,30,20,40,50,10,20]
let arrNew=[]
arr.sort((a,b)=>{
return a-b
})
console.log(arr)
arr.forEach((item,index)=>{
if(item!=arr[index+1]){
arrNew.push(item)
}
})
console.log(arrNew)
=========================================================================================
2、使用对象法,数组去重
let arr=[10,20,30,30,20,40,50,10,20]
let arrNew=[]
let obj={}
arr.forEach(item=>{
if(obj[item]==undefined){
arrNew.push(item)
obj[item]='value'
}
})
console.log(arrNew)
数组升维
let arr =[
{type:'电子产品',name:'iPhone',price: 8888},
{type:'家具',name:'桌子',price: 100},
{type:'食品',name:'瓜子',price: 10},
{type:'家具',name:'椅子',price: 380},
{type:'电子产品',name:'小米手机',price: 1380},
{type :'食品',name:'辣条',price: 5},
{type: '食品',name:'咖啡',price: 50}
];
let obj=[]
let arrNew=[]
arr.forEach(v=>{
if(obj[v.type]==undefined){
obj[v.type]='value'
arrNew.push({
type:v.type,
data:[v]
})
}
else{
arrNew.forEach((v2,j)=>{
if(v.type==v2.type){
arrNew[j].data.push(v)
}
})
}
})
console.log(arrNew)
Set 集合
Set 对象是值的集合,可以按照插入的顺序迭代它的元素。
Set 集合中的元素只会出现一次,即Set集合中的元素是唯一的。
'NaN、undefined等值允许被存储在Set集合中‘
'NaN值在set集合中被认为是相等的'
let set = new Set([NaN,NaN,undefined,undefined,null,null])
===============================================================
Set 集合中存储复杂数据类型(数组、对象及函数等)
多个空数组和空对象、多个函数 表示多个值
let set = new Set([[],[],{},{},function(){},function(){}])
console.log(set)
作用:和数组类似,和数组不同的是不能存放重复的元素
应用场景:数组去重
Set测试,例如:
let set1=new Set([10,20,30,10,20,40])
console.log(set1)
-----------------------------------------------------------------------------------------
数组去重,例如:
let arr=[1,2,3,1,2,4,5]
let set2=new Set(arr)
console.log(set2)
let newArr=[...set2]
console.log(newArr)
Set 集合中的属性与方法
1、size 属性用于返回Set对象值的个数
例如:
let set = new Set([1,2,3,4,5])
console.log(set.size)
/* 5 */
====================================================================
2、操作Set集合的方法
(1)add(value)
在Set集合尾部添加一个元素,返回该Set对象
(2)delete(value)
从Set集合中删除指定的元素。返回布尔值,表示是否删除成功
(3)has(value)
检索Set集合是否包含指定的元素。返回布尔值,表示是否包含
(4)clear()
清除Set集合中所有元素,没有返回值
==================================================================
3、遍历Set集合的方法
(1)values()
返回一个新的迭代器对象(可遍历),该对象包含Set集合中的按插入顺序排列的所有元素的值
let set = new Set([1,2,3,4,5])
let values = set.values()
for (let i of values) {
console.log(i)
}
/* 1 2 3 4 5 */
'没有length属性值,常规的循环语句无法使用'
'只能使用for...of进行遍历'
(2)keys()
与values()方法相同
let set = new Set([1,2,3,4,5])
let keys = set.keys()
for (let i of keys) {
console.log(i)
}
/* 1 2 3 4 5 */
(3)entries()
返回一个新的迭代器对象,该对象包含Set集合中的按插入顺序排列的所有元素的值的[value,value]数组
let set = new Set([1,2,3,4,5])
let entries = set.entries()
for (let i of entries) {
console.log(i)
}
/*
[ 1, 1 ]
[ 2, 2 ]
[ 3, 3 ]
[ 4, 4 ]
[ 5, 5 ]
*/
(4)forEach(value,key,set)
*value:值
*key:键
*set集合
按照插入顺序,为Set集合中每一个元素调一次callback函数
let set = new Set([1,2,3,4,5])
set.forEach((value,key,set) => {
console.log(key,value,set)
})
/*
1 1 Set { 1, 2, 3, 4, 5 }
2 2 Set { 1, 2, 3, 4, 5 }
3 3 Set { 1, 2, 3, 4, 5 }
4 4 Set { 1, 2, 3, 4, 5 }
5 5 Set { 1, 2, 3, 4, 5 }
*/
/*
以上结果可以看出,在set集合中key和value是相等的
*/
Set集合与Array对比
1、数组中用于判断元素是否存在的indexOf() 效率低下 (不如Set集合中has() )
2、Set对象允许根据值删除元素,而数组中必须使用基于下标的splice()
3、数组的indexOf()方法无法找到NaN值,而Set集合可以
4、Set对象存储不重复的值,所以不需要手动处理包含重复值的情况
WeakSet 集合
WeakSet 对象是一些对象的集合,并且其中每个对象值只能出现一次
WeakSet集合与Set集合区别有俩点:
*只能存放对象引用,不能存放值。而Set集合可以
*存储的对象值都是被弱引用的。若没有其他变量或属性引用这个对象值,则这个对象值会被当成垃圾回收掉。因此,WeakSet无法被枚举,无法拿到WeakSet集合包含的所有元素
WeakSet集合提供的方法和Set集合中一样,如下:
(1)add(value)
在WeakSet集合尾部添加一个元素,返回该WeakSet对象
(2)delete(value)
从WeakSet集合中删除指定的元素。返回布尔值,表示是否删除成功
(3)has(value)
检索WeakSet集合是否包含指定的元素。返回布尔值,表示是否包含
(4)clear()
清除WeakSet集合中所有元素,没有返回值
Map 集合
是键值对的集合。任何值都可以作为Map 集合中的键值对。
可以按照插入的顺序迭代它的元素。
与Set集合的操作方法和遍历方法相同,都继承与同一原型
与Set集合唯一的区别是:set(key,value)
例如:
let map = new Map()
let obj = {
name : 'jine',
age : 22,
printf: ()=>{
console.log("yes")
}
}
map.set('name',obj.name)
map.set('age',obj.age)
map.set('fun',obj.printf)
console.log(map)
Map集合与Object对比
Object 的键均为String类型,但在Map里键可以是任意类型
必须手动计算Object的尺寸,但可以很容易获取使用Map迭代尺寸
Map的遍历遵循元素的插入顺序
Object 有原型,所以映射中有一些缺省的键
Promise (解决回调地狱)
Promise 概述
Promise是异步编程的一种解决方案,比传统的解决方案一回调函数和事件一更合理和更强大。
它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise,简单说就是一个容器, 里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
Promise 提供统一的API,各种异步操作都可以用同样的方法进行处理。
Promise 对象代表一个异步操作:
有三种状态:pending(进行中)、fulfilled (已成功)和rejected (已失败)。
Promise对象的状态改变,只有两种可能:
1、成功:从pending变为fulfilled
2、失败:从pending变为rejected。
只有异步操作的结果,可以决定当前是哪一种状态, 任何其他操作都无法改变这个状态。
这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
ajxa和axios也会返回promise对象(有些方法自身便会返回promise对象,不用去手写promise对象)
Promise 语法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例
Promise 构造函数接受一个函数作为参数,
这个作为参数的函数,也有俩个参数,该函数的俩个参数分别是resolve和reject
这俩个参数也是函数,由JavaScript 引擎提供,不用自己部署
异步操作成功后,调用resolve()方法,其内部调用了then()里面的第一个参数函数
异步操作失败后,调用reject()方法,其内部调用了then()里面的第二个参数函数
例如:
const fs=require('fs')
let p=new Promise((resolve,reject)=>{
fs.readFile(`${__dirname}/file/aa.txt`,'utf-8',(err,data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
p.then((data)=>{
console.log(data)
},(err)=>{
console.log(err)
})
-----------------------------------------------------------------------------------------当然也可以为其封装一个函数,以后调用时再执行,例如:
function getPromise(){
return new Promise((resolve,reject)=>{
fs.readFile(`${__dirname}/file/aa.txt`,'utf-8',(err,data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
getPromise().then((data)=>{
console.log(data)
},(err)=>{
console.log(err)
})
回调地狱概述
假如需求是依次去执行异步代码(异步操作无顺序),而且是顺序执行
那么做法是在异步请求成功后的回调函数中,再去执行下一个异步请求
但是会出现回调地狱,例如:
const fs=require('fs')
fs.readFile(`${__dirname}/file/a.txt`,'utf-8',(err,data)=>{
if(err){
console.log(err);
}else{
console.log(data);
fs.readFile(`${__dirname}/file/b.txt`,'utf-8',(err,data)=>{
if(err){
console.log(err);
}else{
console.log(data);
fs.readFile(`${__dirname}/file/c.txt`,'utf-8',(err,data)=>{
if(err){
console.log(err);
}else{
console.log(data);
}
})
}
})
}
})
解决回调地狱
const fs=require('fs')
function getPromise(filename){
return new Promise((resolve,reject)=>{
fs.readFile(`${__dirname}/file/${filename}.txt`,'utf-8',(err,data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
getPromise('a').then((data)=>{
console.log(data)
return getPromise('b')
},(err)=>{
console.log(err)
}).then((data)=>{
console.log(data)
return getPromise('c')
}).then((data)=>{
console.log(data)
},(err)=>{
console.log(err)
})
Promise 方法补充
catch()
抓取错误信息,这样在每个文件后面省略了 (err)=>{console.log(err)}
只需要再最后写个文件后面 catch((err)=>{console.log(err)})
例如:
const fs=require('fs')
function getPromise(filename){
return new Promise((resolve,reject)=>{
fs.readFile(`${__dirname}/file/${filename}.txt`,'utf-8',(err,data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
getPromise('a').then((data)=>{
console.log(data)
return getPromise('b1')
}).then((data)=>{
console.log(data)
return getPromise('c')
}).then((data)=>{
console.log(data)
}).catch((err)=>{
console.log(err)
})
finally()
返回一个Promise对象,在执行 then() 和 catch() 后,都会执行finally指定的回调函数。这样可以避免同样的语句在then() 和 catch() 中各写一次的情况。
'finally() 是ES2018 引入的标准'
all()
用于将多个promise实例,包装成一个新的promise实例
所有promise对象执行成功,all才会成功(相对于并且)
const fs=require('fs')
function getPromise(filename){
return new Promise((resolve,reject)=>{
fs.readFile(`${__dirname}/file/${filename}.txt`,'utf-8',(err,data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
let p1=getPromise('a')
let p2=getPromise('b')
let p3=getPromise('c')
let pAll=Promise.all([p1,p2,p3])
pAll.then((data)=>{
console.log(data)
})
race()
此方法和all()类似,但是只要一个promise执行成功,那么race就成功(相当于或者)
例如:
const fs=require('fs')
function getPromise(filename){
return new Promise((resolve,reject)=>{
fs.readFile(`${__dirname}/file/${filename}.txt`,'utf-8',(err,data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
let p1=getPromise('a')
let p2=getPromise('b')
let p3=getPromise('c')
let pAll=Promise.race([p1,p2,p3])
pAll.then((data)=>{
console.log(data)
})
async 函数
ES7 标准引入。该函数主要目的简化使用Promise异步调用的操作,并对一组Promise 执行某些操作。Promise类似于结构化回调,async/await 类似于组合生成器和Promise。
1、async 关键字
在函数前面 加上关键字 async ,js底层便自动给这个函数生成一个Promise
例如:
let fun = async () => {return 'ok'}
fun().then(value => console.log(value))
'注意:上述代码,return语句中没有await表达式,因此异步函数的返回值被隐式的传递给Promise.resolve'
-------------------------------------------------------------------
2、await 关键字
await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。
返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
若 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。
let promise = new Promise((resolve,rejects) => {
return resolve('Promise is ok ')
})
console.log('同步代码1')
let fun = async () => {
console.log('start...')
let result1 = await 'async中的同步代码'
let result2 = await promise
console.log(result1)
console.log(result2)
console.log('end...')
}
fun()
console.log('同步代码2')
-------------------------------------------------------------------------------------
3、利用 try...catch 捕获 Promise中的rejects
let promise = status =>{
return new Promise((resolve,rejects) => {
if(status){
return resolve('Promise is ok ')
}
return rejects('Promise is failed')
})
}
let fun = async () => {
console.log('start...')
try{
let result = await promise(false)
}catch(err){
console.log(err)
}
}
fun()
Symbol 类型
概述
ES6 新增第六种原始类型 Symbol(符号)类型。
Symbol 类型是唯一的,并且可以用来作为Object的key值
'注意:Symbol 类型是原始类型,是不能通过 new Symbol() 来创建对象'
例如:
let symbol = Symbol('jine')
let sym = Symbol('jine')
console.log(symbol)
console.log(sym)
console.log(sym===symbol)
Symbol.for()
该方法会根据给定的键Key,来从运行时的symbol注册表中找到对应的symbol。若找到,则返回,否则,新建一个与该键关联的symbol,并放入全局symbol注册表中。
let sym = Symbol.for('jine')
console.log(sym)
Symbols 在 for...in 迭代中不可枚举。
可以使用Object.getOwnPropertySymbols() 得到
迭代器
ES5 规范表示集合的数据结构有数组(Array)和对象(Object)
ES6 规范又新增了 Set和Map 俩种集合。
这样在JavaScript中就有四种集合,需要一种统一的机制进行操作。
迭代器便是这种机制,为各种不同的数据结构提供统一的访问机制。
在任何数据结构只要部署 Iterator 接口,便可以完成遍历操作
迭代器具有三种作用:
* 为各种数据结构,提供一个统一的、简便的访问接口
* 使得数据结构的成员能够按某种次序排列
* ES6 新增了 for...of 循环语句,用于遍历迭代器
在 JavaScript中迭代器是一个对象,该对象提供next()方法用于返回序列中的下一项,该方法返回包含 done 和 value 俩个属性的对象
以下便是迭代器,例如:
let fun = arr => {
let index = 0;
return {
next : () => {
return index < arr.length ? {
done : false ,
value : arr[index++]
}:{
done : true
}
}
}
}
let arr = [1,2,3,4,5,6]
let results = fun(arr)
for (let i=0 ; i<arr.length ; i++){
console.log(results.next())
}
Iterator
一种数据只要部署了Iterator 接口 ,就可以称这种数据结构是'可遍历的'。
ES6 规范规定默认的Iterator 接口部署在数据结构的 Symbol:iterator 属性。 (一个数据结构只要具有Symbol.iterator 属性,就可以认为是'可遍历的')
Symbol.iterator 属性本身是一个函数,就可以当成数据结构默认的迭代器生成函数。执行这个函数就会返回一个迭代器。
for…of
ES6 引入 for...of 语句用于遍历迭代器。
一个数据结构只要部署了Symbol.iterator 属性,就被视为具有iterator接口,就可以用for...of 循环遍历它的成员。
for(variable of iterable) {
}
* variable:在每次迭代中,将不同属性的值分配给变量
* iterable: 被迭代枚举其属性的对象
for…of 和 forEach 区别
* forEach() 方法无法跳出循环。break语句和continue语句无效。
* for ... of 语句可以,而且还能配合 return 语句
for…of 和 for…in 区别
* for...in 不仅遍历自身,还会遍历手动添加的,甚至包括原型链的
* 若用于遍历数组的话,遍历得到的键名为字符串类型的数字值
生成器
Generator 函数
虽然可以自定义一个迭代器,但自定义的迭代器需要显式地维护其内部状态。而生成器提供了另一个强大的选择,其提供了允许定义一个包含自有迭代算法的函数,同时可以自动维护其内部状态。
Generator函数可以作为生成一个迭代器的特殊函数,该函数被调用时返回一个Generator对象,该对象是符合可迭代协议和迭代器协议的。
Generator 函数与普通函数区别:
* function* 这种声明方式会定义一个生成器函数,它返回一个Generator对象
* yield 关键字用来暂停和恢复一个生成器函数
例如:
function * fun (arry){
for (let i=0; i<arry.length; i++){
yield arr[i]
}
}
let arr = ['jine','age','hha']
let result = fun(arr)
console.log(result.next())
console.log(result.next())
console.log(result.next())
yield * 表达式
用于委托给另一个Generator 或可迭代对象
yield * [[expression]]
* expression: 返回一个可迭代对象的表达式
例如:
function * funny (){
yield 'funny'
yield 'ok'
}
// 定义一个生成器函数
function * fun (arry){
for (let i=0; i<arry.length; i++){
yield arr[i]
yield * funny()
}
}
let arr = ['jine','age','hha']
// 生成器函数调用返回生成器对象
let result = fun(arr)
console.log(result.next())
console.log(result.next())
console.log(result.next())
console.log(result.next())
console.log(result.next())
console.log(result.next())
console.log(result.next())
console.log(result.next())
console.log(result.next())
/*
{ value: 'jine', done: false }
{ value: 'funny', done: false }
{ value: 'ok', done: false }
{ value: 'age', done: false }
{ value: 'funny', done: false }
{ value: 'ok', done: false }
{ value: 'hha', done: false }
{ value: 'funny', done: false }
{ value: 'ok', done: false }
*/
Generator 对象的方法
1、 next()
返回一个包含属性 done 和 value 的对象。该方法也可以接受一个参数用以向生成器传值
2、return()
返回给定的值并结束生成器
3、throw()
用于向生成器抛出异常,并恢复生成器的执行,返回带有done和value俩个属性的对象
这三个方法本质的作用都是让Generator 函数恢复执行,并且使用不同的语句替换yield表达式
Class 关键字
类
ES6 提供了更接近传统开发语言的写法,引入了类(Class)的概念。
类作为对象的模板,只是一个语法糖。
Class关键字只是让对象原型的写法更加清晰、更加面向对象编程的语法而已。
类的声明
有俩种方式:
1、类的声明方式
Class Name {
}
2、类的表达式
const MyClass = class [classname]{
}
'注意:声明类时,不允许声明提前,也不允许重复声明'
Constructor
构造器,是用于创建和初始化类中创建对象的一种特殊方法。
* 在一个类中只能有一个名为 'Constructor' 的特殊方法。一个类中多次出现将会抛出SyntaxError错误。
* 在一个构造方法中可以使用 super 关键字来调用父类的构造方法
* 若没有显示指定构造方法,则会添加默认的constructor方法
例如:
class People {
constructor (){
this.name = 'jine'
this.age = 22
this.print = () => {
console.log(this.name,this.age)
}
}
}
let jine = new People()
jine.print()
class Person {
constructor (name,age) {
this.name = name
this.age = age
this.printf = () => {
console.log(this.name,this.age)
}
}
}
let people = new Person('jine',22)
people.printf()
getter 与 setter
class People {
constructor (name,age) {
this.name = name
this.age = age
this.printf = () => {
console.log(this.name,this.age)
}
}
}
let jine = new People ('jine',22)
jine.printf()
jine.name = 'ren'
jine.age = 18
jine.printf()
static
用 static 关键字为一个类定义的静态方法。不会在类的实例上被调用,相反被类本身调用
'注意:在static中的this,指向的是类,而不是当前对象'
class People {
constructor (name,age) {
this.name = name
this.age = age
}
static print(name,age){
console.log(name,age)
}
static printf(name,age){
this.print(name,age)
}
}
let jine = new People('jine',22)
console.log(jine)
People.printf('static',18)
类的继承
extends 关键字用于类声明或者类表达式中,以创建一个类,该类作为另一个类的子类
class Person {
constructor (){
this.name = 'jine'
}
}
class People extends Person {
constructor (){
super()
this.age = 2
}
}
'注意:继承的prototype 必须是一个 Object 或者 null '
'注意:在子类的构造器中需要加 super() 关键字 ,此时super()关键字的含义是:指向当前子类的父类构造器'
====================================================================
类继承不只是可以继承constructor中的内容,是全部继承下来(包括普通方法和静态方法)
class Person {
constructor (name){
this.name = name
this.printf = () => {
console.log(this.name)
}
}
fun(){
console.log('fun() ... ')
}
static staMethod(){
console.log('staMethod() ... ')
}
}
class People extends Person {
constructor () {
this.age =22
super()
}
}
let jine = new Person('jine')
console.log(jine)
/* Person { name: 'jine', printf: [Function] } */
jine.printf()
/* jine */
jine.fun()
/* fun() ... */
People.staMethod()
/* staMethod() ... */
/*
结论:以上结果,可看出,子类继承于父类的,不只是constructor() 中的属性和方法,其普通方法和静态方法也可以继承下来
*/
super关键字
1、在constructor中使用 super() 表示:指向当前类的父类构造器。
'注意:super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)'
例如:
class Person {
constructor (name) {
this.name = name
this.printf = () => {
console.log(this.name)
}
}
}
class People extends Person {
constructor (name) {
super(name)
}
}
let jine = new People('jine')
jine.printf()
----------------------------------------------------------------
2、super 作为对象时,在普通方法中,指向父类的原型对象。在静态方法中,指向父类(不是父类的实例)。
'注意: 由于super指向父类的原型对象,所以定义在父类实例方法上的方法和属性,是无法通过super 调用的。'
例如:
class A {
p() {
return 2;
}
}
class B extends A {
fun(){
console.log(super.p());
}
}
let b = new B();
console.log(A.prototype.p())
b.fun()
'需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。'
例如:
class A {
constructor(){
this.name = 'jine'
}
}
A.prototype.age = 22
class B extends A {
printfName(){
console.log(super.name)
}
printfAge(){
console.log(super.age)
}
}
let b = new B();
b.printfName()
b.printfAge()
------------------------------------------------------------
ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。
class A {
constructor() {
this.x = 1;
}
print() {
console.log(this.x);
}
}
class B extends A {
constructor() {
super();
this.x = 2;
}
m() {
super.print();
}
}
let b = new B();
b.m()
'上面代码中,super.print()虽然调用的是A.prototype.print(),但是A.prototype.print()内部的this指向子类B的实例,导致输出的是2,而不是1。也就是说,实际上执行的是super.print.call(this)。'
其他的再多扩展无非离不开super的俩种情况,更多的super说明参考阮一峰的第22.Class的继承中的super关键字
Proxy(代理)
概述
Proxy 也就是代理,可以帮助我们完成很多事情,例如对数据的处理,对构造函数的处理,对数据的验证,说白了,就是在我们访问对象前添加了一层拦截,可以过滤很多操作,而这些过滤,由你来定义。
'此Proxy内容作为了解使用,只需要知道语法和大体机制,暂不深究'
语法
let pro= new Proxy(target, handler);
* target:需要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
* handler属性是当执行一个操作时定义代理的行为的函数(可以理解为某种触发器),具体的handler相关函数请查阅官网
get行为例子:
let obj = {
name : 'jine'
}
let pro = new Proxy(obj,{
get(target,key){
if(key in target){
return target[key]
}else{
throw ReferenceError('此对象属性不存在')
}
},
fun(){
console.log("aaa")
}
})
console.log(pro.name)
console.log(pro.age)
console.log(pro.fun())
set行为,例如:
let obj = {}
let pro = new Proxy(obj,{
get(target,key){
if(key in target){
return target[key]
}else{
return new ReferenceError('此对象中没有此属性')
}
},
set(target,key,value){
if(key === 'age' && value > 22) {
return new ReferenceError('输入年龄太大')
}else{
target[key] = value
return '添加成功'
}
}
})
pro.age = 18
pro.age = 23
console.log(pro.age)
Reflect(反射)
概述
'此内容作为了解,有个大概体系即可,此内容来自 阮一峰ES6'
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。
(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
Proxy(target, {
set: function(target, name, value, receiver) {
var success = Reflect.set(target, name, value, receiver);
if (success) {
console.log('property ' + name + ' on ' + target + ' set to ' + value);
}
return success;
}
});
上面代码中,Proxy方法拦截target对象的属性赋值行为。
它采用Reflect.set方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。
有了Reflect对象以后,很多操作会更易读。
Function.prototype.apply.call(Math.floor, undefined, [1.75])
Reflect.apply(Math.floor, undefined, [1.75])
Reflect 对象中的方法的作用,大部分与Object对象的同名方法的作用都是相同的,而且它与Proxy对象的方法是一一对应的。
模块化
概述
Module 模块
服务器和浏览器都会支持 ES6 模块格式。
不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
export命令:用于规定模块对外的接口
➢外部能够读取模块内部的某个变量、函数、类
➢使用as关键字重命名
➢该命令可以出现在模块的任何位置,只要处于模块顶层即可,除了块作用域内(import也是如此)。
import命令:用于输入其它模块提供的功能
➢变量、函数
➢使用as关键字
➢输入的变量都是只读的
➢Import命令具有提升效果
浏览器加载ES6模块
<script type='module' src='xxx.js'> </script>
了解
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
上面代码中,<script>标签打开defer或async属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。
defer与async的区别是:defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。
览器对于带有type="module"的<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。
如果网页有多个<script type="module">,它们会按照在页面出现的顺序依次执行。
<script>标签的async属性也可以打开,这时只要加载完成,渲染引擎就会中断渲染立即执行。执行完成后,再恢复渲染。
<script type="module" src="./foo.js" async></script>
一旦使用了async属性,<script type="module">就不会按照在页面出现的顺序执行,而是只要该模块加载完成,就执行该模块