es6

本文详细介绍了 ES6 的多种语法特性,包括 let&const 命令、变量的结构赋值、函数扩展、数组与对象扩展、Set 和 Map 数据结构、Promise 对象、Generator 函数、async 函数以及 Class 等,阐述了各特性的用法、特点及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.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属性
3.Class的继承
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值