一、運算符與語句
1. 函數
默認參數
我們可以爲函數的參數賦予默認值,當該函數沒有被賦值的時候,會使用默認值
function fn(name,age=17){
console.log(name +" " + age);
}
fn("Amy"); // Amy 17
需要注意以下幾點:
- 函數使用默認參數的時候,不能有同名參數
- 傳遞null被認爲是有效參數,不會使用默認參數的值
- 未初始化的參數無法成爲默認參數的值
不定參數
使用不定參數,我們可以讓函數在調用的時候傳遞任意個參數值
function f(...values){
console.log(values.length);
}
f(1,2); // 2
f(1,2,3,4); // 4
箭頭函數
箭頭函數提供一種更簡潔書寫函數的方式,語法是 參數 => 函數體,函數體是要返回的結果
var f = v => v
// 等價於
var f = function(a){
return a;
}
var f = (a,b) => a + b
f(6,2); // 8
如果函數體有多行,則使用{}包裹起來即可。
2. 迭代器
迭代器是ES6一種新的遍歷機制,目的是使用一個統一的接口來使得各種數據結構易於訪問。使用迭代器的過程如下:
- 使用Symbol.iterator創建一個迭代器,指向的是當前數據結構的起始位置
- 通過next方法來獲取下一個元素,next方法會返回當前位置的對象,包含了value和done兩個屬性,value是當前位置的元素,done是是否遍歷結束
- 當done爲true的時候,迭代結束
const items = [0,1,2];
const it = item[Symbol.iterator]();
it.next();
> {value:0, done:false}
it.next();
> {value:1, done:false}
it.next();
> {value:2, done:false}
it.next();
> {value:undefined, done:true}
迭代器可以迭代的值有:Array、String、Map、Set和Dom元素
3. 類
定義類
在ES6中,類可以作爲對象的模板被引入,通過class關鍵字來定義類。類可以讓對象原型的寫法更清晰,更像面向對象編程的語法。
class Example {
constructor(a){
this.a = a;
}
}
需要注意的是,類不可以重複定義,且類中方法不需要function關鍵字,也不能加分號。類中的方法依然是定義在prototype上面的,因此重寫或添加方法的時候可以寫
Example.prototype = {
// methods
}
// 或者
Object.assign(Example.prototype, {
// methods
})
類中可以定義靜態方法,但在ES6中,不支持定義靜態屬性
class Example{
static sum(a, b) {
console.log(a+b);
}
}
Example.sum(1, 2); // 3
實例化
我們使用new關鍵字來實例化一個類,就像其他面向對象編程的語言一樣。
class Example {
constructor(a, b) {
this.a = a;
this.b = b;
console.log('Example');
}
sum() {
return this.a + this.b;
}
}
let exam1 = new Example(2, 1);
裝飾器
裝飾器是一個函數,用來修改類的行爲。裝飾器可以給類使用,也可以給方法使用。
給類使用的裝飾器要接收一個參數target,是需要修飾的類
function testable(isTestable) {
return function(target) {
target.isTestable=isTestable;
}
}
@testable(true)
class Example {}
Example.isTestable; // true
給方法修飾的裝飾器有三個參數,分別是target(要修飾的方法)、name(修飾的屬性名)、descriptor(屬性的描述對象)
class Example {
@writable
sum(a, b) {
return a + b;
}
}
function writable(target, name, descriptor) {
descriptor.writable = false;
return descriptor; // 必須返回
}
裝飾器執行的時候由外向內進入,由內向外執行
封裝
我們可以在類中定義屬性,再用getter和setter來進行訪問和修改
class Example1{
constructor(a, b) {
this.a = a;
this.b = b;
}
get a(){
console.log('getter');
return this._a;
}
set a(a){
console.log('setter');
this._a = a;
}
}
注意setter不能寫成this.a = a
,這樣寫會無限遞歸最終報出RangeError。getter不可單獨出現,且必須和setter在同級出現(不能一個在父類一個在子類)。
繼承
我們可以通過extends關鍵字來讓一個類繼承一個父類
class Father {
static test1(){
return 1;
}
}
class Child extends Father {
constructor(){
super();
}
static test3(){
return super.test1+2;
}
}
super關鍵字可以訪問到父類對象。在普通方法中,super訪問到的是父類的原型,在靜態方法中,super訪問到的是父類。
4. 模塊
ES6的模塊會自動開啓嚴格模式,可以導入和到處各種類型的變量和對象。每個模塊都有自己的上下文,聲明的變量都是局部變量,不會污染全局;且每個模塊只能加載一次,若加載同目錄下同文件,則直接從內存中讀取。
export和import
我們使用export和import來導入導出各種類型的變量
- 到處的函數聲明必須要有名稱
- 不僅能導出聲明還能導出引用
- export可以出現在模塊的任意位置,但必須處於模塊頂層
- import會被提升到模塊頭部,首先執行
/*-----export [test.js]-----*/
let myName = "Tom";
let myAge = 20;
let myfn = function(){
return "My name is" + myName + "! I'm '" + myAge + "years old."
}
let myClass = class myClass {
static a = "yeah!";
}
export { myName, myAge, myfn, myClass }
/*-----import [xxx.js]-----*/
import { myName, myAge, myfn, myClass } from "./test.js";
console.log(myfn());// My name is Tom! I'm 20 years old.
console.log(myAge);// 20
console.log(myName);// Tom
console.log(myClass.a );// yeah!
建議使用大括號指定所有輸出的變量集合在文檔尾部。函數與類都需要對應的名稱,到處文檔尾部避免了無對應名稱。其中import關鍵字還有以下幾個需要注意的地方
- 不允許在加載模塊的腳本中改寫接口的引用指向,即可以改寫import變量類型爲對象的屬性值,不能改寫import變量類型爲基本類型的值
- 多次重複執行一個import指令,只會執行一次
- import是靜態執行,不能使用表達式和變量
as
as關鍵字可以將導入的模塊重命名
/*-----export [test1.js]-----*/
let myName = "Tom";
export { myName }
/*-----export [test2.js]-----*/
let myName = "Jerry";
export { myName }
/*-----import [xxx.js]-----*/
import { myName as name1 } from "./test1.js";
import { myName as name2 } from "./test2.js";
console.log(name1);// Tom
console.log(name2);// Jerry
export default
在一個文件中,export、import可以有多個,而export default僅有一個。export default的default是對應的導出接口變量;通過export的方式導出,import的時候需要加{},而export default不需要;export default向外暴露的成員,可以使用任意變量來接收。
var a = "My name is Tom!";
export default a; // 僅有一個
export default var c = "error";
// error,default 已經是對應的導出變量,不能跟着變量聲明語句
import b from "./xxx.js"; // 不需要加{}, 使用任意變量接收
二、異步編程
1. Promise對象
Promise對象是異步編程的一個對象,可以獲取異步操作的消息。
狀態
Promise異步操作有三種狀態:pending、fulfilled和rejected。除了異步操作的結果,其他任何操作都無法改變狀態。狀態有以下幾個缺點:
- 一旦新建狀態就會立即執行,中途無法取消
- 如果不設置回調函數,Promise內部的錯誤不會反應到外部
- 當處於pending狀態的時候,無法得知是剛剛開始還是即將完成
then方法
then方法接收兩個函數作爲參數,一個是Promise執行成功時的回調,一個是Promise執行失敗時的回調,兩個函數只有一個會被調用。在JavaScript事件隊列的當前運行完成之前,回調函數永遠不會被調用。我們可以通過多次.then的形式添加回調函數
const p = new Promise(function(resolve,reject){
resolve(1);
}).then(function(value){ // 第一個then // 1
console.log(value);
return value * 2;
}).then(function(value){ // 第二個then // 2
console.log(value);
}).then(function(value){ // 第三個then // undefined
console.log(value);
return Promise.resolve('resolve');
}).then(function(value){ // 第四個then // resolve
console.log(value);
return Promise.reject('reject');
}).then(function(value){ // 第五個then //reject:reject
console.log('resolve:' + value);
}, function(err) {
console.log('reject:' + err);
});
使用Promise的時候最好保持扁平化,不要嵌套Promise,注意總時返回或終止Promise鏈。大部分瀏覽器不能終止Promise鏈中的rejection,建議跟上.catch(error => console.log(error));
2. Generator函數
ES6引入了Generator函數,可以通過yield關鍵字將執行流掛起,爲改變執行順序提供了可能性,從而實現異步編程。Generator在定義的時候,在function關鍵字後面要添加*,且函數內部存在yield表達式
function* f(){
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
Generator函數在使用的時候和迭代器十分類似,需要使用next()方法來訪問內部狀態。
f.next();
// one
// {value: "1", done: false}
f.next();
// two
// {value: "2", done: false}
f.next();
// three
// {value: "3", done: true}
f.next();
// {value: undefined, done: true}
next()方法可以傳遞參數。如果next()方法不傳遞參數,yield表達式返回值爲undefined,如果next()方法傳遞參數,該參數會作爲上一個yield表達式的返回值。
Generator函數也可以通過yield*
來實現嵌套,相當於在一個Generator函數中調用另一個Generator函數
function* callee() {
console.log('callee: ' + (yield));
}
function* caller() {
while (true) {
yield* callee();
}
}
const callerObj = caller();
callerObj.next();
// {value: undefined, done: false}
callerObj.next("a");
// callee: a
// {value: undefined, done: false}
callerObj.next("b");
// callee: b
// {value: undefined, done: false}
// 等同於
function* caller() {
while (true) {
for (var value of callee) {
yield value;
}
}
}
3. async關鍵字
async函數是ES7新增的關鍵字,和Promise、Generator有很大關係。async函數會返回一個Promise對象,可以使用then方法增加回調函數。
async function helloAsync(){
return "helloAsync";
}
console.log(helloAsync()) // Promise {<resolved>: "helloAsync"}
helloAsync().then(v=>{
console.log(v); // helloAsync
})
async關鍵字修飾的函數中,可以使用await關鍵字。await關鍵字只能在async函數中使用,用於讓一個Promise對象等待,在async函數外使用會報錯。被await關鍵字修飾的Promise對象,await將等待Promise正常處理完成後返回其處理結果。
function testAwait (x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function helloAsync() {
var x = await testAwait ("hello world");
console.log(x);
}
helloAsync ();
// hello world
正常情況下,await後面是Promise對象,但是也可以跟其他對象
function testAwait(){
console.log("testAwait");
}
async function helloAsync(){
await testAwait();
console.log("helloAsync");
}
helloAsync();
// testAwait
// helloAsync
對於Promise對象,await會暫停執行,等待Promise對象resolve,然後回覆async函數的執行並返回解析值。非Promise對象會直接返回對應的值。
參考資料
[1] 菜鳥教程ES6
歡迎加入交流羣QQ1107710098