ECMAScript 6 是ECMA於2015.06發布的版本,作為一個分界點,現在我們通常把這之后的版本統稱為ES6。ES6帶來了許多全新的語法,同時添加了類的概念,可以預見的是,JavaScript正朝着工程化語言邁進,我們並不知道這對於年輕的JavaScript來說是好還是壞,因為它最開始是做為一款輕量級的腳本語言而風靡全球的。
一 新的原始類型和變量聲明
1,symbol
在ES6之前,我們知道JavaScript支持6種數據類型:object,string,boolean,number,null,undefined。現在,ES6新增了一種原始數據類型:symbol,表示獨一無二的值,即每個symbol類型的值都不相同。這讓我想起了另一個特殊的值:NaN,想一想,他們是不是有一點類似呢!
1 var sy = Symbol('test'); 2 var sy1 = Symbol('test'); 3 console.log(tepeof sy);//'symbol'
4 sy == sy1;//false
5 var sy2 = new Symbol('test');//error : Symbol is not a constructor
創建symbol數據類型的值時,需要給Symbol函數傳遞一個字符串,並且有一點特殊的是:不能使用new關鍵字調用它。另外,每個symbol類型值都是獨一無二的,即使傳遞的是相同的字符串。
2,let和const
ES6新增了兩個聲明變量的關鍵字:let和const。
他們聲明的變量僅在let和const關鍵字所在的代碼塊內起作用,即在使用let和const的那一對大括號{}內起作用,也稱塊級作用域(ES6之前只有函數作用域和全局作用域)。
let和const聲明變量不會在預編譯過程中有提升行為(在全局聲明也不會變成window的屬性),且同一變量不能重復聲明。所以要使用這類變量,只能在let和const關鍵字之后使用它們。
let和const關鍵字還有一個特性:“暫時性死區”,即在使用了該關鍵字的塊級作用域中,其內部使用let和const關鍵字聲明的變量與外部作用域中的變量相互隔絕,互不影響。即使是同名變量。
1 var a = 1; 2 { 3 console.log(a);//error Cannot access 'a' before initialization
4 let a = 0; 5 console.log(a);//0
6 } 7 console.log(a);//1
const用來聲明一個常量,聲明時必須賦值,且一旦聲明就不能改變。
其實說const變量不能更改是不准確的,請看下面的例子:
1 const obj = { 2 name:'ren', 3 age:12
4 }; 5 obj = {};//error
6 obj.sex = male; 7 consol.log(obj);//{name:'ren',age:12;sex:'male'}
const聲明的如果是一個原始值,那么上面的說法是准確的,如果const聲明的是一個引用值,那么更准確的說法應該是一個不能被重新賦值的變量。
3,解構賦值
解構賦值是對賦值運算符的擴展。它是一種針對數組或者對象進行模式匹配,然后對其中的變量進行賦值。
let [a,b,c] = [1,2,3]; console.log(a,b,c);//1,2,3
************************** let [a,b,c] = [1,,3]; console.log(a,b,c);//1,undefined,3
************************** let [a,,b] = [1,2,3]; console.log(a,b);//1,3
************************** let [a,..b] = [1,2,3];//...是剩余運算符,表示賦值運算符右邊除第一個值外剩余的都賦值給b
console.log(a,b);//1,[2,3]
事實上所有可枚舉(iterable)的對象都可以使用結構賦值,例如數組,字符串對象,以及ES6新增的Map和Set類型。
1 let arr = 'hello'; 2 let [a,b,c,d,e] = arr; 3 console.log(a,b,c,d,e);//'h','e','l','l','o'
對象的解構賦值和數組類似,不過左邊的變量名需要使用對象的屬性名,並且用大括號{}而非中括號[]:
1 let obj = {name:'ren',age:12,sex:'male'}; 2 let {name,age,sex} = obj; 3 console.log(name,age,sex);//'ren' 12 'male'
4 let {name:myName,age:myAge,sex:mySex} = obj;//自定義變量名
5 console.log(myName,myAge,mySex);//'ren' 12 'male'
二 新的對象和方法
1,Map和Set
Map對象用於保存鍵值對,任何值JavaScript支持的值都可以作為一個鍵或者一個值。這聽起來和對象差不多啊?其實它們還是有區別的:
a) object的鍵只能是字符串或ES6的symbol值,而Map可以是任何值。
b) Map對象有一個size屬性,存儲了鍵值對的個數,而object對象沒有類似屬性。
1 let myMap = new Map([['name','ren'],['age',12]]); 2 console.log(myMap);//{'name'=>'ren','age'=>12}
3 myMap.set('sex','male'); 4 console.log(myMap);//{'name'=>'ren','age'=>12,'sex'=>'male'}
5 myMap.get('name');//'ren'
6 myMap.has('age');//true
7 myMap.delete('age');//true
8 myMap.has('age');//false
9 myMap.get('age');//undefined
Map構造函數接收一個二維數組來創建一個Map對象。數組元素的第0位表示Map對象的key,第1位表示Map對象的value。
Map對象使用set方法來新增數據,set方法接收兩個參數,第一個表示key,第二個表示value。使用get方法獲取數據,參數是對象的key。
Map對象使用delete方法來刪除數據,接收一個參數,表示需要被刪除的key。
Map對象使用has方法檢測是否已經具有某個屬性,返回boolean值。
Set對象和Map對象類似,但它是用來存儲一組唯一值的,而不是鍵值對。類似數組,但它的每個元素都是唯一的。
1 let mySet = new Set([1,2,3]); 2 console.log(mySet);//{1,2,3}
3 mySet.add(4); 4 console.log(mySet);//{1,2,3,4}
5 mySet.delete(1);//true
6 mySet.has(1);//false
利用Set對象唯一性的特點,可以輕松實現數組的去重:
1 let arr = [1,1,2,3,4,4]; 2 let mySet = new Set(arr); 3 let newArr = Array.from(mySet); 4 console.log(newArr);//[1,2,3,4]
2,對象新特性
創建對象的字面量方式可以更加簡潔。直接使用變量名作為屬性,函數體作為方法,最終變量值變成屬性值,函數名變成方法名。
1 let name = 'ren'; 2 let age = 12; 3 let myself = { 4 name, 5 age, 6 say(){ 7 console.log(this.name); 8 } 9 }; 10 console.log(myself);//{name:'ren',age:12,say:fn}
11 myself.say();//'ren'
對象的拓展運算符(...)三點。用於拷貝目標對象所有可遍歷的屬性到當前對象。
1 let obj = {name:'ren',age:12}; 2 let person = {...obj}; 3 console.log(person);//{name:'ren',age:12}
4 obj == person;//false
5 let another = {sex:'male'}; 6 let someone = {...person,...another};//合並對象 7 console.log(someone);//{name:'ren',age:12,sex:'male'}
ES6對象新增了兩個方法,assign和is。
assign用於淺拷貝源對象可枚舉屬性到目標對象。
1 let source = {a:{ b: 1},b: 2}; 2 let target = {c: 3}; 3 Object.assign(target, source); 4 console.log(target);//{c: 3, a: {b:1}, b: 2}
5 source.a.b = 2; 6 console.log(target.a.b);//2
如果有同名屬性,那么目標對象的屬性值會被源對象的屬性值覆蓋。所以數組的表現就有一點特別了:
1 Object.assign([1,2,3],[11,22,33,44]);//[11,22,33,44]
數組的index就是屬性名,當使用assign方法時,從第0位開始,目標數組的值便開始被源數組的值覆蓋了。
is方法和(===)功能基本類似,用於判斷兩個值是否絕對相等。
1 Object.is(1,1);//true
2 Object.is(1,true);//false
3 Object.is([],[]);//false
4 Object.is(+0,-0);//false
5 Object.is(NaN,NaN);//true
他們僅有的兩點區別是,is方法可以區分+0還是-0,還有就是它認為NaN是相等的。
3,字符串新方法
includes()判斷字符串是否包含參數字符串,返回boolean值。如果想要知道參數字符串出現的位置,還是需要indexOf或lastIndexOf方法。
startsWith()/endsWith(),判斷字符串是否以參數字符串開頭或結尾。返回boolean值。這兩個方法可以有第二個參數,一個數字,表示開始查找的位置。
1 let str = 'blue,red,orange,white'; 2 str.includes('blue');//true
3 str.startsWith('blue');//true
4 str.endsWith('blue');//false
repeat()方法按指定次數返回一個新的字符串。如果次數是大於0的小數則向下取整,0到-1之間的小數則向上取整,其他負數將拋出錯誤。
1 console.log('hello'.repeat(2));//'hellohello'
2 console.log('hello'.repeat(1.9));//'hello'
3 console.log('hello'.repeat(-0.9));//''
4 console.log('hello'.repeat(-1.9));//error
padStart()/padEnd(),用參數字符串按給定長度從前面或后面補全字符串,返回新字符串。
1 let arr = 'hell'; 2 console.log(arr.padEnd(5,'o'));//'hello'
3 console.log(arr.padEnd(6,'o'));//'helloo'
4 console.log(arr.padEnd(6));//'hell ',如果沒有指定將用空格代替
5 console.log(arr.padStart(5,'o'));//'ohell'
另外,如果字符串加上補全的字符串超出了給定的長度,那么,超出的部分將被截去。
4,數組的新方法
of()是ES6新增的用於創建數組的方法。of把傳入的參數當做數組元素,形成新的數組。
1 let arr = Array.of(1,'2',[3],{}); 2 console.log(arr);//[1,'2',[3],{}]
from()方法可以將可迭代對象轉換為新的數組。函數可接受3個參數:第一個表示將被轉換的可迭代對象,第二個是回調函數,將對每個數組元素應用該回調函數,然后返回新的值到新數組,第三個是回到函數內this的指向。后兩個參數是可選的。
1 let obj = { 2 double(n) { 3 return n * 2; 4 } 5 } 6 let arr = [1, 2, 3]; 7 console.log(Array.from(arr, function (n){ 8 return this.double(n); 9 }, obj)); // [2, 4, 6]
find()和findIndex(),查找數組中符合條件的元素值或索引,方法不會修改原數組。
接受一個回調函數作為參數,函數可以接受四個參數,分別是當前遍歷到的元素,當前遍歷到的索引,數組本身以及函數內this的指向。方法會把回調函數作用於每一個遍歷到的元素,如果遍歷到某一個元素可以使回調函數返回true,那么find方法會立即返回該元素,findIndex方法會返回該元素的索引。並終止繼續遍歷。
如果有多個符合條件的,也將只返回第一個。如果遍歷完整個數組也無法是回調函數返回true,那么find方法將返回undefined,findIndex方法將返回-1。
1 let arr = [1,2,3,4,5]; 2 console.log(arr.find((ele) => { 3 return ele === 1; 4 }));//1 5 console.log(arr.findIndex((ele) => { 6 return ele > 4; 7 }));//4
fill()/copyWithin(),替換數組中部分元素,會修改原數組。
1 let arr = [1,2,3,4,5]; 2 console.log(arr.fill(0,0,3));//[0,0,0,4,5]
3 //參數1表示目標值,參數2,3表示替換的始末位置,左閉右開區間。
4 console.log(arr.copyWithin(0,2,4));//[0,4,0,4,5]
5 //參數1表示修改的起始位置,參數2,3表示用來替換的數據的始末位置,左閉右開區間。
fill()用指定的值替換,copyWithin()使用數組中原有的某一部分值替換。
includes()用於檢測數組是否包含某個值,可以指定開始位置。
1 let arr = [1,2,3,4,5]; 2 console.log(arr.includes(2));//true
3 console.log(arr.includes(1,1));//false
三 函數
1,參數默認值
ES6首次添加了參數默認值。我們再也不用在函數內部編寫容錯代碼了。
1 function add(a=1,b=2){ 2 return a + b; 3 } 4 add();//3
5 add(2);//4
6 add(3,4);//7
和參數默認值一起,ES6還帶來了不定參。它的功能和使用arguments差不多。
1 function add(...num){ 2 return num.reduce(function(result,value){ 3 return result + value; 4 }); 5 } 6 add(1,2,3,4);//10
下面介紹的箭頭函數沒有arguments屬性,如果箭頭函數內要實現不定參,上述方式就是一個不錯的選擇了。
2,箭頭函數
箭頭函數實現了一種更加簡潔的書寫方式,並且也解決了關鍵字聲明方式的一些麻煩事兒。箭頭函數內部沒有arguments,也沒有prototype屬性,所以不能用new關鍵字調用箭頭函數。
箭頭函數的書寫方式:參數 => 函數體。
1 let add = (a,b) => { 2 return a+b; 3 } 4 let print = () => { 5 console.log('hi'); 6 } 7 let fn = a => a * a; 8 //當只有一個參數時,括號可以省略,函數體只有單行return語句時,大括號也可以省略,強烈建議不要省略它們,是真的難以閱讀
當函數需要直接返回對象時,你必須使用小括號把對象包裹起來。否則將拋出錯誤。
1 const fn = () =>{name:'ren',age:12}; 2 // SyntaxError
3 *******************************************
4 const fn = () =>({name:'ren',age:12});
箭頭函數和普通函數最大的區別在於其內部this永遠指向其父級AO對象的this。
普通函數在預編譯環節會在AO對象上添加this屬性,保存一個對象(請參照《JavaScript之深入對象(二)》)。每個普通函數在執行時都有一個特定的this對象,而箭頭函數執行時並不直接擁有this屬性,如果你在箭頭函數中使用this,將根據函數作用域鏈,直接引用父級AO對象上this綁定的對象。普通函數的AO對象只有在函數執行時才產生,換言之,普通函數的this是由函數執行時的環境決定。而箭頭函數的特別之處在於,當函數被定義時,就引用了其父級AO對象的this,即箭頭函數的this由定義時的環境決定。
根據箭頭函數的特點,不難推測:如果定義對象的方法直接使用箭頭函數,那么函數內的this將直接指向window。
1 var age = 123; 2 let obj = { 3 age:456, 4 say:() => { 5 console.log(this.age); 6 } 7 }; 8 obj.say();//123
9 //對象是沒有執行期上下文的(AO對象),定義對象的方法實際上是在全局作用域下,即window
如果你一定要在箭頭函數中讓this指向當前對象,其實也還是有辦法的(但是沒必要這么麻煩啊,直接使用普通函數不是更好嗎?):
1 var age = 123; 2 let obj = { 3 age:456, 4 say:function(){ 5 var fn = () => { 6 console.log(this.age); 7 } 8 return fn(); 9 } 10 }; 11 obj.say();//456
我們來分析一下這是怎么做到的:首先,我們使用obj調用say方法時,say內創建了AO對象,並且該AO對象的this屬性指向了obj(這里不明白的請回去復習一下我的《JavaScript之深入函數/對象》),然后,say內部又聲明了一個箭頭函數。我們說箭頭函數在聲明時就要強行引用父級AO的this屬性,那么現在該箭頭函數的父級AO是誰呢?當然就是say的AO啦,所以這里箭頭函數的this直接就綁定了obj,最后箭頭函數在執行時拿到的this,實際上就是say方法的AO.this,即obj本身。
上面是在對象中使用箭頭函數,如果那讓你難於理解,那么請看下面這種方式:在普通函數中使用箭頭函數。
1 var obj = {name:'ren'}; 2 function test(){ 3 var fn = () => { 4 console.log(this); 5 }; 6 fn(); 7 } 8 test();//window
9 test.call(obj);//{name:'ren'}
test函數在全局執行時,其this指向window,這時也產生了箭頭函數的定義,於是箭頭函數內的this也被指向了window,所以最終打印出window對象。
當我們手動改變test函數執行時this的指向時,箭頭函數定義所綁定的this實際上也被我們修改了。所以最終打印出obj。
四 class(類)
class 作為對象的模板被引入ES6,你可以通過 class 關鍵字定義類。class 的本質依然是一個函數。
1,創建類
1 class Ex {//關鍵字聲明方式
2 constructor(name){ 3 this.name = name; 4 this.say = () => { 5 console.log(this.name); 6 } 7 } 8 methods(){ 9 console.log('hello ' + this.name); 10 } 11 static a = 123; 12 static m = () => { 13 console.log(this.a); 14 }; 15 } 16 //let ex = class{} 字面量方式
17 var example = new Ex('ren'); 18 example.say();//'ren'
19 Ex.m();//123
20 example.methods();//'hello ren'
constructor是創建類必須的方法,當使用new調用類創建實例時,將自動執行該方法,該方法和構造函數類似,默認返回this對象。實例的方法和屬性都定義在constructor內部。相當於構造函數的this方式。
類保留了prototype屬性,類中的方法不需要使用function關鍵字,並且方法之間不需要逗號隔開。類中定義的方法實際上還是保存在類的prototype屬性上。
使用static關鍵字定義類的靜態屬性和方法。類中不能定義共有屬性,要想定義實例的共有屬性還是需要使用prototype屬性:Ex.prototype.屬性名 = 屬性值。
創建實例依然使用new關鍵字。
2,類的繼承
類的繼承通過extends關鍵字實現。
1 class Person { 2 constructor (name,age){ 3 this.name = name; 4 this.age = age; 5 } 6 say(){ 7 console.log(this.name + ':' + this.age); 8 } 9 } 10 class Student extends Person{ 11 constructor (name,age,sex){ 12 super(name,age); 13 this.sex = sex; 14 } 15 } 16 var student = new Student('ren',12,'male'); 17 student.name;//'ren'
18 student.sex;//'male'
19 student.say();//'ren:12'
子類繼承自父類,不會隱式的創建自己的this對象,而是通過super()引用父類的this。這個過程和在子構造函數內使用父構造函數call(this)很像,但他們有本質的區別。另外,ES6規定,super()必須在子類的this之前執行。所以一般我們把super()放在子類constructor方法的第一行,這樣准沒錯!
五 模塊導入和導出
1,導入
ES6使用關鍵字 import 導入模塊(文件),有兩種常用的方式:
1 import ‘模塊名稱’ from ‘路徑’; 2 import ‘路徑’;
通過 import...from 的方式引入模塊,模塊名稱實際上相當於定義一個變量,用來接收即將導入的模塊。
路徑可以有很多方式,既可以是絕對路徑,也可以是相對路徑,甚至只是一個簡單的模塊名稱,更甚至連文件后綴都不需要。當你使用該命令時,系統會自動從配置文件中指定的路徑中去尋找並加載正確的文件。
import Vue from "vue"; //完整路勁其實是 "../node_modules/vue/dist/vue.js";
通過 import... 的方式一般用來引入樣式文件或預處理文件,因為他們不需要用變量來接收。
2,導出
ES6 通過 export 和export default 導出模塊。導出的含義是向外暴露、輸出,在一個文件中通過 import 導入另一個文件,通過變量即可以接收到導出的數據了。一般情況下,JS文件可以向外輸出變量、函數和對象。
1 let name = 'ren',age = 12; 2 export {name,age}; 3 //注意:變量需要用大括號包裹,然后才能向外輸出
如果僅需向外暴露一個變量:
1 export var name = 'ren';
使用 export 向外輸出函數的用法和變量相同,這里不再舉例。
總結:使用 export 向外輸出成員時,可以同時輸出多個,並且必須用‘{}’大括號包裹,在其他地方使用 import 導入時,接收成員的變量名必須和這里輸出的名稱一致,同時,可以根據實際情況,僅接收實際需要的的成員(接收的時候也要用大括號包裹)。
如果希望通過 export 向外暴露成員,並且在導入的時候自定義接收名稱,那么你可以使用 as 關鍵字重命名。
1 let name = 'ren', age = 12; 2 export {name, age}; 3 ************************************ 4 import {name as myName, age as myAge} from 'url';
與 export 相比,export default 有以下幾點不同:首先,在同一個模塊中,export default 只允許向外暴露一次成員;然后,這種方式可以使用任意的名稱接收,不像 export 那樣有嚴格的要求;最后,export 和 export default 可以在同一模塊中同時存在。
1 let person = {name:'ren'}; 2 let age = 12; 3 let address = 'cd'; 4 export default person; 5 export {age}; 6 export {address}; 7 ************************** 8 import man,{age,address} from 'url'
小技巧:通常 import 無法直接被瀏覽器識別,即如果在HTML文檔中引入的 JS 文件直接使用了 import 關鍵字,瀏覽器會報錯。要想直接在被HTML文檔引用的 JS 文件中使用 import,需要給該 <script> 標簽添加type屬性,且其值應該是 module。
六 異步機制
ES6新增了兩種實現異步的新機制,Promise和Generator。文筆有限,怕講的不清楚,誤人子弟,請有興趣的同學去下面的鏈接繼續學習,廖老師的教程也是受很多人推崇的,當然MDN更官方。(實際上是需要較大篇幅才能講明白,這里就偷個懶了)
1,Promise
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://www.liaoxuefeng.com/wiki/1022910821149312/1023024413276544
2,Generator
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Generator
https://www.liaoxuefeng.com/wiki/1022910821149312/1023024381818112