一.let&const命令
1.let命令有以下几个特点:
a).let声明的变量只在其所在的代码块中有效。以下代码中由于当前的i只在本轮有效,所以每次循环的i都是一个新的变量,js内部引擎能记住上一次循环的值,帮助重新生成一个正确值的i。
但是如果将let换成var,由于i是全局变量,最后会输出10。
var a=[];
for(let i=0;i<10;i++){ //父作用域
a[i]=function () { //循环体内部是子作用域,可以使用 let i=...
console.log(i);
}
}
a[6]();
b).不存在变量提升。在变量声明前引用回会报错。
c).暂时性死区。变量声明之前不可以使用变量。let x=x
这样也会报错
d).不允许在同一个作用域内声明相同的变量。
2.块级作用域
a).外层代码块的值不会受到内层代码块同名变量的值影响。
b).es6允许在块级作用域中声明函数,但只能在使用大括号的情况下,且最好使用函数表达式声明(因为函数声明形式具有和var声明一样的变量提升)。
3.const命令
const声明一个不能更改的常量,特点和let命令一样:
a)只在其块级作用域中有效;b)不存在变量提升;c)暂时性死区;d)不允许重复声明;
由于其本质上保存的是变量指向的内存地址,所以用其声明对象时可以修改对象内部值,但是不能重新赋值一个新的对象。
es6声明变量的6种方式:
1)var
2) function
3) let
4) const
5) import
6) class
二.变量的结构赋值
1.数组的解构赋值
只要是可遍历的结构都可以使用,例如数组、Set结构、generator函数…
let [x,y]=[1,2];
let [a,b]=new Set(['aa','bb']);
es6内部使用===严格相等判断一个位置是否有值,结构赋值严格相等于undefined时默认值才会生效
let [x=1]=[undefined]; //x=1
let [y=1]=[null]; //y=null
2.对象的解构赋值
a) 变量必须与属性同名,才会取到值:
let obj={first:'hello',last:'world'};
let {first:ff,last:ll}=obj; //ff:'hello' ll:'world'
b) first是匹配的模式,ff才是真正被赋值的变量。
故let {x,y}={x:11,y:22}
之所以x与y有值是因为它是let {x:x,y:y}={x:11,y:22}
的简写。
c) 在对已声明的变量进行解构赋值时,要避免把大括号写在行首
let x;
{x}={x:11}; //报错,js会将它理解为代码块
({x}={x:11}); //正确
3.字符串的解构赋值
const [a,b,c,d,e]='hello';
4.数值和布尔值的解构赋值
会先将其转为对象,但是由于undefined和null无法转为对象,故会报错:
let {toString:s}=123;
s===Number.prototype.toString //true
let {prop:x}=undefined; //typeError
三.函数的扩展
1.函数参数的默认值
a) 函数的length属性,为定义默认值的参数之前的所有参数个数
(function(a,b=3,c){}).length //1
b) rest参数(…xx),里面搭配的变量是一个数组。这个参数后不能再跟其它参数。
function con(...values){console.log(values);}
con(1,2,3); //[1,2,3]
2.箭头函数
a) 用法
[1,2,3].map(x=>x*x); //与下面等价
[1,2,3].map(function (x){return x*x;})
//当箭头函数的变量涉及到大括号,返回值涉及到对象时,要用圆括号包起来
const full=({a,b})=>({fineA:a,fineB:b});
b) 注意事项
- 箭头函数体内的this对象就是定义时所在的对象,而不是执行时所在的对象
- 箭头函数没有自己的this,不可以当作构造函数,也就是不能用new
- 不能用arguments对象,该对象不存在
- 不能用yield命令,因此不能用作Generator函数
3.绑定this
foo::bar;
等价于bar.bind(foo);
双冒号将左边的上下文环境对象绑定到右边的函数上,返回的是原对象,故可采用链式写法。
4.尾调用优化
a) 原理:
尾调用是函数式编程的一个概念,就是在当前函数的最后一步调用另一个函数,函数在调用的时候会在内存形成一个调用栈,由于尾调用是函数的最后一步操作,所以不需要再保留外层函数的调用帧,会大大节省内存。
b) 尾递归。函数在最后一步调用自己。
平常的Fibonacci数列计算方法在数值偏大时容易发生堆栈溢出,尾递归优化实现:
function fabonacci(n,num1=1,num2=1) {
if(n<=1){return num2}
return fabonacci(n-1,num2,num1+num2);
}
c) 尾调用优化只在严格模式下开启,因为尾调用发生时会改写函数的调用栈,正常模式下内部的arguments和caller变量会失真,而严格模式下这些变量被禁用。
四.数组与对象的扩展
1.数组的扩展方法
扩展方法 | 参数 | exp |
---|---|---|
Array.from() | 类似数组的对象(有length属性的对象) | 可遍历对象 | let ps=document.querySelectorAll('p'); let parr=Array.from(ps); | Array.from(new Set([1,2])) |
数组实例的find()和findIndex | 回调函数 function(value,index,arr) | [1,5,10,15].find(val=>val>9) 直到找到第一个返回值true的成员然后返回该成员 | findIndex一样,但是返回的是成员位置,所有成员都不符合则返回-1。 |
数组实例的entries() keys() values | 实例直接使用,无参数,返回遍历器对象 | for(let [key,val] of [1,2].entries()){} entries()是对键值对遍历,keys()是对键,values()是对值。 |
数组实例的includes() | 与字符串的方法类似,接受一个具体要查找的值,返回布尔值 | [1,2,3].includes(4) //false 对NaN的判断与indexOf()不一样 [NaN].indexOf(NaN) //-1 [NaN].includes(NaN) //true |
另外注意一点,Map和Set数据结构的has方法要与includes区分一下,Map的has方法查找键名,Set的has方法查找键值。
2.对象
a) Object.is()
参数是用来比较的值,功能与严格相等===基本一致,不同的地方有两个:
+0===-0 //true
NaN===NaN //false
Object.is(+0,-)) //false
Object.is(NaN,NaN) //true
b) Object.assign()
用于将原对象的所有可枚举属性复制到目标对象,第一个参数是原对象,后面所有都是目标对象。但是这个实行的是浅拷贝。
在有目标对象即首参数的前提下,如果其它参数有其它类型的值,由于只有字符串的包装对象产生可枚举属性,它会以数组形式被复制,其它类型值都会被忽略。
let v1='ab',v2=true,v3=10;
let obj=Object.assign({},v1,v2,v3); //{"0":"a","1":"b"}
//以下两种方式等价
let z={a:22,c:33};
let obj={...z};
let obj=Object.assign({},z);
五.Set和Map数据结构
1.Set数据结构
- 类似于数组,但是成员值不重复,判断成员的方法类似于严格相等,但是不同的是NaN是等于自身的;
- 参数可以是一个数组或者具有iterable接口的其它数据结构
let set=new Set([1,2,3])
; - 向Set加入值时不会发生类型转换,所以5和’5’是不同的
a) 实例属性和方法(操作方法&遍历方法)
实例的操作方法 | 功能&返回值 |
---|---|
.add(value) | 添加值,返回结构本身 |
.delete(value) | 删除值,返回布尔值 |
.has(value) | 返回布尔值 |
.clear() | 无返回值 |
实例的遍历方法 | 功能&返回值 |
---|---|
.keys() 、 .values() 、.entris() 、 .forEach() | Set结构中键名和键值是同一个值,除forEach()无返回值外,其它三返回的都是遍历器对象 |
2.Map数据结构
Map解决了Object只能用字符串作键名的限制性,它类似于对象,也是键值对的集合,但是‘键’可以是各种类型的值(包括对象)。
实例的操作方法 | 参数&返回值 |
---|---|
set(key,value) | 如果key已存在,则更新,无则新建;返回的是当前Map对象 |
get(key) | 找到则返回value,否则返回undefined |
delete(key) | 删除 返回布尔值 |
has(key) | 返回布尔值 |
clear() | 清楚所有成员 无返回值 |
作为构造函数初始化时,接受一个数组作为参数,该数组的成员是一个个表示键值对的数组。const map=new Map([ ['name','张'],['title','aaa'] ]);
注意:只有是对同一个对象的引用,Map才将其视为同一个键,只要内存地址不一样,就是两个键
const map=new Map();
map.set(['a'],111);
map.get(['a']) //undefined
遍历方法与内部判断相等方法与Set一样。
六.Promise对象
简单来说,是一个保存着某个未来才会结束的事件的结果。有两个特点:
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有Pending、Fulfilled、Rejected三种状态,由异步操作的结果决定,其他任何操作都无法影响。
- 一旦状态改变就不会再变。
缺点:a) 一旦新建会立即执行,无法中途取消
b) 不设置回调函数的话,内部错误不会反应到外部
c) 当处于Pending状态时,无法判断具体进行到哪个阶段(刚刚开始还是即将完成)
1.用法
Promise是一个构造函数,可以创建一个Promise实例,参数是一个函数,该函数的参数是resolve和reject函数,由js引擎提供。
var promise=new Promise(function(resolve,reject){
if(){ //异步操作完成
resolve(value); //将异步操作的结果作为参数传递出去
}else{
reject(error);
}
});
2.Promise.prototype.then()
Promise实例可以用then方法分别指定Resolve和Reject状态的回调函数,参数都是之前的实例中传出的值,then方法返回的是一个新的Promise实例。
promise.then(function(value){},function(error));
以下代码的输出顺序是什么?
let promise=new Promise((resolve,reject)=>{
console.log('promise');
resolve();
});
promise.then(()=>{console.log('Resolved.')});
console.log('log');
//promise 实例新建后会立即执行
//log then方法指定的回调函数会在当前脚本所有同步任务执行完成后再执行
//Resolved.
3.Promise.prototype.catch()
此方法等价于.then(null,reject(error){})
,但是比这种方法好。返回的也是一个Promise对象
promise.then(function(data){
//success
y+2; //y没定义,会抛出错误被后面catch到
}).catch(function(error){
//error
});
3.Promise.prototype.all()
用于将多个Promise实例包装成一个新的Promise实例。
参数可以是数组,也可以是具有iterator接口且返回的每个成员都是Promise实例的结构。
let p= Promise.all([p1,p2,p3]);
只有p中的所有实例状态都resolve时,p的状态才会变成resolved,然后将每个实例的返回值组成数组传出; 只要有一个实例被reject了,p就会变成reject,第一个被reject的实例的返回值会被传出。
4.Promise.prototype.race()
参数与用法和.all()
一样,不同的是let p= Promise.race([p1,p2,p3]);
只要有一个实例率先改变状态,p的状态就会改变,返回先改变的实例的返回值。
这样就可以将某个promise实例与定时器放在一起,用于在某个时间内还没得到结果就reject。
let p=Promise.race([
fetch('/practice.js'),
new Promise((resolve,reject){
setTimeout(() =>reject(new Error('request error')), 5000);
})
]);
七.Generator函数
1.基本用法
function* helloWorldGenerator(){
yield 'hello';
yield 'world';
return 'ending';
}
var hw=helloWorldGenerator();
执行函数会返回一个遍历器对象,以上就是把这个遍历器对象赋值给hw,调用它的next()方法,返回一个具有value属性和done属性的对象,value属性就是当前遍历到的yield表达式的值,done属性表示遍历是否结束。
hw.next(); //{value:'hello',done:false}
hw.next(); //{value:'world',done:false}
hw.next(); //{value:'ending',done:true}
hw.next(); //{value:undefined,done:true}
注意:a) yield表达式只能用在Generator函数里面,即它的父级函数必须是Generator函数
b) yield表达式如果用在另一个表达式中,必须放在圆括号中console.log('hello'+yield 'world')
2.next()方法的参数
yield语句本身是无返回值的,也就是let a=yield 3;
中a
是没有值的。
next方法的参数就是为上一条yield语句设置返回值,这样就可以在Generator函数开始运行后向函数内部注入值:
function* foo(x){
var y=2*(yield (x+1));
var z=yield (y/3);
return (x+y+z);
}
var b=foo(5);
b.next(); //{value:6,done:false} 第一次若传递参数,则无效
b.next(12); //{value:8,done:false} y的值是 2*12
b.next(3); //{value:32,done:false} z的值是 3
3.Generator.prototype.return()
用来返回给定的值,并终结函数遍历。如果函数内部有try{...}finally{...}
代码块,则return方法会推迟到finally代码块执行完。
function* numbers(){
yield 1;
try{
yield 2;
yield 3;
}finally{
yield 4;
yield 5;
}
yield 6;
}
var g=numbers();
g.next(); //{value:1,done:false}
g.next(); //{value:2,done:false}
g.return(88); //{value:4,done:false}
g.next(); //{value:5,done:false}
g.next(); //{value:88,done:true}
4.yield* 表达式
用于在一个Generator函数里面执行另一个Generator函数
function* con(){
yield* helloWorldGenerator();
}
//等价于
function* con(){
for(let val of helloWorldGenerator()){
yield val;
}
}
八.async函数
1.用法
async
函数return返回一个Promise
对象,函数内部return语句返回的值会成为调用then
方法里的回调函数的参数。
await
命令后面跟一个Promise
对象,如果不是,会被转成一个立即resolve的Promise
对象,函数会等待一个await命令的异步操作完成再处理后面的。
async function foo(){
await 888;
return await 123; //此处返回的是await命令处理的结果123被转成的Promise对象
}
foo().then((val)=>console.log(val)) //123
注意:
- 如果
await
命令后的promise对象变成reject状态,不管前面有没有return语句,reject
方法中的参数都会传入外部调用的catch
方法中。 - 只要有一个
await
命令后面的promise变成reject,整个async函数都会中断执行,所以这个时候在函数内部使用.catch()
方法捕捉到错误进行处理是很必要的。
async function f(){
try{
await Promise.reject('出错');
}catch{
...
}
return await Promise.resolve('hello');
}
f().then(val=>console.log(val) ) //hello
.catch(err=> console.log(err) ) //如果函数f中没有trycatch机制,这里会显示 出错
- 如果多个
await
命令后面的异步操作不存在继发关系,可以让它们同时触发。
let foo=await getFoo();
let bar=await getBar();
let [foo,bar]=await Promise.all([getFoo(),getBar()]);
假设某个DOM元素上部署了一系列动画,前一个结束才能开始后一个,有一个动画出错,就停止执行,并返回上一个成功执行的返回值。
async function chainAnimations(elem,animations){
var ret=null;
try{
for(let anim of animations){
ret = await anim(elem);
}
}catch(e){
console.log(e);
}
return ret;
}
九.Class
es6中的通过构造函数定义新对象的Class写法更像面向对象编程的语法。
类中的constructor方法就是实际上用到的构造方法。
class Point{
constructor (x,y){
this.x=x;
this.y=y;
}
toString(){
console.log('class');
}
}
let b=new Point();
类Point的所有方法都是定义在类的prototype
属性上的,并且都是不可枚举的。
Point===Point.prototype.constructor //true
b.constructor===Point.prototype.constructor //true
1.this的指向
类的内部如果含有this,它会默认指向类的实例对象,但是一旦单独使用,就会报错
class Point{
constructor(){
this.a=333;
}
sum(){
console.log(this.a);
}
}
let b=new Point();
const {sum}=b;
sum(); //这里调用sum,内部的this指向当前运行的环境,找不到a这个属性,会报错。
解决方法除了可以在构造函数中绑定this,还可以用箭头函数
class Point{
constructor(){
this.a=333;
this.sum=()=>{
console.log(this); //箭头函数中的this默认为定义时所在的this,这里是Point对象
console.log(this.a);
};
}
}
let b=new Point();
const {sum}=b;
sum(); //333
2.Class的静态方法和实例属性、静态属性
a) 所有在类中定义的方法都会被实例继承,但是前面加了static
关键字,就不会被继承,只能被通过类调用。
class Point{
static sum(){
console.log('sum');
}
}
Point.sum(); //sum
b) 实例属性可以通过等式写入类的定义中,被类的实例读取。
class Myclass{
myid=2;
}
let a=new Myclass(); //a.myid为2
c) 静态属性就是在实例属性的写法前面加上static关键字。
class Myclass{
static myid=2;
}
let a=new Myclass(); //a无myid属性