什么是ES6?
ECMAScript 6.0 是继ECMAScript 5.1 之后 JavaScript 语言的下一代标准,发布在2015年6月,它的目标,是使得JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
一、let、const命令
let、const与var的区别?
a. let、const有块级作用域的概念,只要是定义的变量,就是一个块级作用域;var只有在函数中才有作用域;
b. let、const不允许重复声明;
c. let和var声明的变量,都可以再修改,而const定义的是常量,值是不可以修改的(对象除外,对象的属性可以修改)
作用域的概念:
ES5有两个作用域:全局和局部
ES6有了块作用域的概念:块作用域,通俗的理解,就是一个大括号,就是一个块作用域;
let声明
案例:
使用let声明的变量不可重复,如下
function test2(){
let a = 1;
let a = 2;
}
test2()
这样声明会报下图错误:
这个意思是,a变量重复声明了;
const声明
const定义时,必须要给初始值,否则下面再重新赋值会报错,因为const定义的值不可以修改;
const声明的是常量,不可以修改(一般的值是不可以修改的,如果是对象格式是可以修改的,比如给已创建的对象中添加属性是可以的),并且声明时必须要赋值;
用途:为了防止意外修改变量,比如,引入库名,组件名等
二、解构赋值
什么是解构赋值?
es6入门中的解释:ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构;
模式匹配:
左边和右边要的数据格式要一样;
解构赋值的分类
这里重点理解:数组解构与对象解构
如果解构赋值没有找到对应的赋值,这时默认是undefined
数组解构赋值:
使用场景
对象解构赋值
在对象中的解构赋值与数据的顺序是无关的,比如:
let {a,b,c} = {b:10,a=11,c=12}
console.log(a); // 11
上面的json格式解构赋值时,key名要与data数据中的key名一致;否则找不到对应的数据,会显示undefined;
解构赋值给默认值
语法:
let {time:12,id=0} = {}
用途:
比如在写运动框架时,需要对传过来的参数加默认值;
ES5写法:
function move(obj,json,options){
options = options || {};
options.time = options.time || 300;
}
ES6解构赋值写法:
function move(obj,josn,{time=300}={}){
}
三、正则扩展
新增的特性
ES5与ES6写法对比
ES6增加U和Y的修饰符
U 修饰符
四、字符扩展
字符串新增特性
字符串中新增了几个方法,是es7预案的,需要下载补丁才会支持,否则会报错,所以在当前项目下,安装babel-polyfill库,如图:
安装成功后,需要在index.js入口文件引入这个库,就可以进行使用了,如图:
a).字符Unicode表示法
b).codPointAt()使用方法
c).includes(str)、startsWith(str)、endsWith(str)方法的使用
d).repeat(number)方法的使用
e).模版字符串
f).padStart(s1,s2)、padEnd(s1,s2)方法,ES7提案的API,很重要!!!
j).标签模版
String.raw
五.数值扩展
数值处理新增特性:
1.新增方法 ;2.方法调整
二进制与八进制
Number.isFinite()
Number.isInteger()
Number.isSafeInteger()、Number.MAX_SAFE_INTEGER、Number.MIN_SAFE_INTEGER
Math.trunc()
Math.sign()
Math.cbrt()
六.数组扩展
ES5方法回顾:
1.遍历数组的方法
说明:通过遍历数组可以得到下标和下标对应的值;
forEach()、map()、filter()、every()、some()
forEach()使用方法
let arr =[1,3,4,5,6,7]
let newArr = arr.forEach(function(item,index){
//可以得到值和下标
console.log(item,index)
});
console.log(newArr) // undefined
forEach的返回值为 undefined
map()使用方法
let arr =[1,3,4]
let newArr = arr.map(function(item,index){
//可以得到值和下标
console.log(item,index)
});
console.log(newArr) // undefined
map的返回值为[ undefined , undefined , undefined]
map会把返回的值放到一个新的数组中,默认return 是undefined,所以上面数组有几项,就返回几个undefined。
所以通过这一点,就可以把循环得到的值,进行计算,通过return 再返回到新数组中;
具体实例如下:
let arr =[1,3,4]
let newArr = arr.map(function(item,index){
//将循环的每一项乘以2后,再返回到新数组中
return item * 2;
});
console.log(newArr);//[2,6,8] 得到的是计算后的新数组
filter()使用方法
filter也可以循环数组,得到下标和值,但是更多作为过滤使用;具体如下:
//过滤出小于3的数
let arr = [1,2,3,4];
let newArr = arr.filter(function(item,index){
return item < 3;
});
//当不写return 时返回的是一个空数组
console.log(newArr); // [] 得到一个空数组
filter的返回值,返回的是回调函数中为true的那一项
如果上案例,return item < 3 ,这时返回的是小于3的数;
ES6新方法:
数组复制的方法:
1) for循环;
2) Array.from(arr);//数组新增from()方法
3) let arr2 = [...arr];//展开运算符
Array.of()
Array.from()
fill()
keys()、values()、entries()
copyWithin(s1,s2,s3)
find()、findIndex()
includes(num)
七.函数扩展
ES5中参数是没有默认值的;
函数参数默认值
根据以上es5中默认值的情况来看,如果参数是0,会被读成false,导致运算出现问题;
所以es6扩展了直接给默认值的写法,如下:
关于函数作用域
rest参数(...)
扩展运算符(...)
1.数组的扩展:将一个数组转为逗号分隔的参数序列;
说明:是把数组中的值,以逗号分隔,把每个值展开;不是转成字符串;
例:找到数组中的最大值
let arr =[1,5,7,9,3,55];
//原始做法:
console.log(Math.max(1,5,7,9,3,55));//55
//使用扩展运算符的做法:
console.log(Math.max(...arr));//55
2.对象的扩展:用于取出对象中所有可遍历的属性,拷到当前对象中;
例:
let obj1 ={a:1,b:2};
let obj2 = {
...obj1 ,
c:3 //这里也可以写自己的属性
//obj1 中有a属性,那么如果obj2中再写一个a属于,就会把obj1中已扩展过来的a属于的值改变
a:66 //这样再打印obj2的a属性就是66
};
说明以上这样的做法,相当于浅拷贝;
箭头函数
注意事项:
1.箭头函数中没有arguments对象,如果要用,可以用rest参数代替;
2.不能当作构造函数,不可以使用new命令,否则会抛出错误(Fn is not constructor);
3.不能当作Generator函数使用;
this指向
es5普通函数中的this
1. this表示谁调用的就是谁;
2. 默认情况,在严格模式下返回是undefined,非严格模式下是window;
3. 可以用call,apply,bind改变this的指向;
es6函数中的this
1. 箭头函数体内没有自己的this对象,所以在使用的时候,
其内部的this就是定义时所在环境的对象,而不是使用时所在环境的对象;
2. 不可以用call,apply,bind改变this的指向;
3. 箭头函数中没有arguments对象,如果要用,可以用rest参数代替;
普通函数与箭头函数this指向示例
尾调用
八.对象扩展
*这里的扩展指的Object对象,不是指类生成的对象
简洁表示法
属性表达式
新增API
Object.is(str1,str2)
判断两个字符串是否相等
str1是否与str2相等,这个判断与es5中的 '===' 判断是一样的
console.log(Object.is('a','a'));//true
//数组也是引用类型,所以这里是false
console.log(Object.is([],[]));//false
Object.assign(str1,str2):拷贝
str1:要拷贝到这个对象上;
str2:把str2对象拷贝到str1对象上;
注意点:
1. 这种拷贝的属性是有限制的,属于浅拷贝;
2. 只拷贝对象自身上的属性,如果对象有继承和不可枚举的属性,是不可以拷贝的;
3. 静态方法,返回值是str1对象;
console.log(Object.assign({a:'a'},{b:'b'}));//{a: "a", b: "b"}
Object.entries(obj)
获取对象的key和value的值,传入要拷贝的对象
let test ={a:123,b:456};
for(let [key,value] of Object.entries(test)){
console.log( [key,value] );//["a", 123] ["b", 456]
}
扩展运算符
let arr =[1,2,3,4];
console.log(...arr);//1 2 3 4
//下面这个是ES7提的预案,暂时没有支持方法
let {a,b,...c}={a:'test',b:'kill',c:'ddd',d:'ccc'};//这样写页面会报语法错误
Symbol数据类型
Symbol的概念:提供独一无二的值,声名的值永远不会重复和相等;
声明方法
方法一:这种是直接声名Symbol值,值一定不会相等;
leta1 =Symbol();
leta2 =Symbol();
console.log(a1===a2);//false
方法二:使用Symbol.for(str)方法声名;
str:是Symbol中的key值,有这个key值,for方法在声名这个独一无二的变量时,会先检查key值是不是在全局注册过,如果注册过返回注册时候的值,如果没有注册过,就会声成一个独一无二的值;
这种声名的好处:参数中是key名,相当于给Symbol起了个名字,方便之后的调用;
let a3 = Symbol.for('a3');
let a4 = Symbol.for('a3');
true 就表示 a3 和 a4 的值是相等的,因为 a4 的Symbol的key值是已经注册过了,声名 a3 时注册的,
所以 a3 的这个key值,存在的变量就是 a3,所以两者比较是true
console.log( a3 === a4 );//true
Symbol的作用
获取Symbol的属性
九.数据结构
Set()
定义方法
1> 通过new方式来创建 new Set()
2> new Set(arr) 可以向Set中加入一个数组结构的数据,会自动变成Set类型的数据
Set 中的一些操作方法
add(str):向Set中添加成员;
delete(str):删除Set中str元素,返回删除后的Set集合
clear():清空所有Set中的元素,返回清空后的Set集合
has(str):检查Set中有没有str元素,返回boolean,有true,没有false
size属性:获取Set的成员数量(长度);
Set的特性
Set中的元素是不可以重复的,如果元素中有重复,会只显示不重复的元素,重复元素会自动去掉,利用这个特性可以对数组进行去重。
let arr =[1,1,1,2,2,3,3];
let list =newSet(arr);
console.log(list);//{1, 2, 3}
在去重的时候需要注意,Set只去重同数据类型的数据,数组中不同数据类型是不会强制类型转换的;
let arr2 =[1,'1',2,2,3,3];
let list2 =newSet(arr2);
//通过打印出的元素可以看出没有类型转换
console.log(list2);// {1, "1", 2, 3}
Set实例的遍历(读取里面的元素)
使用keys()、values()、entries()、forEach、直接使用let of来遍历里面的元素
WeakSet()
WeakSet与Set的区别:
1. 支持的数据类型不一样,WeakSet的元素只能是对象(boolean,String等这些都不可以);
2. WeakSet中的对象是一个弱引用,不会去检测这个对象有没有在其它地方用过, 不会跟GC挂勾,就是说,在WeakSet中添加了一个对象, 这个对象不是值拷过来,只是地址的引用,而且也不会去检测这个地址是不是返回GC回收了;
3. 有些Set的属性和方法WeakSet是没有的(clear()、size属性、不能遍历);
WeakSet方法:
1. add(val):添加成员
2. delete(val):删除成员
3. has(val):判断val成员是否存在,返回boolean类型,true存在,反之false
使用案例:
Map()对象
Map的方法:
1. set(key,value):添加元素,key,value格式的
2. get(key):根据key名,找对应的value值
3. delete(key):删除key名对应的值
4. clear():清空map
5. size属性:获取map中的成员数量
Map的循环:
不能使用for ..in,没有效果;需要使用for ...of来循环
例如:
let map = new Map();
map.set('a','aaa');
map.set('b','bbb');
for(let key of map){
console.log(key);//a,aaa, b,bbb
}
注意:这里of 后面直接写map 得到的是key和value,因为这里默认是map.entries();
也可以是只循环出key 或者 value
写法如下:
for(let key of map.keys()){ //只循环key
console.log(key);//a b
}
for(let value of map.values()){ //只循环value
console.log(value);//aaa bbb
}
Map的两种写法:
WeakMap()
WeakMap与Map的区别:
1. 支持的数据类型不一样,WeakSet的元素只能是对象(boolean,String等这些都不可以);
2. WeakSet中的对象是一个弱引用,不会去检测这个对象有没有在其它地方用过,不会跟GC挂勾,就是说,在WeakSet中添加了一个对象,
这个对象不是值拷过来,只是地址的引用,而且也不会去检测这个地址是不是返回GC回收了;
3. 有些Set的属性和方法WeakSet是没有的(clear()、size属性、不能遍历);
WeakMap的方法:
1. add(val):添加成员
2. delete(val):删除成员
3. has(val):判断val成员是否存在,返回boolean类型,true存在,反之false
let weakMap = new WeakMap();
let o ={};
weakMap.set(0,123);
console.log(weakMap.get(o));//123
数据结构对比
说明:在es5中,经常使用的数据结构就是Array和object来存储;es6中,新增了Map和Set,那么什么时候使用这些类似,下面就做一个对比;
数据结构横向对比,增,查,改,删
map和array的对比
增加成员
查询成员
修改成员
删除成员
set和array的对比
增加成员
查询成员
修改成员
删除成员
Map,Set与Object对比
增加成员
查询成员
修改成员
删除成员
通过几上对比,提一个建议性的总结,纯属个人看法:
能使用map,不使用数组,如果对数据要求高,数据结构要求存储的唯一性,考虑set,放弃使用object;
Proxy与Reflect
Proxy:代理的意思,作用就是相当于代理商,连接了用户与对象最中间的那一层;
Reflect:反射Object的作用;
这两种的方法是一模一样的,只是生成对象的形式不一样;这里只演示Proxy的方法;
Proxy是通过new的方式;Reflect是直接调用,如Reflect.get这样的写法;
Proxy对象说明:
原始数据对象,通过Proxy对象,对原始数据进行了代理,代理后,会生成一个新的对象,如下案例monitor,之后对这个数据的操作,都是对monitor对象进行操作,最终Proxy都会将操作后的数据,返回给obj。可以理解成,Proxy对obj所有属性有一个读取的作用,可以说是拦截,也可以是代理;
案例:
let obj ={ //原始数据,可以看做是供应商
time:'2017-12-27',
name:'net',
_r:123
};
/*
Proxy(obj,{})
obj:要代理的数据对象;{}:这里面实现这些代理的方法
*/
let monitor =new Proxy(obj,{
/*
get(target,key):拦截(代理)对象属性的读取
target:就是obj这个原始数据中所有的数据;
key:数据对象中的key名,下面的monitor调用哪个属性,
这个key就是哪个属性名
*/
get(target,key){
//将读取到的2017换成2018
return target[key].replace('2017','2018');
},
/*
set(target,key,value):拦截对象设置属性
target:原始数据对象; key:要修改的属性;value:要修改的值;
*/
set(target,key,value){
//这里举例只改key值是name的数据,其它的值不可以修改
if(key==='name'){//当key是name时
//就让name的值,等于value
return target[key] =value;
}else{
//否则返回之前的值
return target[key];
}
},
/*
has(target,key):判断当前对象中,是否有某个属性
拦截key in object操作
*/
has(target,key){
//要只暴露name属性,就是说通过 key in object方法,
//只能判断到name属性是存在的,其它都查不到
if(key==='name'){
return target[key];
}else{
return false;
}
},
/*
deleteProperty(target,key):拦截delete
target:原始数据对象;key:数据中的key值
*/
deleteProperty(target,key){
//这里拦截下划线开头的属性,就可以删除,其它的全部不可以删除
if(key.indexOf('_') >-1){
delete target[key];
return true
}else{
return target[key];
}
},
/*
ownKeys(target):
拦截Object.keys,Object.getOwnPropertySymbols,Object.getOwnPropertyNames,
要获取的属性名。保护指定属性不被以上这些方法获取到
*/
ownKeys(target){
//通过Object.keys把原始key者拿出来,然后再过滤
return Object.keys(target).filter(item=>item !='time')
}
});
//通过打印可以看出,Proxy代理了原始数据,并做出修改,这不会影响到原始数据;
console.log(monitor.time);//2018-12-27
console.log(obj.time);//2017-12-27
monitor.time='2019';
console.log(monitor.time);//2018-12-27 并没有修改成2019
monitor.name='hello';
console.log(monitor.name);//hello 这里就改变了,说明代理的set方法起了作用
console.log('name' in monitor);//true 查询到有这个属性
console.log('time' in monitor);//false 没查询到有这个属性,因为限制了查询
delete monitor.time;
console.log(monitor.time);//发现获取到了,说明没有被删除
delete monitor._r;
console.log(monitor); //{time: "2017-12-27", name: "hello"} 发现被删除了,拦截是有作用的
console.log(Object.keys(monitor));// ["name", "_r"] 这里没有time,time被拦截保持了
总结:通过用户拿到的对象,和原始对象,之间是不可以直接操作的,要通过代理,在代理的层面,可以通过不同的业务逻辑来做相应的处理;比如怎么读,怎么写;
实际应用场景:
通过Proxy和Reflect来实现业务解耦的校验模块;比较常用的应用场景;
function validator(target,validator){
return new Proxy(target,{
//这里就是进入到代理了
_validator :validator,
set(target,key,value,proxy){//这里是Proxy对象的set方法
//判断当前目标对象,有没有Key值
if(target.hasOwnProperty(key)){
let va =this._validator[key];//把这个条件,这个条件是封装好的验证方法,拿出来
if(!!va(value)){//如果这个是存在,就把值拿出来
//这个时候,就把代理返回到真实数据中
return Reflect.set(target,key,value,proxy)
}else{//不存在,就抛出异常
throw Error(`不能设置${key}到${value}`)
}
}else{//如果没有就抛出异常
//不存在就把key显示出去
throw Error(`${key} 不存在`)
}
}
})
}
const personValidator = {
//name校验的方法,如果name是string类型,就允许代理修改,否则不允许
name(val){
return typeof val==='string'
},
//age必须是数字,并且大于18
age(val){
return typeof val ==='number' &&val >18;
}
}
//创建一个Person对象
class Person{
constructor(name,age){//构造函数
this.name =name;
this.age =age;
//返回的是一个代理
return validator(this,personValidator)
}
}
//下面就可以实际验证了
const person =new Person('lilei',30);
console.log(person)
十.类与对象
类的基本定义与对象的生成
类的继承
继承传递参数
父类构造函数中的参数,如何传递到子类参数中,并且子类如何替换继承过来的父类参数;
使用super()来实现子类向父类传递参数;
super():不传参数时,表示子类全部使用父类的所有默认参数和值;
super(str1...):传参数时,表示要替换父类的参数,需要替换几个,就传几个;
关于super特别要注意:在继承关系中,子类在传递参数中,使用了super,那么super一定要放在子类
构造函数中的第一行,否则会报错;
getter与setter
静态方法
静态属性
十一.Promise
什么是promise:实际上就是一个对象,主要是用来传递异步操作的数据;
什么是异步:比如,a和b两个函数,a执行完再执行b,在程序上,这个步骤,有两种方式去执行:
一种是通过回调,一种是事件触发的方式;
promise的作用:解决异步操作的问题;
es5的异步写法
ES6的promise写法
连续串行的promise执行
关于串行的问题
关于串行过程,如果在中间某一步出错了,怎么捕获这个错误?
使用promise提供的catch来捕获。
promise两个重要场景
Promise.all()的使用场景
使用promise.all的方法,把多个promise的实例,当做是一个promise实例;
promise.all([p1,p2,p3]):这里传的是一个数组格式,数组传多个promise实例;
注意,当里面多个promise实例状态全部发生改变的时候,才会触发promise.all,返回的新的promise对象,后面才可以使用then等一些方法。
场景一:所有图片加载完再添加到页面中
Promise.race()的使用场景
有三个图片,在三个不同的位置,这个页面需要加载其中一张图片,但不知道这三个图片哪个返回比较快,也不关心哪个比较快,但是知道这三个图片的加载来源,加载出一个来源就可以;所以就是说,先到先得,哪个先加载出来,就显示哪个;
Promise.race():格式,写法和promise.all一样;
race里面的promise实例,哪一个先改变了,就会触发race的实例,剩下的就不管了
十二.Iterator和for...of循环
Iterator接口可以看成是一个遍历器;
Iterator接口的作用:实现不同数据类型之间,统一读取里面的数据;
for...of循环的过程,就是在背后不断的调用Iterator接口来达到这种形式;不同的数据结构通过for...of统一的这种形式,来达到读取不同的数据结构的目标,但是背后的Iterator接口是不同的;
数组循环中for..of与for ..in的区别:for..of循环的是数组中的每一项,for ..in循环的是索引;
Iterator接口写法
简单的Iterator接口写法:[Symbol.iterator]()以方法的形式表示;
接口都有一个next():这个方法是用来查看是否还有数据,返回的是对象类型,固定的有两个属性,value和done,value返回的是数据中的值,done是boolean类型,true表示下面还有数据,false表示没有数据了。
Iterator自定义
Iterator接口也可以用来自定义;
for...of不可以循环对象,可以通过Iterator接口来实现。
十三.Generator
Generator:异步编程的解决文案,也可以说是一个状态机;可以遍历,语法就是一个函数;
异步编程有:回调,promise,这个Generator相对于这两个比较高级一些;
基本定义
Generator返回的就是一个Iterator接口
写法,就是一个function后面紧跟着一个星号,function*(){}
以上得出,Generator调用next()会一直找到函数中的return ,就会结果查找,done返回true;
上面的return 返回的是undefined所以找到这里就会结果查找;
yield
yield语句本身没有返回值,或者每次返回undefined;
next
next是可以带参数的,参数会给上一个yield的值;
实际应用
作为状态机的演示
说明:比如有a b c三种状态描述一个事物,这个事物只存在三种状态a b c,永远没有第四种状态;generator处理这种状态最适合;
async与await
async与await:这两个是generator的语法糖,就是把generator的function* (){}中的星号去掉,在function前面加async,函数中的yield改成await。其它的使用方法全部一样;想执行以下代码,需要加一个babel的插件
实际使用场景
场景一,抽奖
前端如何做抽奖次数的限制,当抽奖次数用完后,提示用户不可以再抽奖。
重点是怎么计算当前还有多少次,之前的做法是设置一个全局变量,来保存当前次数,这样非常不安全,如果对方知道这个次数的变量,会随意修改,这样就无法拦截了;而且尽量不要把数据存放在全局变量中,影响页面性能;
下面使用generator来实现;
场景二,长轮询
长轮询,如何说服务端某一个数据定期变化,前端需要定期去取这个变化的数据;
因为http是无状态的链接,取经常变化的状态,可以使用websocket和长轮询;
因为这个websocket兼容性不是很好,所以一般经常使用的就是长轮询的方法;
之前的长轮询是通过定时器,不断的去取状态,使用generator可以让页面的代码变的更优雅。把相关业务区分开;
十四.Decorator(修饰器)
概念:修饰器是一个函数,用来修改类的行为,修改类的行为,可以理解为扩展类的功能,这个修饰器的修改范围,只能是类的;
需要装一个支持Decorator语法的包:npm install babel-plugin-transform-decorators-legacy --save-dev
安装后,要在.babelrc文件中加一个插件:"plugins":["transform-decorators-legacy"]
修饰器的函数
函数中三个参数说明:
target:要修改的类本身
name:属性名
descriptor:属性的描述对象
现实中实用案例
日志系统(埋点):这里是把埋点的相关逻辑放到了修饰器里面,业务相关逻辑代码写到一个class中;
这样写的好处:
1.把埋点系统抽离出来,成为一个可复用的模块;如后续发现变化,直接改埋点的相关代码,不需要动class中的代码;
2.业务代码也简洁了,好维护了;
总结就是,使用修饰器,可以使代码更有复用性、简洁、好维护!!!
十五.Module模块化
个人理解来讲,一个一个的js文件就是一个个的模块,模块中的变量代码相互引用。
模块化要实现的目标,要解决功能单一;一个模块一个功能;
引用方法
首先在js文件中,把需要暴露的变量导出,如图:
导出方法一:
导出方法二:
在当前文件中,导出后,使用import引入
引入方法:
1、将导出的所有变量都写在大括号中
import {a,test,hello} from "./class/lesson18"
2、大括号里,需要哪个写哪个
import {a} from "./class/lesson18"
3、如果遇到导出的变量非常多,有好几十甚至几百个可以使用 import * as name from url 这种方式
'*':表示文件中所有导出的变量;
'name':是一个别名,另起一个名字,所有的变量都会存到这个对象中;
import * as test18 from "./class/lesson18" // test18就是一个别名,这个对象中存放了所有的变量名