一、let 和 const
1、let可以聲明變量
let name = '張三';
console.log(name);//張三
2、ES6新增塊級作用域
注:在ES6之前作用域分爲:全局作用域、函數作用域
var name1 = '張三';
let name2 = '李四';
console.log(name2);//李四
console.log(name1);//張三
console.log(name2);//報錯
上面代碼在代碼塊之中,分別用let和var聲明瞭兩個變量。然後在代碼塊之外調用這兩個變量,結果let聲明的變量報錯,var聲明的變量返回了正確的值。而在代碼塊內let聲明的變量返回了正確的值。這表明,let聲明的變量只在它所在的代碼塊有效。
3、let的使用場景
var a =[]; for(var i=0;i<10;i++){
a[i] = function(){
console.log(i);
}
}
a[6]();//10
上面的代碼中,變量i是var聲明的,在全局範圍內都有效。所以每一次循環,新的i值都會覆蓋舊值,導致最後輸出的是最後一輪的i的值。
var a = []; for(let i=0;i<10;i++){
a[i]=function(){
console.log(i);
}
}
a[6]();//6
上面代碼中,變量i是let聲明的,聲明的變量僅在塊級作用域內有效,當前的i只在本輪循環有效,所以每一次循環的i其實都是一個新的變量,最後輸出的是6.
4、不存在變量提升
console.log(a);
console.log(b); var a = 2;//undefined
let b = 3;//報錯
上面代碼中,變量a用var命令聲明,會發生變量提升,即腳本運行時,變量a已經存在了,但是麼有值,所以會輸出undefined。變量b用let命令聲明,不會發生變量提升。這表明在聲明它之前,變量b是不存在的,這時如果用到它,就會拋出一個錯誤。
5、暫時性死區
let b = 3 {
b=4 }
console.log(b);//4
當代碼執行到代碼塊中時,b=4在代碼塊中是沒有定義的,所以它會往外找定義的變量,找到let b=3;那麼b=4就會覆蓋b=3,所以最後 b=4
let b=3 {
b=4;
let b=5;
}
console.log(b);//報錯
上面代碼中,存在全局變量b,但是塊級作用域內又聲明瞭一個局部變量b,導致後者綁定這個塊級作用域,不會再向塊區外面找,所以在let聲明變量前,對b賦值就會報錯。暫時性死區的本質就是,只要一進入當前作用域,所要使用的變量就已經存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現,纔可以獲取和使用該變量。
ES6明確規定,如果區塊中存在let和const命令,這個區塊對這些命令聲明的變量,從一開始就形成看封閉作用域。凡是在聲明之前就使用這些變量,就會報錯。總之,在代碼塊內,使用let命令聲明變量之前,該變量都是不可用的。在語法上,稱爲“暫時性死區”。
6、不允許重複聲明
{
let a = 10;
let a = 8;
console.log(a);//報錯
}
let不允許在相同的作用域內重複聲明同一個變量。
7、頂層對象屬性
var a = 2;
console.log(window.a);//2
let b = 3;
console.log(window.b);//undefined
頂層對象,在瀏覽器環境指的是window對象,在Node指的是global對象。ES5之中,頂層對象的屬性與全局變量是等價的。
ES6規定,var命令和function命令聲明的全局變量,依舊是頂層對象的屬性;let命令、const命令、class命令聲明的全局變量、不屬於頂層對象屬性。 所以上面代碼中,全局變量b是由let聲明 ,不是頂層對象,返回undefined。
8、const
上面介紹 let 有的屬性,const 都有;唯一的區別是const聲明的變量是不可以修改的,const聲明一個只讀的常量,一旦聲明,常量的值就不能改變。
const a =2;
a=3;
console.log(a);//報錯
const特殊的場景
const a ={name:'張三'};
a.name = '李四';
console.log(a);//李四
上面代碼中,常量a儲存的是一個地址,這個地址指向一個對象,不可變的只是這個地址,即不能把a指向另一個地址,但對象本身是可變的。
二、變量解構賦值
ES6允許按照一定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構。
1、模式匹配
模式匹配:只要等號倆邊的模式相同,左邊的變量就會被賦予對應的值。下面是解構列子
let [foo,[[bar],baz]]=[1,[[2],3]];
console.log(foo);//1
console.log(bar);//2
console.log(baz);//3
let [ , , third]=['a','b','c'];
console.log(third);//c
let [x, ,y]=[5,6,7];
console.log(x);//5
console.log(y);//7
let [head,...tail]=[1,2,3,4];
console.log(head);//1
console.log(tail);//[2, 3, 4]
let [k,j,...z]=['a'];
console.log(k);//a
console.log(j);//undefined
console.log(z);//[] //注:如果解構不成功,變量的值就等於undefined。
2、不完全解構
即等號左邊的模式,只匹配一部分的等號右邊的數組,這種情況下,解構依然可以成功。
let [x,y]=[1,2,3];
x //1
y //2
let [a,[b],c]=[1,[2,3],4];
a//1
b//2
c//4 //注:var,let,const命令都適用解構賦值
3、默認值
[x,y='b']=['a'];
console.log(x);//a
console.log(y);//b //解構賦值允許指定默認值
4、對象解構賦值
var {foo,bar}={foo:'aaa',bar:'bbb'};
console.log(foo);//aaa
console.log(bar);//bbb
注:對象解構與數組有一個重要的不同。數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。對象解構也可以指定默認值。
5、函數參數解構賦值
function add([x,y]){ return x+y;
}
add([1,2]);//3
上面代碼中,函數add的參數表面上是一個數組,但在傳入參數的那一刻,數組參數就被解構成變量x和y。對於函數內部的代碼來說,它們能感受的參數就是X和Y。
//函數參數的解構也可以使用默認值
function move({x=0,y=0}={}){ return [x,y];
}
move({x:3,y:8});//[3,8]
move({x:3});//[3,0]
move({});//[0,0]
move();//[0,0]
上面代碼中,函數move的參數就是一個對象,通過對這個對象就行解構,得到變量x和y的值,如果解構失敗,x和y等於默認值。
三、字符串擴展
1、for…of循環遍歷
var str ='html'; for(let i of str){
console.log(i);
} //h //t //m //l //for of遍歷將字符串每個字符都遍歷出來了
2、includes()、startsWith()、endsWith()
includes():返回布爾值,表示是否找到了參數字符串。
startsWith():返回布爾值,表示參數字符串是否在源字符串的頭部。
endsWith():返回布爾值,表示參數字符串是否在源字符串的尾部。
var str = 'Hello world!';
str.stsrtsWith('world',6)//true
str.endsWith('Hello',5)//true
str.includes('Hello',6)//false
上面代碼表示,使用第二個參數n時,endsWith的行爲與其他倆個方法有所不同,它針對前n個字符,而其他倆個方法針對從第n個位置直到字符串結束。
3、repeat()
//repeat方法返回一個新字符串,表示將原字符串重複n次。
'x'.repeat(3);//'xxx'
'hello'.repeat(2);//'hellohello'
4、模板字符串
模板字符串:是增強版的字符串,用反引號(`)標識,它可以當作普通字符串使用,也可以用來定義多行字符串,或者在字符串中嵌入變量,模板字符串中嵌入變量,需要將變量名寫在${}之中。
var name ='Bob',time='today';
`hello ${name},how are you ${time}?`
四、數值擴展
1、二進制和八進制的表示法
ES6提供了二進制和八進制數值的新寫法,分別用前綴0b和0o表示。
2、Number.isNaN()、Number.parseInt()、Number.parseFloat()
//Number.isNaN()用來檢查一個值是否爲NaN
Number.isNaN(NaN);//true
Number.isNaN(15);//false
Number.isNaN('15');//false
console.log(Number.isNaN(true));//false
//它們與傳統的全局方法isNaN()的區別在於,傳統方法先調用Number()將非數值的值轉爲數值,再進行判斷,而該新方法只對數值有效,非數值一律返回false.
//ES6將全局方法parseInt()和parseFloat(),移植到Number對象上面,行爲完全保持不變。
//ES5寫法
parseInt('12.34');//12
parseFloat('16.88#');//16.88
//ES6寫法
Number.parseInt('12.34');//12
Number.parseFloat('16.88#');//16.88 //這樣做的目的,是逐步減少全局性方法,使得語言逐步模塊化
3、Math對象的擴展
Math.trunc():去除一個數的小數部分,返回整數部分
Math.trunc(4.1);//4
Math.trunc(4.9);//4
Math.trunc(NaN);//NaN
Math.trunc('foo');//NaN
Math.trunc();//NaN //對於非數值,Math.trunc內部使用Number方法將其先轉爲數值,對於空值和無法截取整數的值,返回NaN.
Math.sign():用來判斷一個數到底是正說、負數、還是零。
// 它會返回五中值:參數爲正數,返回1;參數爲負數,返回-1;參數爲0,返回0;參數爲-0,返回-0;其他值,返回NaN.
Math.sign(-5);//-1
Math.sign(5);//+1
Math.sign(0);//+0
Math.sign(-0);//-0
Math.sign(NaN);//NaN
Math.sign('foo');//NaN
Math.cbrt():用於計算一個數的立方根
Math.cbrt(8);//2
Math.cbrt('27');//3
Math.cbrt('hello');//NaN //對於非數值,Math.cbrt方法也是先使用Number方法將其轉爲數值。
Math.hypot():返回所有參數的平方和的平方根
Math.hypot(3,4);//5
Math.hypot(3,'4');//5
Math.hypot(3,4,'foo');//NaN //如果參數不是數值,Math.hypot方法將其轉爲數值,只要有一個參數無法轉爲數值,就會返回NaN.
五、數組擴展
1、Array.form()
//Array.form():用於將倆類對象轉爲真正的數組:類似數組的對象和可遍歷的對象
var oList = document.getElementById('list');
var aLi = Array.from(oList.children);
//實際應用中,常見的類似數組的對象是DOM操作返回的NodeList集合,以及函數內部的arguments對象。Array.from都可以將它們轉爲真正的數組。
2、數組實列find()和findIndex()
數組實例的find方法,用於找出第一個符合條件的數組成員。它的參數是一個回調函數,所有數組成員依次執行該回調函數,直到找出第一個返回值爲true的成員,然後返回該成員。如果沒有符合條件的成員,則返回undefined。
var arr =[1,5,6,9]; var result = arr.find(function(v){ if(v>5){ return true;
}else{ return false;
}
});
console.log(result);//6
數組實例的findIndex方法的用法與find方法非常類似,返回第一個符合條件的數組成員的位置,如果所有成員都不符合條件,則返回-1。
var arr =[1,5,6,9]; var result = arr.find(function(v){ if(v>5){ return true;
}else{ return false;
}
});
console.log(result);//2
六、函數擴展
1、函數參數默認值
// ES6允許爲函數的參數設置默認值,即直接寫在參數定義的後面。
function log(x,y='world'){
console.log(x,y);
}
log('hello');//hello world
log('hello',china);//hello china
log('hello','')//hello
2、參數變量是默認聲明的,不能用let 或const再次聲明。
function foo(x=5){
let x =1;//error
const x=2;//error
} // 上面代碼中,參數變量x是默認聲明的,在函數體中,不能用let或const再次聲明,否則會報錯。
3、rest參數
ES6引入rest參數(形式爲“…變量名”),用於獲取函數的多餘參數,這樣就不需要使用arguments對象了。rest參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。
function print(a,...b){
console.log(a,b);
}
print(4,5,6,7,8,);//4,[5,6,7,8]
4、箭頭函數
1.箭頭函數簡化了函數的書寫方式,使得書寫更加優雅;
2.箭頭函數綁定了上下文的this,使得this指向符合預期,而不必使用 _this = this 或.bind(this)獲得預期的this值
5、嚴格模式
從ES5開始,函數內部可以設定爲嚴格模式。《ECMAScript 2016標準》做了一點修改,規定只要函數參數使用了默認值、解構賦值、或者擴展運算符,那麼函數內部就不能顯式設定爲嚴格模式,否則會報錯。
6、箭頭函數
//ES6允許使用“箭頭”(=>)定義函數
var f = v =>v; // 上面的箭頭函數等同於
var f = function(v){ return v;
} //如果箭頭函數不需要參數或需要多個參數,就使用一個圓括號代表參數部分。
var sum =(num1,num2)=>num1+num2; //等同於
var sum =function(num1,num2){ return num1+num2;
} // this在箭頭函數中的使用
1:在普通函數中,this指向的是window,在嚴格模式下,this指向的是undefined 2:在方法內,this指向的是方法的擁有者 3:在箭頭函數內,this指向的是創建箭頭函數時所在的環境中this指向的值。
document.onclick=ev=>{
alert(ev.clientX+','+ev.clientY);
};
let sum=(a,b)=>a+b;
alert(sum(12, 88)); // 100
當箭頭函數不加{}時, 箭頭後的表達式默認爲返回值
七、對象擴展
1、屬性的簡潔表示法
//ES6允許直接寫入變量和函數,作爲對象的屬性和方法。這樣的書寫更加簡潔
function f(x,y){ return{x,y};
} //等同於
function f(x,y){ return{x:x,y:y};
}
f(1,2);//{x:1,y:2}
//同名屬性可以縮寫:
let obj = {
name// 等同於name:name
}
2、Object.assign()
//用於對象的合併,將源對象(source)的所有屬性,複製到目標對象(target)
var target ={a:1}; var sourcel={b:2}; var sourde2={c:3};
Object.assign(target,sourcel,sourde2);
target //{a:1,b:2,c:3}
Object.assign方法的第一個參數是目標對象,後面的參數都是源對象。注意,如果目標對象與源對象有同名屬性,或多個源對象有同名屬性,則後面的屬性會覆蓋前面的屬性。
Object.assign(target,source1,source2);
(1)Object.assign方法用於對象的合併,將源對象(source)的所有可枚舉屬性,複製到目標對象(target);
(2)目標對象有同名屬性會覆蓋源對象;
(3)如果該參數不是對象,則會先轉成對象,然後返回;
(4)如果undefined和null在target參數位置則報錯,否則轉換不了對象而跳過;
(5)Object.assign方法實行的是淺拷貝,而不是深拷貝;
常見用途:
(1)爲對象添加屬性
(2)爲對象添加方法
(3)克隆對象 Object.assign({},origin)
(4)合併多個對象 Object.assign(target,…sources)
(5)爲屬性指定默認值 Object.assign({},DEFAULTS,options)
3、Object.is
Object.is() 解決=== 的兩個問題;
ES5 比較兩個值是否相等,只有兩個運算符:相等運算符()和嚴格相等運算符(=)。它們都有缺點,前者會自動轉換數據類型,後者的NaN不等於自身,以及+0等於-0
Object.is(+0,-0) // false
Object.is(NaN,NaN) // true
4、Object.getOwnPropertyDescriptors
Object.getOwnPropertyDescriptors()
返回某個對象屬性的描述對象(descriptor)
5、__proto__屬性,Object.setPrototypeOf(),Object.getPrototypeOf()
__proto__調用的是Object.prototype.proto,無論從語義的角度,還是從兼容性的角度,都不要使用這個屬性,而是使用下面的Object.setPrototypeOf()(寫操作)、Object.getPrototypeOf()(讀操作)、Object.create()(生成操作)
6、Object.keys(),Object.values(),Object.entries()
ES5 引入了Object.keys方法,返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名。ES2017 引入了跟Object.keys配套的Object.values和Object.entries,作爲遍歷一個對象的補充手段,供for…of循環使用。
八、Set和Map
1、Set
ES6提供了新的數據結構Set。它類似於數組,但是成員的值都是唯一的,沒有重複的值。Set本身是一個構造函數,用來生成Set數據結構。
var oSet = new Set([1,3,7]);
oSet.add(4);
oSet.add(7);
console.log(oSet); // 數組去重
var arr = [1,4,5,7,7,5,4,1]; // (方法一)Array.from方法可以將Set結構轉爲數組
console.log(Array.from(new Set(arr))); // (方法二)
console.log([...new Set(arr)]);
Set實例屬性
// constructor屬性:構造函數,默認就是Set函數。 // size屬性:返回Set實例的成員總數。
var oSet = new Set([2,5,8]);
console.log(oSet.constructor);
console.log(oSet.size);
Set實列方法
Set實例的方法分爲兩大類:操作方法(用於操作數據)和遍歷方法(用於遍歷成員)。
//(操作方法)
//add(value):添加某個值,返回Set結構本身。
//delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功
//has(value):返回一個布爾值,表示該值是否爲Set的成員。
//clear():清除所有成員,沒有返回值。
//(遍歷方法) //keys(),values(),entries():keys方法、values方法、entries方法返回的都是遍歷器對象。
//由於 Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),所以keys方法和values方法的行爲完全一致。
let set = new Set(['red','green','blue']); for(let item of set.keys()){
console.log(item);
} // red // green // blue
for(let item of set.values()){
console.log(item);
} // red // green // blue
for(let item of set.entries()){
console.log(item);
} //['red','red'] //['green','green'] //['blue','blue'] //上面代碼中,entries方法返回的遍歷器,同時包括鍵名和鍵值,所以每次輸出一個數組,它的兩個成員完全相等。
2、Map
JavaScript的對象(Object),本質上是鍵值對的集合,但是傳統上只能用字符串當作鍵。這給它的使用帶來了很大的限制。爲了解決這個問題,ES6提供了Map數據結構。它類似於對象,也是鍵值對的集合,但是“鍵”的範圍不限於字符串,各種類型的值(包括對象)都可以當作鍵。也就是說,Object結構提供了“字符串—值”的對應,Map結構提供了“值—值”的對應。如果你需要“鍵值對”的數據結構,Map比Object更合適
var o = new Map();
o.set('name','張三');
o.set('function(){}','567');
Map實列的屬性和方法
//size屬性:返回Map結構的成員總數。
//set(key, value):set方法設置key所對應的鍵值,然後返回整個Map結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵。set方法返回的是Map本身,因此可以採用鏈式寫法。
//get(key):get方法讀取key對應的鍵值,如果找不到key,返回undefined
//has(key):返回一個布爾值,表示某個鍵是否在Map數據結構中。
//delete(key):刪除某個鍵,返回true。如果刪除失敗,返回false。
//clear():清除所有成員,沒有返回值。
九、Gennerator生成器
Generator函數是ES6提供的一種異步編程解決方案,語法行爲與傳統函數完全不同。從語法上,可以把它理解成,Generator函數是一個狀態機,封裝了多個內部狀態。形式上,Generator函數是一個普通函數,但是有兩個特徵。一是,function關鍵字與函數名之間有一個星號;二是,函數體內部使用yield語句,定義不同的內部狀態(yield語句在英語裏的意思就是“產出”)。執行Generator函數會返回一個遍歷器對象,也就是說,Generator函數除了狀態機,還是一個遍歷器對象生成函數。返回的遍歷器對象,可以依次遍歷Generator函數內部的每一個狀態。
//generator生成器函數
function* sum(a,b){ var c = a + b;
yield c; var d = c + a;
yield d; var e = d + b;
yield e; var f = c + d +e; return f;
} var o = sum(4,7);
console.log(o.next());//{value: 11, done: false}
console.log(o.next());//{value: 15, done: false}
console.log(o.next());//{value: 22, done: false}
console.log(o.next());//{value: 48, done: true}
console.log(o.next());//{value: undefined, done: true}
上面代碼中的next方法,是使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield語句(或return語句)爲止。換言之,Generator函數是分段執行的,yield語句是暫停執行的標記,而next方法可以恢復執行。
yield語句:由於Generator函數返回的遍歷器對象,只有調用next方法纔會遍歷下一個內部狀態,所以其實提供了一種可以暫停執行的函數。yield語句就是暫停標誌。
遍歷器對象的next方法的運行邏輯如下:
1:遇到yield語句,就暫停執行後面的操作,並將緊跟在yield後面的那個表達式的值,作爲返回的對象的value屬性值。
2:下一次調用next方法時,再繼續往下執行,直到遇到下一個yield語句。
3:如果沒有再遇到新的yield語句,就一直運行到函數結束,直到return語句爲止,並將return語句後面的表達式的值,作爲返回的對象的value屬性值。
4:如果該函數沒有return語句,則返回的對象的value屬性值爲undefined。
next()方法:會執行generator的代碼,然後,每次遇到yield x;就返回一個對象{value: x, done: true/false},然後“暫停”。返回的value就是yield的返回值,done表示這個generator是否已經執行結束了。如果done爲true,則value就是return的返回值。當執行到done爲true時,這個generator對象就已經全部執行完畢,不要再繼續調用next()了。
十、class的寫法
俗話說物以類聚人以羣分,具有一定的共同屬性或特點的,就是calss來定義的類。
// 定義一個類
class Dog{
constructor(name,age){ this.name = name; this.age = age;
}
bark(){
console.log('旺旺!')
}
} var oDaHuang = new Dog('大黃',3); var oWangCai = new Dog('旺財',4);
console.log(oDaHuang);
console.log(oWangCai);
oDaHuang.bark();
oWangCai.bark(); // 上面代碼表明,類的數據類型就是函數,類本身就指向構造函數。 // 使用的時候,也是直接對類使用new命令,跟構造函數的用法完全一致 // constructor方法:是類的默認方法,通過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,如果沒有顯式定義,一個空的constructor方法會被默認添加。
類的繼承
//Class之間可以通過extends關鍵字實現繼承 // 下面代碼定義了一個類Dog,該類通過extends關鍵字,繼承了Animal類的所有屬性和方法。
class Animal {
constructor() { this.legs = 4;
}
eat() {
console.log('吃東西!');
}
}
class Dog extends Animal{
constructor(name, age) {
super(); // uper作爲對象時,指向父類的原型對象。
this.name = name; this.age = age;
}
bark() {
super.eat(); // super作爲函數調用時,代表父類的構造函數。
console.log('啊嗚!');
}
sleep() {
console.log('睡覺!');
}
} var oDaHuang = new Dog('大黃', 3); var oWangCai = new Dog('旺財', 4);
oDaHuang.bark();
console.log(oDaHuang); // {legs: 4, name: "大黃", age: 3}
console.log(oWangCai); //{legs: 4, name: "旺財", age: 4}
super關鍵字:
既可以當作函數使用,也可以當作對象使用。在這兩種情況下,它的用法完全不同。
第一種情況,super作爲函數調用時,代表父類的構造函數。ES6 要求,子類的構造函數必須執行一次super函數。
注:作爲函數時,super()只能用在子類的構造函數之中,用在其他地方就會報錯
第二種情況,super作爲對象時,指向父類的原型對象。
十一、Promises
Promise 是異步編程的一種解決方案,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。
所謂Promise,簡單說就是一個容器,裏面保存着某個未來纔會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。
1.Promise對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功,也可叫resolved)和rejected(已失敗);
2.狀態不受外界影響,且一旦狀態改變,就不會再變;
缺點:
1.無法取消Promise,一旦新建它就會立即執行,無法中途取消;
2.不設置回調函數,Promise內部拋出的錯誤,不會反應到外部;
3.當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成);
用法:
1.ES6 規定,Promise對象是一個構造函數,用來生成Promise實例;
2.Promise構造函數接受一個函數作爲參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由 JavaScript 引擎提供;
3.resolve函數的作用是,將Promise對象的狀態從“未完成”變爲“成功”(即從 pending 變爲 resolved),在異步操作成功時調用,並將異步操作的結果,作爲參數傳遞出去;
4.reject函數的作用是,將Promise對象的狀態從“未完成”變爲“失敗”(即從 pending 變爲 rejected),在異步操作失敗時調用,並將異步操作報出的錯誤,作爲參數傳遞出去;
5.Promise實例生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回調函數。
6.then方法可以接受兩個回調函數作爲參數。第一個回調函數是Promise對象的狀態變爲resolved時調用,第二個回調函數是Promise對象的狀態變爲rejected時調用。其中,第二個函數是可選的,不一定要提供。這兩個函數都接受Promise對象傳出的值作爲參數。建議使用then().catch(),而不是兩個回調函數;
7.Promise.prototype.then()同第6點;
8.Promise.prototype.catch() 這方法是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。
9.Promise.prototype.finally() finally方法用於指定不管 Promise 對象最後狀態如何,都會執行的操作。該方法是 ES2018 引入標準的。
10.Promise.all() 接受一個數組作爲參數,數組裏都是 Promise 實例,如果不是,就會先調用下面講到的Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理;
例如:const p=Promise.all([p1,p2,p3]);
(1)只有p1、p2、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數;
(2)只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
11.Promise.race()
const p=Promise.race([p1,p2,p3]);
只要p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
12.Promise.resolve() 將現有對象轉爲 Promise 對象,有4條規則;
13.Promise.reject() Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。
14.Promise.try() 不知道或者不想區分,函數f是同步函數還是異步操作,但是想用 Promise 來處理它。
應用:
const preloadImage=function(path){
return new Promise(function(resolve,reject){
const image=new Image();
image.onload=resolve;
image.onerror=reject;
image.src=path;
})
};
十二、modules
在 ES6 之前,社區制定了一些模塊加載方案,最主要的有 CommonJS 和 AMD 兩種。前者用於服務器,後者用於瀏覽器。ES6 在語言標準的層面上,實現了模塊功能,而且實現得相當簡單,完全可以取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。
ES6 模塊的設計思想是儘量的靜態化,使得編譯時就能確定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時確定這些東西。
ES6 模塊不是對象,而是通過export命令顯式指定輸出的代碼,再通過import命令輸入。
1、ES6 的模塊自動採用嚴格模式
2、export 命令
export var firstName=‘Michael’;
或 var firstName=‘Michael’; export{firstName};
export function multiply(x,y){return x*y;};
export輸出的變量就是本來的名字,但是可以使用as關鍵字重命名
export命令規定的是對外的接口,必須與模塊內部的變量建立一一對應關係,而不能導出一個值,如export m;
export語句輸出的接口,與其對應的值是動態綁定關係,即通過該接口,可以取到模塊內部實時的值。
export命令可以出現在模塊的任何位置,只要處於模塊頂層就可以。
3、import命令
import命令接受一對大括號,裏面指定要從其他模塊導入的變量名。大括號裏面的變量名,必須與被導入模塊對外接口的名稱相同;
可以使用as關鍵字,將輸入的變量重命名;
import命令輸入的變量都是隻讀的,因爲它的本質是輸入接口。也就是說,不允許在加載模塊的腳本里面,改寫接口;
import命令具有提升效果,會提升到整個模塊的頭部,首先執行;
import是靜態執行,所以不能使用表達式和變量;
import語句會執行所加載的模塊,多次重複執行同一句import語句,那麼只會執行一次,而不會執行多次;
import可以使用整體加載,即用星號(*)指定一個對象,所有輸出值都加載在這個對象上面,import * as xxx from ‘xxx’;
4、export default 命令
export default就是輸出一個叫做default的變量或方法,然後系統允許你爲它取任意名字,故import的時候可以隨意取名字;
export default也可以用來輸出類。
5、export 與 import 的複合寫法
如果在一個模塊之中,先輸入後輸出同一個模塊,import語句可以與export語句寫在一起;
export{foo as myFoo} from ‘my_module’;// 接口改名
6、import()動態加載
import命令能夠接受什麼參數,import()函數就能接受什麼參數,兩者區別主要是後者爲動態加載;
(1)按需加載
(2)條件加載
(3)動態的模塊路徑
十三、Spread Operator … 展開運算符/擴展運算符
ES6中另外一個好玩的特性就是Spread Operator 也是三個點兒…接下來就展示一下它的用途。
組裝對象或者數組
//數組
const color = ['red', 'yellow']
const colorful = [...color, 'green', 'pink']
console.log(colorful) //[red, yellow, green, pink]
//對象
const alp = { fist: 'a', second: 'b'}
const alphabets = { ...alp, third: 'c' }
console.log(alphabets) //{ "fist": "a", "second": "b", "third": "c" }
有時候我們想獲取數組或者對象除了前幾項或者除了某幾項的其他項
//數組
const number = [1,2,3,4,5]
const [first, ...rest] = number
console.log(rest) //2,3,4,5
//對象
const user = {
username: 'lux',
gender: 'female',
age: 19,
address: 'peking'
}
const { username, ...rest } = user
console.log(rest) //{"address": "peking", "age": 19, "gender": "female" }
對於 Object 而言,還可以用於組合成新的 Object 。(ES2017 stage-2 proposal) 當然如果有重複的屬性名,右邊覆蓋左邊
const first = {
a: 1,
b: 2,
c: 6,
}
const second = {
c: 3,
d: 4
}
const total = { ...first, ...second }
console.log(total) // { a: 1, b: 2, c: 3, d: 4 }
//擴展運算符:擴展運算符(spread)是三個點(...)。它好比rest參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。
console.log(...[1,2,3]);//1 2 3
console.log(1,...[2,3,4],5);//1 2 3 4 5
//擴展運算符應用一 // 合併數組
[1,2].concat(more);//ES5
[1,2,...more];//ES6
var arr1 = ['a','b']; var arr2 = ['c']; var arr3 = ['d','e']; //ES5的合併數組
arr1.concat(arr2,arr3);//['a','b','c','d','e'] //ES6的合併數組
[...arr1,...arr2,...arr3];//['a','b','c','d','e']
//擴展運算符應用二 //與解構賦值結合
const[first,...rest]=[1,2,3,4,5];
first//1
rest//[2,3,4,5]
// 如果將擴展運算符用於數組賦值,只能放在參數的最後一位,否則會報錯。
const[...first,last]=[1,2,3,4,5];//報錯
const[first,...middle,last]=[1,2,3,4,5];//報錯
//擴展運算符應用二 //字符串:擴展運算符還可以將字符串轉爲真正的數組。
[...'hello']//['h','e','l','l','o']
十四、async 函數
es6引入了 async 函數,使得異步操作變得更加方便。
1、async 函數是什麼?
一句話,它就是 Generator 函數的語法糖。
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
一比較就會發現,async函數就是將 Generator 函數的星號(*)替換成async,將yield替換成await,僅此而已。
async函數對 Generator 函數的改進,體現在以下四點。
- 內置執行器
- 更好的語義
- 更廣的適用性
- 返回值是 Promise
2、await 語句
- await後面是一個promise對象,如果不是,會轉成一個resolve的promise對象
async function f() {
return await 123;
}
f().then(function (a) {
console.log(a);
})
- await後面的promise對象,如果reject,則reject參數會被cache參數接收到,寫不寫return都可以,並且reject下面的代碼不會執行,如果想下面的代碼執行,必須用try cache包住
async function a() {
try{
await Promise.reject('出錯了!')
}catch (e){
return await Promise.resolve('請重新填寫')
}
}
a().then(function () {
console.log(err);
}).catch(function (err) {
console.log(err);
})
上面的代碼如果不用try cache包裹reject,則下面的代碼不會執行,並且reject語句是不用return返回的,resolve語句是需要用return返回;
3、async錯誤處理常用寫法
如果await後面的異步操作出錯,那麼等同於async函數返回的 Promise 對象被reject。所以通常的處理方法有兩種
- 用try catch包住可能會出錯的部分
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
- 另一種寫法是對可能要出錯的異步操作添加catch回調函數
async function myFunction() {
await somethingThatReturnsAPromise().catch((err)=> {
console.log(err);
})
}
4、多個await語句同時觸發
let a=await getFoo();
let b=await getBar();
上面的兩個方法都是異步操作,不存在依賴關係,所以我們可以同時觸發,改寫成下面的
//第一種寫法
let [a,b]=Promise.all([getFoo(),getBar()])
//第二種寫法
let aPromise=getFoo();
let bPromise=getBar();
let a=await aPromise;
let b=await bPromise;
5、async 函數的實現原理
async函數就是將執行器和Generator做爲一個函數返回。
async function fn(){}
//等同於
function fn() {
return spawn(function* () {
})
}
function spawn(genF) {
/****
* 返回的是一個promise
*/
return new Promise(function(resolve, reject) {
var gen=genF(); //運行Generator這個方法;
/***
* 執行下一步的方法
* @param fn 一個調用Generator方法的next方法
*/
function step(fn) {
//如果有錯誤,則直接返回,不執行下面的await
try {
var next=fn();
}catch (e){
return reject(e)
}
//如果下面沒有yield語句,即Generator的done是true
if(next.done){
return resolve(next.value);
}
// 如果下面還有yield語句,resolve繼續執行遞歸執行gen.next(),reject則拋出錯誤
Promise.resolve(next.value).then((val)=>{
step(function(){ return gen.next(val) } )
}).catch((e)=>{
step(function(){ return gen.throw(e) } )
})
}
step(function () {
return gen.next();
})
});
}