最近團隊要求使用React Native開發移動應用, 會使用到JS的相關知識, 趁此機會學習一下ES6, 也算是拓寬自己的知識棧了。學習參考的是阮一峰老師的《ES6標準入門(第二版)》一書, 同時也參考了《JavaScript 標準參考教程》回顧JS在ES6之前的內容。 之前雖然對JS已經有所瞭解,但終歸不是很熟悉,希望通過這次學習可以對JS有一個比較全面和深入的瞭解,書籍看過了但是還是不夠深刻,因此這裡將學習的內容整理做下筆記,加深理解與記憶的同時也便於以後查閱。

一、ES6簡介

ES6(ECMAScript)是JavaScript語言的下一代標準, 已於2015年6月釋出, 其目標是使JavaScript語言可用於編寫複雜的大型應用程式。有人疑惑ECMAScript和JavaScript的關係, 簡單來說就是前者是後者的規格,後者是前者的一種實現。一般來說兩者其實等價

1.ECMAScript發展歷史

  • 1997年ECMA1.0釋出, 1998年釋出2.0, 1999年12月釋出3.0
  • 2000年開始制定4.0並於2007年釋出草案,但因太過激進遭到反對,因此將一部分修改延後
  • 2009年12月釋出5.0
  • 2015年6月ECMAScript6.0正式通過

2.部署進度查詢

通過訪問這裡可以檢視各大瀏覽器的最新版本對ES6的支援。
阮一峰老師也寫了一個ec-checker模組檢查各個執行環境對ES6的支援。
通過執行下面命令可以檢視本機對ES6的支援程度。

$ npm install -g es-checker
$ es-checker

3.Babel與Traceur轉碼器

Babel是一個ES6轉碼器,可以將ES6程式碼轉化為ES5程式碼,從而在瀏覽器或其他環境中執行。這意味著我們可以用ES6的方式編寫程式, 而又不需要擔心現有的環境是否支援,具體的使用方式參閱官網介紹,這裡不再贅述。
程式碼示例:

//轉碼前
input.map(item => item+1);

//轉碼後
input.map(function(item){
    return item + 1;
});

Traceur是谷歌公司提供的轉碼器,有興趣的同學可以查閱其官網學習。

二、 let與const命令

1.let命令

ES6新增了let命令用來宣告變數, 用法與var類似,但是用let宣告的變數只在其let所在的程式碼塊內有效。在ES5中, 我們一般都是用var宣告變數,在函式之外宣告的變數會被視為全域性變數,同時存在變數提升現象,其方法和變數的宣告都會被預設提升到所在作用域的頂部,因此即使在我們宣告之前我們依然可以使用變數和方法,但是let命令與其不同,下面是let命令的幾個特點:

  • 不存在變數提升, 因此變數必須在宣告之後才能使用,否則會報錯
  • 暫時性死區,因為在變數宣告之前該變數是無法使用,在宣告之前的程式碼區域對於該變數而言相當於死區, 這在語法上成為暫時性死區
  • let不允許在相同作用域內重複宣告同一個變數

ps: 感覺let命令與var命令相比更接近於Java等面嚮物件語言的宣告變數的方式

2.塊級作用域

在ES5中只有全域性作用域和變數作用域,由此引發了兩個問題:

  • 內層變數覆蓋外層變數
  • 用來計數的迴圈變數會洩露為全域性變數

在ES6中因為let命令的加入,實際是使ES6多了一個塊級作用域。因為let宣告的變數是在let所在的程式碼塊內有效,因此我們可以很明確的知道:

  • 外層作用域無法讀取內層作用域的變數
  • 塊級作用域外部無法呼叫塊級作用域內部定義的函式, ES6規定函式本身的作用域在其所在的塊級作用域內

注意一點: 在嚴格模式下,函式只能在頂層作用域和函式內宣告, 其他情況下(比如在if/迴圈程式碼塊)中宣告都會報錯

3.const命令

const用來宣告常量, 一旦宣告,其值不能改變。const命令具有以下特點:

  • const一旦宣告常量,必須立即初始化,不能留到以後賦值
  • const的作用域與let相同, 只在宣告所在的塊級作用域有效,
  • const和let一樣,不可以重複宣告常量
  • 對於複合型別變數, 變數名不指向資料, 而是指向資料所在的地址。const命令只保證該地址不會變, 但是該地址的資料可以改變,這一點和Java一樣,地址不變,但地址中的物件可以改變。

總體而言感覺let和const命令的加入使得JS宣告變數和常量的方式更加像面嚮物件語言的區域性變量了。

如果真的需要將物件凍結, 需要使用Object.freeze方法。下面是一個將物件徹底凍結的函式:

var constanize = (obj) => {
    Object.freeze(obj);
    Object.keys(obj).forEach((key, value) => {
        if(typeof obj[key] === 'object'){
            constantize(obj[key]);
        }
    });
};

4.跨模組常量以及全域性物件的屬性

const宣告的常量只能在當前程式碼塊有效,如果想要設定跨模組的常量需要用到export和import命令.程式碼示例如下:

//constants.js模組
export const A = 1;
export const B = 2;

//test.js模組
import * as constants form './constants';
console.log(constants.A);//1

全域性物件是最頂層的物件, 在瀏覽器環境指window物件,在Node.js中指的是global物件。在ES5中全域性物件的屬性和全域性變數是等價的。因為我們很容易不知不覺中建立全域性變數,這被視為一個很大的問題。ES6中做了修正:

  • var命令和function命令宣告的全域性變數依舊是全域性物件的屬性
  • let、const、class命令宣告的全域性變數不屬於全域性物件的屬性

