ES6新特性

  • 2019 年 10 月 3 日
  • 筆記

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關鍵字之後使用它們。

1 {  2     let a = 0;  3     console.log(a);//0  4 }  5 console.log(a);//error a is not defined

  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';

   

二  新的對象和方法

  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(),查找數組中符合條件的元素值或索引。如果有多個符合條件的,將只返回第一個。

1 let arr = [1,2,3,4,5];  2 console.log(arr.find(1));//1  3 console.log(arr.findIndex(5));//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 var returnObj = () =>{  2     var obj = {name:'ren',age:12};  3     retufn obj;  4 };  5 //var returnObj = () => ({name:'ren',age:12});

  箭頭函數和普通函數最大的區別在於其內部this永遠指向其父級AO對象的this。

  普通函數在預編譯環節會在AO對象上添加this屬性,保存一個對象(請參照《JavaScript之深入對象(二)》)。每個普通函數在執行時都有一個特定的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方法的第一行,這樣准沒錯!

 

五  非同步機制

  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