1、ES6与JavaScript的关系
????????JavaScript 由 Brendan Eich 于 1995 年发明,并于 1997 年成为 ECMA 标准。
????????ECMAScript 是该语言的官方名称。
????????从 2015 年起,ECMAScript 按年命名(ECMAScript 2015)。
????????ES6 指的是 ECMAScript 的第六代版本 因发布时间为2015年 又可称为 ECMAScript 2015。
????????ECMAScript 主要版本:
????????浏览器支持情况:
?
2、变量声明const和let
???ES6 新增了 const 关键字用于声明常量, let 关键字声明变量。
????????const 声明的变量为常量,常量在声明时必须赋值,声明后不能被修改:
const?PI?=?3.14;
PI?=?3.1415; //报错 Uncaught TypeError: Assignment to constant variable.
????????如果 const 声明的常量是一个对象,对象所包含的属性值是可以被修改的,但不能替换为一个新的对象即对象的引用地址不能修改:
const car = {color:'red'};
car.color = 'black'; //可以修改 111027110181937
car = {color:'black'}; //报错 Uncaught TypeError: Assignment to constant variable.
????????const 为块级作用域,即在所属的大括号范围内有效。超出范围程序将抛出错误:
{
var i = 1;
const j = 2;
}
console.log("i=" + i); //输出 i=1
console.log("j=" + j); //报错 Uncaught ReferenceError: j is not defined
????????与 const 一样, let 声明的变量也是块级作用域,即在所属的大括号范围内有效。超出范围程序将抛出错误:
{
var i = 1;
let j = 2;
}
console.log("i=" + i); //输出 i=1
console.log("j=" + j); //报错 Uncaught ReferenceError: j is not defined
????????区别于 var,let 关键词声明的变量不具备变量提升(hoisting)特性:
console.log("i=" + i); //输出 i=undefined
var i = 1;
console.log("j=" + j); //报错 VM320:3 Uncaught ReferenceError: j is not defined
let j = 2;
????????区别于 var,同一作用域内,let 变量不能重复声明:
var i = 1;
var i = 2; //可以重复声明
let j = 3;
let j = 4; //报错 VM287:4 Uncaught SyntaxError: Identifier 'j' has already been declared
????????var 的变量提升(hoisting)特性:
var?i?=?1;?????????????//?全局变量i
f();???????????????????//?f是函数,虽然定义在调用的后面,但是函数声明会提升到作用域的顶部。?
console.log(i);????????//?i?=?1,??当前为全局变量i
function?f()?{
??console.log(i);??????//?当前i是下一行的i的声明提升后的默认值undefined
??var?i?=?2;???????????//局部变量
??console.log(i);??????//?i?=?2
}
????????运行结果:
????????let 在 for 循环中的作用域:
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log("i=" + i);
}, 0);
} // 输出 10次 i=10
for (let j = 0; j < 10; j++) {
setTimeout(function () {
console.log("j=" + j);
}, 0);
} // 循环输出 j=0 到 j=9
????????运行结果:
3、模板字符串
?模板字符串(template string)是传统的javascript字符串的加强版。用反引号 `` 标识。它可以当作普通字符串使用,也可以用来方便的处理多行字符串和在字符串中嵌入变量。
????????普通字符串。与传统javascript字符串使用一致:
?let?hello?=?`hello?world`;
????????字符串中如果需要使用反引号,则需要在前面添加反斜杠进行转义:
let hi = `\`hi\`, dude`;
????????处理多行字符串。使用传统字符串需要使用换行和缩进的时候使用 + 或者 concat() 进行拼接是一件复杂而痛苦的事情。而模板字符串允许在反引号范围内任意的换行和缩进,所有的换行和缩进格式都能得到保留,非常方便:
let?table?=?`
????????<table>
????????????<tr>
????????????????<td>cell</td>
????????????????<td>cell</td>
????????????</tr>
????????</table>
????????`;
console.log(table);
????????打印结果:
?????????在字符串中嵌入变量。传统字符串可以使用 + 进行拼接从而将变量嵌入字符串中,模板字符串使用 ${ } 形式将变量嵌入字符串中:
let?job?=?"程序员",?salary?=?"100";
//传统字符串拼接拼接
let?say?=?"我的工作是"?+?job?+?",?我每月可以挣"?+?salary?+?"大洋,真开心!";??
//模板字符串嵌入
say?=?`我的工作是${job},?我每月可以挣${job}大洋,真开心!`;???????????????????
????????${ }内部可以直接放入字符串也可以放入放入变量、表达式、对象、方法。如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的 toString() 方法。
4、解构赋值
????????解构赋值是对赋值运算符的扩展。他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
????????数组的解构赋值:
let?[a1,?a2,?a3]?=?[1,?2,?3];
console.log(`a1=${a1},a2=${a2},a3=${a3}`);
let?[b1,?,?b2]?=?[1,?2,?3];
console.log(`b1=${b1},b2=${b2}`);
let?[c1,?c2,?c3,?c4,?c5]?=?'hello';
console.log(`c1=${c1},c2=${c2},c3=${c3},c4=${c4},c5=${c5}`);
????????输出结果:
????????对象的解构赋值:
let?a1?=?{?x,?y?}?=?{?x:?'aaa',?y:?'bbb'?};
console.log(a1);??//a1.x='aaa',?a1.y='bbb'
let?a2?=?{?x:?['hello',?{?z:?'world'?}]?};
let?a3?=?{?x:?[y,?{?z?}]?}?=?a2;
console.log(a3);??//a3.x[0]='hello',?a3.x[1].z='world'
?????????输出结果:
????????解构默认值,当解构的对象的属性没有值甚至不存在时可以设置默认值:
let?[x?=?1,?y?=?2,?z?=?3]?=?[4,?undefined,?6];
console.log(`x=${x},y=${y},z=${z}`);
function?fun({?x?=?1,?y?=?2,?z?})?{
????console.log(`x=${x},y=${y},z=${z}`);
}
fun({});
fun({?x:?3?});
fun({?x:?4,?z:?5?});
fun({?x:?6,?y:?7,?z:?8?});
fun({?z:?9?});
?????????输出结果:
?5、展开运算符
????????展开运算符是三个点 ... ,能将数组转换为参数序列:
function?add(x,?y)?{
????return?x?+?y;
}
let?numbers?=?[1,?2];
console.log(add(...numbers));
????????输出结果:
????????与数组解构表达式结合,... 也可称为剩余运算符:
let?[a,?b,?...c]?=?[1,?2,?3,?4,?5,?6];
console.log(a,?b,?c);
?????????输出结果:
????????即 ...c 匹配数组除去前面确定的变量 a 和 b 后剩余的部分。
????????注意:与解构表达式结合时剩余运算符只能位于最后一个变量前,放在之前的变量前会抛出异常:
?let?[...a,?b,?c]?=?[1,?2,?3,?4,?5,?6];
//报错 Uncaught SyntaxError: Rest element must be last element
let?[x,?...y,?z]?=?[1,?2,?3,?4,?5,?6];
//报错 Uncaught SyntaxError: Rest element must be last element
????????与对象解构表达式结合:
let { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a, b, rest);
? ? ? ? 输出结果:
?????????同样剩余运算符放在之前的变量前会抛出异常:
let?{?...rest,?a,?b?}?=?{?a:?1,?b:?2,?c:?3,?d:?4?};
//报错 Uncaught SyntaxError: Rest element must be last element
????????数组的拼接,使用 ... 运算符可以进行数组的拼接操作:
?let?a?=?[1,?2],?b?=?[4, 5];
?let?c?=?[...a,?...b];
let d = 3;
//单个元素加入拼接,对应变量无需使用 ... 运算符
let e = [...a, d, ...b];
console.log(c,e);
????????输出结果:
?????????对象的拷贝赋值。使用 ... 运算符可以将源对象拷贝到目标对象,拷贝后需注意字段值修改的作用范围:
let?src?=?{?user:?'admin',?password:?'123456',?job:?{?title:?'lawyer',?salary:?1000?}?};
let?tar?=?{?...src,?tel:?'110'?};
src.password?=?'888888'; //源单独修改
tar.user?=?'user'; //目标单独修改
src.job.title?=?'teacher'; //源和目标同步修改
tar.job.salary?=?200; //源和目标同步修改
console.log(src,?tar);
????????输出结果:
????????可以看到非对象的属性修改,源和目标是独立互不影响的;对象属性的修改源和目标同步变化,因为对象的索引地址未发生变化。?
let tar = {...src }; //可以当做是src的浅拷贝
????????这种拷贝赋值的方法效果与 Object.assign() 相同:
let?src?=?{?user:?'admin',?password:?'123456',?job:?{?title:?'lawyer',?salary:?1000?}?},?tar={ tel:?'110' };
Object.assign(tar,?src);
src.password?=?'888888';
tar.user?=?'user';
src.job.title?=?'teacher';
tar.job.salary?=?200;
console.log(src,?tar);
????????输出结果:
?6、箭头函数
????????ES6之前,使用普通函数把数组每个字符串转换为大写形式:
const upperCaseStr = ['Hello',?'World'].map(function?(src)?{
????return?src.toUpperCase();
});
????????使用箭头函数:
const?upperCase?=?['Hello',?'World'].map(str?=>?str.toUpperCase());
? ? ? ? 通过对比可以发现代码书写上有了极大地简便。函数的参数只有一个,不需要使用 () 包起来,但是超过一个, 则必须需要将参数列表放在圆括号内:
const?add?=?(a,?b)?=>?a?+?b;
????????一般箭头函数都只有一个表达式作为函数主体,因此没有花括号 {} 且自动返回表达式,如果箭头函数内部需要多行代码,则需使用常规语法。按照常规语法上述箭头函数可按如下格式书写:
const upperCaseStr = ['Hello',?'World'].map(str?=>?{
????return?str.toUpperCase();
});
const?add?=?(a,?b)?=>?{
????return?a?+?b;
};
? ? ? ? 箭头函数对this的影响:对于普通函数, this 的值基于函数如何被调用:
function?Counter()?{
????this.count?=?0;
}
Counter.prototype.addCount?=?function?()?{
???setTimeout(function?()?{
????????this.count++;
????????console.log(`count:?${this.count}`);
????},?100);
}
const?counter?=?new?Counter();
counter.addCount(); //输出 count:?NaN
????????传递给 setTimeout() 的函数被调用时没用到 new、call() 或 apply(),也没用到上下文对象。意味着函数内的 this 的值是全局对象,而不是 counter 对象。实际上发生的情况是,在setTimeout调用的函数内部创建了新的 count 变量(默认值为 undefined),然后自增(undefined + 1 结果为 NaN)。
????????对于箭头函数,this 的值基于函数周围的上下文,this 的值和函数外面的 this 的值是一样的:
function?Counter()?{
????this.count?=?0;
}
Counter.prototype.addCount?=?function?()?{
setTimeout(()?=>?{
????????this.count++;
????????console.log(`count:?${this.count}`);
????},?100);
}
const?counter?=?new?Counter();
counter.addCount(); //输出 count:?1
????????以上箭头函数中的 this 与外部的 this 值相同,都是指向调用的 counter 对象。
7、Promise
????????Promise是ES6中提供异步编程的解决方案,相比传统的通过回调函数和事件的方案更具有优越性,可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
????????Promise共有三种状态:Pending(进行中),Resolved(已完成又称为Fulfilled),Rejected(已失败)。先看一个简单的使用示例:
new?Promise(
????function?(resolve,?reject)?{
????????let?score?=?Math.round(Math.random()?*?100); //通常是耗时较长的异步操作。
????????console.log(`score:?${score}`);
????????if?(score?>=?60)?{
????????????resolve('pass');
????????}?else?{
????????????reject("fail");
????????}
????}
).then(function?resolveCallback(data)?{
????console.log(`resolved?data?:?${data}`);
},?function?rejectCallback(data)?{
????console.log(`reject?data?:?${data}`);
});
????????运行结果如下:
????????new Promise() 构造时的函数 function(resolve,?reject){} 用于处理异步操作。根据异步操作的结果通过调用 resolve() 和 reject() 设置 Promise 状态。then() 方法里第一个参数为处理 Resolved 状态的回调方法,第二个参数为处理 Rejected 状态的回调方法。
????????Promise 允许只设置 Resolved 状态,对应 then() 方法中也只需一个处理 Resolved 状态的回调方法:
new?Promise(
????(good)?=>?{
????????//?一些异步操作
????????good('pass');
????}
).then((data)?=>?{
????console.log(`resolved?data?:?${data}`);
});
?????????运行结果:
????????Promise 的状态只能被设置一次,多次的设置不会生效:
new?Promise(
????(pass,?fail)?=>?{
????????//?一些异步操作?
????????pass('pass');??//?设置状态为?Resolved??成功设置
????????fail('fail');??//?设置状态为?Rejected??不能成功设置
????}
).then((data)?=>?{
????console.log(`resolved?data?:?${data}`);
},?(data)?=>?{
????console.log(`reject?data?:?${data}`);
});
?????????运行结果:
?????????Promise 提供了 Promise.all 和 Promise.race 方法来处理多个 Promise 的问题。
????????Promise.all 将多个 Primise 实例合并成一个实例,只有当所有实例都变成 Resolved 状态才会执行回调:
const?p1?=?new?Promise((get)?=>?{
????setTimeout(()?=>?{
????????console.log('大王');
????????get("大王");
????},?200);
});
const?p2?=?new?Promise((get)?=>?{
????setTimeout(()?=>?{
????????console.log('小王');
????????get();
????},?100);
});
Promise.all([p1,?p2]).then(data?=>?{
????console.log(data);
????console.log('?王炸!');
});
????????运行结果:
????????其中 then() 里边的 data 为 Promise 实例数组 [p1,p2] 对应传递的参数数组 [data1,data2],data1 为 '大王' ,p2 没有传递参数 data2 为 undefined。
????????Promise.race 同样是将多个 Promise 实例包装成一个新的 Promise 实例, 多个实例中只要有一个实例状态改变,实例状态就改变了,返回的第一个改变的实例状态:
const?pTimeout?=?new?Promise((resolve)?=>?{
????setTimeout(()?=>?{
console.log('pTimeout?resolve');
????????resolve({?code:?'TIMEOUT'?});
????},?100);
});
const?pRequest?=?new?Promise((resolve)?=>?{
????setTimeout(()?=>?{
console.log('pRequest?resolve');
????????resolve({?code:?'OK',?data:?{}?});
????},?200);
});
Promise.race([p1,?p2]).then(res?=>?{
????if?(res.code?===?'OK')?{
????????console.log('request?ok');
????}?else?{
????????console.log('request?timeout');
????}
});
?????????运行结果:
?????????以上代码模拟接口数据请求处理请求超时的情况,pTimeout 设置超时时间 100 ms, pRequest 模拟数据请求200ms后返回数据,两个异步操作 pTimeout先状态改变, 打包的 Promise实例跟着改变,打印请求超时, 如果 pRequest 先于 pTimeout 改变状态则打印请求成功。
8、async, await
????????async 和 await 是用来处理异步的。即你需要异步像同步一样执行,需要异步返回结果之后,再往下依据结果继续执行。
????????async 是“异步”的简写,而 await 可以认为是 async wait 的简写。async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
????????相比传统的回调方法和 Promise 写法,async-await 让我们书写代码时更加流畅,也增强了代码的可读性。
????????传统回调方式处理多异步流程:
const?getUp?=?function?(callback)?{
????????????setTimeout(()?=>?{
????????????????console.log('get?up?at?7:00.')
????????????????callback();
????????????},?100);
????????}
????????const?washFace?=?function?(callback)?{
????????????setTimeout(()?=>?{
????????????????console.log('wash?face?at?7:10.')
????????????????callback();
????????????},?100);
????????}
????????const?brushTeeth?=?function?(callback)?{
????????????setTimeout(()?=>?{
????????????????console.log('brush?teeth?at?7:20.')
????????????????callback();
????????????},?100);
????????}
????????const?haveBreakfast?=?function?(callback)?{
????????????setTimeout(()?=>?{
????????????????console.log('have?breakfast?at?7:30.')
????????????????callback();
????????????},?100);
????????}
????????const?goToWork?=?function?()?{
????????????setTimeout(()?=>?{
????????????????console.log('go?to?work?at?8:00.')
????????????},?100);
????????}
????????const?happyDay?=?function?()?{
????????????getUp(function?()?{
????????????????washFace(function?()?{
????????????????????brushTeeth(function?()?{
????????????????????????haveBreakfast(function?()?{
????????????????????????????goToWork();
????????????????????????});
????????????????????});
????????????????});
????????????});
????????}
????????happyDay();
????????运行结果:
?Promise 处理多异步流程:
const?getUp?=?function?()?{
????????????return?new?Promise((resolve)?=>?{
????????????????setTimeout(()?=>?{
????????????????????console.log('get?up?at?7:00.')
????????????????????resolve();
????????????????},?100);
????????????});
????????}
????????const?washFace?=?function?()?{
????????????return?new?Promise((resolve)?=>?{
????????????????setTimeout(()?=>?{
????????????????????console.log('wash?face?at?7:10.')
????????????????????resolve();
????????????????},?100);
????????????});
????????}
????????const?brushTeeth?=?function?()?{
????????????return?new?Promise((resolve)?=>?{
????????????????setTimeout(()?=>?{
????????????????????console.log('brush?teeth?at?7:20.')
????????????????????resolve();
????????????????},?100);
????????????});
????????}
????????const?haveBreakfast?=?function?()?{
????????????return?new?Promise((resolve)?=>?{
????????????????setTimeout(()?=>?{
????????????????????console.log('have?breakfast?at?7:30.')
????????????????????resolve();
????????????????},?100);
????????????});
????????}
????????const?goToWork?=?function?()?{
????????????return?new?Promise((resolve)?=>?{
????????????????setTimeout(()?=>?{
????????????????????console.log('go?to?work?at?8:00.')
????????????????????resolve();
????????????????},?100);
????????????});
????????}
????????const?happyDay?=?function?()?{
????????????getUp().then(washFace()).then(brushTeeth()).then(haveBreakfast()).then(goToWork());
????????}
????????happyDay();
????????运行结果:
????????注意:因为Promise在构造的时候就会被立即执行,所以以上代码采用 const p = function(){return new Promise()} 的方式进行定义,才能满足在需要调用时才执行的需求。
????????async, await 处理多异步流程:
const?getUp?=?function?()?{
????????????return?new?Promise((resolve)?=>?{
????????????????setTimeout(()?=>?{
????????????????????console.log('get?up?at?7:00.')
????????????????????resolve();
????????????????},?100);
????????????});
????????}
????????const?washFace?=?function?()?{
????????????return?new?Promise((resolve)?=>?{
????????????????setTimeout(()?=>?{
????????????????????console.log('wash?face?at?7:10.')
????????????????????resolve();
????????????????},?100);
????????????});
????????}
????????const?brushTeeth?=?function?()?{
????????????return?new?Promise((resolve)?=>?{
????????????????setTimeout(()?=>?{
????????????????????console.log('brush?teeth?at?7:20.')
????????????????????resolve();
????????????????},?100);
????????????});
????????}
????????const?haveBreakfast?=?function?()?{
????????????return?new?Promise((resolve)?=>?{
????????????????setTimeout(()?=>?{
????????????????????console.log('have?breakfast?at?7:30.')
????????????????????resolve();
????????????????},?100);
????????????});
????????}
????????const?goToWork?=?function?()?{
????????????return?new?Promise((resolve)?=>?{
????????????????setTimeout(()?=>?{
????????????????????console.log('go?to?work?at?8:00.')
????????????????????resolve();
????????????????},?100);
????????????});
????????}
????????const?happyDay?=?async?function?()?{
????????????await?getUp();
????????????await?washFace();
????????????await?brushTeeth();
????????????await?haveBreakfast();
????????????await?goToWork();
????????}
????????happyDay();
????????运行结果:
? ? ? ? 对比一下代码方式,明显async, await的方式要更简洁易读。?
?
|