備註: ES6中一共有6種宣告變數的方法,除了傳統的var和function命令, ES6新添加了let、const、import、class命令。

三、變數的解構賦值

概念: ES6允許按照一定的模式, 從陣列物件中提取值,對變數進行賦值, 這被稱為解構。
程式碼示例:

//以前的變數賦值
var a = 1;
var b = 2;
var c= 3;

//ES6解構賦值
var [a,b,c] = [1,2,3];

本質上這種寫法屬於”模式匹配”, 只要等號兩邊的模式相同,左邊的變數就會被賦予對應的值。

解構賦值的條件: 等號右邊必須是可遍歷的解構。只要某種資料結構具有Iterator介面, 都可以採用陣列形式的解構賦值。

解構賦值允許指定預設值, ES6內部使用嚴格相等===來判斷一個位置是否有值,只有右邊的值嚴格等於undefined,預設值才會生效;同時預設值也可以引用解構賦值的其他變數, 但是該變數必須已經宣告。

1.物件的解構賦值

除了陣列之外解構還可用於物件,但與陣列不同,陣列的元素是按次序排列的, 變數的取值由它的位置決定; 而物件的屬性沒有次序,變數必須與屬性同名才能取到正確的值,程式碼示例:

var {bar, foo} = {foo:"aaa", bar:"bbb"};
foo//"aaa"
bar//"bbb"

//實際上上面物件的解構賦值是以下形式的簡寫
var {foo:foo, bar:bar} = {foo:foo, bar:bar}

物件的解構賦值的內部機制, 是先找到同名屬性, 然後在賦值給對應的變數。真正被賦值的是後者, 而不是前者。程式碼示例:

var {foo:baz} = {foo:"aaa", bar:"bbb"};
//首先找到與屬性同名的foo,然後為其對應的變數baz賦值,真正被賦值的是變數baz.

baz //"aaa"
foo //error:foo is not defined

物件解構賦值的注意事項:
* 物件解構也可以指定預設值,其生效的條件是物件的屬性值嚴格等於undefined;
* 解構賦值的變數都會重新宣告, 因此不能使用let或者const命令提前宣告變數, var命令可以;

  • 解構也可以用於巢狀結構的物件,如果是巢狀的物件,而且子物件所在的父屬性不存在,那麼將會報錯。程式碼示例:
var obj = {
    p:[
       "hello",
       {y:"world"}
       ]
}

var {p:[x,{y}]} = obj;

x//"hello"
y//"world"
//報錯
var {foo:{bar}} = {baz:"baz"}

2.字串的解構賦值

字串可以被轉換為一個類似陣列的物件, 因此也可以解構賦值, 程式碼示例:

const{a,b,c,d,e} = 'hello';
a//"h"
b//"e"
c//"l"
d//"l"
e//"o"
//另外轉成的物件還有length屬性:
let{length:len} = 'hello';
len//5

3.數值和布林值的解構賦值

如果等號右邊是數值或者布林值,則會先轉換為物件,其包裝物件都有toString屬性,因此我們可以獲取到其屬性值,程式碼示例:

let{toString:s} = 123;
s === Number.prototype.toString //true

解構賦值的規則是:只要等號右邊的值不是物件,就將其先轉化為物件。由於undefined和null無法轉換為物件所以對他們進行結構賦值會報錯

4.函式引數的結構賦值

函式引數也可以結構賦值,程式碼示例:

//引數並不是陣列,而是通過結構得到x和y變數
function add([x, y]){
    return x+y;
}
add([1,2])//3
//也可以使用預設值,undefined會觸發函式擦書的預設值
function move({x=0, y=0}={}){
    return [x,y];
}

5.圓括號的使用問題

下面三種解構賦值不能使用圓括號:
* 變數宣告語句中不得使用圓括號;
* 函式引數中,模式不能帶有圓括號;
* 不能將整個模式或巢狀模式中的一層放在圓括號中;

可以使用圓括號的情況只有一種:賦值語句的非模式部分可以使用圓括號

6.解構賦值的用途

交換變數的值

[x,y] = [y,x];

定義函式引數

function f([x,y,z]){...}//f([1,2,3])

functionf({x,y,z}){...}//f({z:3,y:2,x:1})

從函式返回多個值


//返回一個數組
function example(){
    return [1,2,3];
}
var [a,b,c] = example();

//返回一個物件
function test(){
    return {
        foo:1,
        bar:2
    };
}
var {foo, bar} = test();

提取JSON資料

var jsonData = {
    id:42,
    status:"OK",
    data:[867, 5309]
}

let {id, status, data:number} = jsonData;
//id = 42, status = OK, number = [867, 5309];

指定函式引數的預設值

function move({x=0,y=0}){...}

遍歷Map結構

任何部署了Iterator介面的物件都可以用for…of迴圈遍歷,Map原生支援Iterator結構配合解構賦值,獲取key和value非常方便

//獲取鍵key
for(let [key] of map){}
//獲取值value
for(let[,value] of map){}
//獲取鍵值
for(let [ley,value] of map){}

輸入模組的指定方法
載入模組時,往往需要指定輸入哪些方法, 解構賦值使得輸入語句非常清晰

const {SourceMapConsumer, SourceNode} = require("source-map");

上面就是關於ES6let和const命令的相關簡記,繼續學習ES6+的其他知識點。