1. 程式人生 > >ES6常用的新語法學習筆記

ES6常用的新語法學習筆記

簡介

ES6是 JavaScript 語言的下一代標準,已經在 2015 年 6 月正式釋出了。它的目標,是使得 JavaScript 語言可以用來編寫複雜的大型應用程式,成為企業級開發語言。

1.let和const

let

let和var 差不多,都是用來宣告變數,但是let的宣告只能在{}內,以下程式碼輸入都是10

var a = [];
for (var i = 0; i < 10; i++) {
    a[i] = function () {//將a[i]賦予一個方法,輸出i
        console

.log(i);
    };
}a[2](); // 10

但是如果把var i=0換成let i=0;的話當呼叫a[數字i的範圍]的時候輸出的就是你填入的數字,把let宣告理解為區域性物件,像下面這段程式碼:

for (let i = 0; i < 3; i++) {

  let i = 'abc';

  console.log(i);}

只會輸出三次abd,這個輸出的i是最近的i,而不是迴圈條件中的i

另外,如果使用var未定義的物件會輸出undefined,而使用let的話則會報錯

 

暫時性死區

var tmp = 123;
if (true) {
    tmp = 'abc'; // ReferenceError
    let tmp;
}

上方程式碼會tmp is not defined的錯誤,存在全域性變數tmp,但是塊級作用域內let又聲明瞭一個區域性變數tmp,導致後者繫結這個塊級作用域,所以在let宣告變數前,對tmp賦值會報錯。

ES6 明確規定,如果區塊中存在let和const命令,這個區塊對這些命令宣告的變數,從一開始就形成了封閉作用域。凡是在宣告之前就使用這些變數,就會報錯。

在程式碼塊內,使用let命令宣告變數之前,該變數都是不可用的。這在語法上,稱為“暫時性死區”

 

總之,暫時性死區的本質就是,只要一進入當前作用域,所要使用的變數就已經存在了,但是不可獲取,只有等到宣告變數的那一行程式碼出現,才可以獲取和使用該變數。

 

let不允許在相同作用域內{},重複宣告同一個變數。

// 報錯function func() {

  let a = 10;

  var a = 1;}

// 報錯function func() {

  let a = 10;

  let a = 1;}

 

 

允許在塊級作用域內宣告函式。

函式宣告類似於var,即會提升到全域性作用域或函式作用域的頭部。

同時,函式宣告還會提升到所在的塊級作用域的頭部。

像以下程式碼執行只會輸出 I am inside

function f() {
    console.log('I am outside!');
}
(function () {
    function f() {
        console.log('I am inside!');
    }
    if (false) {
    }
    f();
}());

 

const命令

const宣告一個只讀的常量。一旦宣告,常量的值就不能改變。const一旦宣告變數,就必須立即初始化,不能留到以後賦值。對於const來說,只宣告不賦值,就會報錯。

const宣告的常量,也與let一樣不可重複宣告。

const實際上保證的,並不是變數的值不得改動,而是變數指向的那個記憶體地址所儲存的資料不得改動。對於簡單型別的資料(數值、字串、布林值),值就儲存在變數指向的那個記憶體地址,因此等同於常量。但對於複合型別的資料(主要是物件和陣列),變數指向的記憶體地址,儲存的只是一個指向實際資料的指標,const只能保證這個指標是固定的(即總是指向另一個固定的地址),至於它指向的資料結構是不是可變的,就完全不能控制了。因此,將一個物件宣告為常量必須非常小心。

const foo = {};

// 為 foo 新增一個屬性,可以成功foo.prop = 123;

foo.prop // 123

// 將 foo 指向另一個物件,就會報錯foo = {}; // TypeError: "foo" is read-only

ES6 宣告變數的六種方法

ES5 只有兩種宣告變數的方法:var命令和function命令。ES6 除了新增letconst命令,後面章節還會提到,另外兩種宣告變數的方法:import命令和class命令。

var命令和function命令宣告的全域性變數,依舊是頂層物件的屬性;另一方面規定,let命令、const命令、class命令宣告的全域性變數,不屬於頂層物件的屬性。

 

2.變數的解構賦值

陣列的解構賦值

es6允許寫成這樣:let [a, b, c] = [1, 2, 3];

上面程式碼表示,可以從陣列中提取值,按照對應位置,對變數賦值。

 

let [x, y, z] = new Set(['a', 'b', 'c']);

x // "a"

這是一個set集合,事實上,只要某種資料結構具有 Iterator 介面,都可以採用陣列形式的解構賦值。

 

ES6 內部使用嚴格相等運算子(===),判斷一個位置是否有值。所以,只有當一個數組成員嚴格等於undefined,預設值才會生效。

 

let [x = 1] = [undefined];

x // 1let [x = 1] = [null];

x // null

上面程式碼中,如果一個數組成員是null,預設值就不會生效,因為null不嚴格等於undefined

 

物件的解構賦值

let { foo, bar } = { foo: "aaa", bar: "bbb" };

foo // "aaa"bar // "bbb"

物件的解構與陣列有一個重要的不同。陣列的元素是按次序排列的,變數的取值由它的位置決定;而物件的屬性沒有次序,變數必須與屬性同名,才能取到正確的值。

 

如果變數名與屬性名不一致,必須寫成下面這樣。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };

baz // "aaa"

let obj = { first: 'hello', last: 'world' };let { first: f, last: l } = obj;

f // 'hello'

l // 'world'

 

let { foo: baz } = { foo: "aaa", bar: "bbb" };

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

上面程式碼中,foo是匹配的模式,baz才是變數。真正被賦值的是變數baz,而不是模式foo

物件的解構也可以指定預設值。預設值生效的條件是,物件的屬性值嚴格等於undefined

var {x = 3} = {x: undefined};

x // 3

var {x = 3} = {x: null};

x // null

上面程式碼中,屬性x等於null,因為nullundefined不嚴格相等,所以是個有效的賦值,導致預設值3不會生效。

如果解構失敗,變數的值等於undefined

let {foo} = {bar: 'baz'};

foo // undefined

 

字串的解構賦值

字串也可以解構賦值。這是因為此時,字串被轉換成了一個類似陣列的物件。

const [a, b, c, d, e] = 'hello';

a // "h"

b // "e"

c // "l"

d // "l"

e // "o"

類似陣列的物件都有一個length屬性,因此還可以對這個屬性解構賦值。

let {length : len} = 'hello';

len // 5

數值和布林值的解構賦值 

解構賦值時,如果等號右邊是數值和布林值,則會先轉為物件

let {toString: s} = 123;

s === Number.prototype.toString // true

let {toString: s} = true;

s === Boolean.prototype.toString // true

上面程式碼中,數值和布林值的包裝物件都有toString屬性,因此變數s都能取到值。

解構賦值的規則是,只要等號右邊的值不是物件或陣列,就先將其轉為物件。由於undefinednull無法轉為物件,所以對它們進行解構賦值,都會報錯。

let { prop: x } = undefined; // TypeError

let { prop: y } = null; // TypeError

 

函式引數的解構賦值

函式的引數也可以使用解構賦值。

function add([x, y]){

  return x + y;}

add([1, 2]); // 3

上面程式碼中,函式add的引數表面上是一個數組,但在傳入引數的那一刻,陣列引數就被解構成變數xy。對於函式內部的程式碼來說,它們能感受到的引數就是xy

下面是另一個例子。

[[1, 2], [3, 4]].map(([a, b]) => a + b);

// [ 3, 7 ]

 

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

 

用途

(1)交換變數的值

let x = 1;let y = 2;

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

(2)從函式返回多個值

函式只能返回一個值,如果要返回多個值,只能將它們放在陣列或物件裡返回。有了解構賦值,取出這些值就非常方便。

// 返回一個數組

function example() {

  return [1, 2, 3];

}let [a, b, c] = example();

// 返回一個物件

function example() {

  return {

    foo: 1,

    bar: 2

  };}

let { foo, bar } = example();

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

const map = new Map();

map.set('first', 'hello');

map.set('second', 'world');

for (let [key, value] of map) {

  console.log(key + " is " + value);}

// first is hello// second is world

如果只想獲取鍵名,或者只想獲取鍵值,可以寫成下面這樣。

// 獲取鍵名for (let [key] of map) {

  // ...}

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

  // ...}

 

3.字串的擴充套件

使得字串可以被for...of迴圈遍歷。把這個字串當做集合遍歷輸出

for (let codePoint of 'foo') {

  console.log(codePoint)

}

 

String 的新方法:

includes():返回布林值,表示是否找到了引數字串。

startsWith():返回布林值,表示引數字串是否在原字串的頭部。

endsWith():返回布林值,表示引數字串是否在原字串的尾部。

這三個方法都支援第二個引數,表示開始搜尋的位置。

repeat方法返回一個新字串,表示將原字串重複n次。

 

padStart()用於頭部補全,padEnd()用於尾部補全。

'x'.padStart(5, 'ab') // 'ababx'

'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'

'x'.padEnd(4, 'ab') // 'xaba'

如果原字串的長度,等於或大於指定的最小長度,則返回原字串。

'xxx'.padStart(2, 'ab') // 'xxx'    'xxx'.padEnd(2, 'ab') // 'xxx'

matchAll方法返回一個正則表示式在當前字串的所有匹配

String.raw方法,往往用來充當模板字串的處理函式,返回一個斜槓都被轉義(即斜槓前面再加一個斜槓)的字串,對應於替換變數後的模板字串:

String.raw`Hi\n${2+3}!`;

// 返回 "Hi\\n5!"

String.raw`Hi\u000A!`;

// 返回 "Hi\\u000A!"

 

模板字串 

$('#result').append(

  'There are <b>' + basket.count + '</b> ' +

  'items in your basket, ' +

  '<em>' + basket.onSale +

  '</em> are on sale!');

上面這種寫法相當繁瑣不方便,ES6 引入了模板字串解決這個問題。

$('#result').append(`

  There are <b>${basket.count}</b> items

   in your basket, <em>${basket.onSale}</em>

  are on sale!

`);

模板字串(template string)是增強版的字串,用反引號(`)標識。它可以當作普通字串使用,也可以用來定義多行字串,或者在字串中嵌入變數。大括號內部可以放入任意的 JavaScript 表示式,可以進行運算,以及引用物件屬性。模板字串之中還能呼叫函式:

function fn() {

  return "Hello World";}

`foo ${fn()} bar`

    

 

let template = `
<ul>
  <% for(let i=0; i < data.supplies.length; i++) { %>
    <li><%= data.supplies[i] %></li>
  <% } %>
</ul>
`;

上面程式碼在模板字串之中,放置了一個常規模板。該模板使用<%...%>放置 JavaScript 程式碼,使用<%= ... %>輸出 JavaScript 表示式。

 

4.數值的擴充套件

Math.trunc()

Math.trunc方法用於去除一個數的小數部分,返回整數部分。

Math.trunc(4.1) // 4

Math.trunc(4.9) // 4

Math.trunc(-4.1) // -4

Math.trunc(-4.9) // -4

Math.trunc(-0.1234) // -0

對於非數值,Math.trunc內部使用Number方法將其先轉為數值。

Math.trunc('123.456') // 123

Math.trunc(true) //1

Math.trunc(false) // 0

Math.trunc(null) // 0

對於空值和無法擷取整數的值,返回NaN。

Math.trunc(NaN);      // NaN

Math.trunc('foo');    // NaN

Math.trunc();         // NaN

Math.trunc(undefined) // NaN

Math.sign()

Math.sign方法用來判斷一個數到底是正數、負數、還是零。對於非數值,會先將其轉換為數值。

它會返回五種值。

  • 引數為正數,返回+1;
  • 引數為負數,返回-1;
  • 引數為 0,返回0;
  • 引數為-0,返回-0;
  • 其他值,返回NaN。

Math.cbrt()

Math.cbrt方法用於計算一個數的立方根。

Math.cbrt(-1) // -1Math.cbrt(0)  // 0Math.cbrt(1)  // 1Math.cbrt(2)  // 1.2599210498948734

對於非數值,Math.cbrt方法內部也是先使用Number方法將其轉為數值。

Math.cbrt('8') // 2Math.cbrt('hello') // NaN

 

Math.clz32()

JavaScript 的整數使用 32 位二進位制形式表示,Math.clz32方法返回一個數的 32 位無符號整數形式有多少個前導 0。

Math.clz32(0) // 32Math.clz32(1) // 31Math.clz32(1000) // 22Math.clz32(0b01000000000000000000000000000000) // 1Math.clz32(0b00100000000000000000000000000000) // 2

Math.imul()

Math.imul方法返回兩個數以 32 位帶符號整數形式相乘的結果,返回的也是一個 32 位的帶符號整數。

Math.imul(2, 4)   // 8Math.imul(-1, 8)  // -8Math.imul(-2, -2) // 4

Math.fround()

Math.fround方法返回一個數的32位單精度浮點數形式。

對於32位單精度格式來說,數值精度是24個二進位制位(1 位隱藏位與 23 位有效位),所以對於 -224 至 224 之間的整數(不含兩個端點),返回結果與引數本身一致。

Math.hypot()

Math.hypot方法返回所有引數的平方和的平方根。

指數運算子

ES2016 新增了一個指數運算子(**)。

2 ** 2 // 4

2 ** 3 // 8

這個運算子的一個特點是右結合,而不是常見的左結合。多個指數運算子連用時,是從最右邊開始計算的。

// 相當於 2 ** (3 ** 2)

2 ** 3 ** 2

// 512

上面程式碼中,首先計算的是第二個指數運算子,而不是第一個。

指數運算子可以與等號結合,形成一個新的賦值運算子(**=)。

let a = 1.5;

a **= 2;

// 等同於 a = a * a;

let b = 4;

b **= 3;

// 等同於 b = b * b * b;

在js中的<<和>>位運算子也能使用

 

5.函式的擴充套件

ES6 允許為函式的引數設定預設值,即直接寫在引數定義的後面。

function log(x, y = 'World') {

  console.log(x, y);}

log('Hello') // Hello Worldlog('Hello', 'China') // Hello Chinalog('Hello', '') // Hello

 

ES6 引入 rest 引數(形式為...變數名),用於獲取函式的多餘引數,這樣就不需要使用arguments物件了。rest 引數搭配的變數是一個數組,該變數將多餘的引數放入陣列中。和java中的類似

function add(...values) {

  let sum = 0;

  for (var val of values) {

    sum += val;

  }

  return sum;}

add(2, 5, 3) // 10

上面程式碼的add函式是一個求和函式,利用 rest 引數,可以向該函式傳入任意數目的引數。

函式的name屬性返回函式的名字.

 

箭頭函式

var f = v => v;

// 等同於

var f = function (v) {

  return v;

};

如果箭頭函式不需要引數或需要多個引數,就使用一個圓括號代表引數部分。

var f = () => 5;

// 等同於var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;

// 等同於var sum = function(num1, num2) {

  return num1 + num2;};

如果箭頭函式不需要引數或需要多個引數,就使用一個圓括號代表引數部分。

var f = () => 5;

// 等同於

var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;

// 等同於

var sum = function(num1, num2) {

  return num1 + num2;

};

如果箭頭函式的程式碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用return語句返回。

var sum = (num1, num2) => { return num1 + num2; }

 

 

由於大括號被解釋為程式碼塊,所以如果箭頭函式直接返回一個物件,必須在物件外面加上括號,否則會報錯

// 報錯let getTempItem = id => { id: id, name: "Temp" };

// 不報錯let getTempItem = id => ({ id: id, name: "Temp" });

如果箭頭函式只有一行語句,且不需要返回值,可以採用下面的寫法,就不用寫大括號了。

let fn = () => void doesNotReturn();

箭頭函式可以與變數解構結合使用。

const full = ({ first, last }) => first + ' ' + last;

// 等同於function full(person) {

  return person.first + ' ' + person.last;}

箭頭函式有幾個使用注意點。

(1)函式體內的this物件,就是定義時所在的物件,而不是使用時所在的物件。

(2)不可以當作建構函式,也就是說,不可以使用new命令,否則會丟擲一個錯誤。

(3)不可以使用arguments物件,該物件在函式體內不存在。如果要用,可以用 rest (...var)引數代替。

(4)不可以使用yield命令,因此箭頭函式不能用作 Generator 函式。

上面四點中,第一點尤其值得注意。this物件的指向是可變的,但是在箭頭函式中,它是固定的。

function foo() {

  setTimeout(() => {

    console.log('id:', this.id);

  }, 100);}

var id = 21;

foo.call({ id: 42 });

// id: 42

上面程式碼中,setTimeout的引數是一個箭頭函式,這個箭頭函式的定義生效是在foo函式生成時,而它的真正執行要等到 100 毫秒後。如果是普通函式,執行時this應該指向全域性物件window,這時應該輸出21。但是,箭頭函式導致this總是指向函式定義生效時所在的物件(本例是{id: 42}),所以輸出的是42

箭頭函式可以讓setTimeout裡面的this,繫結定義時所在的作用域,而不是指向執行時所在的作用域。

function Timer() {

  this.s1 = 0;

  this.s2 = 0;

  // 箭頭函式  

setInterval(() => this.s1++, 1000);

  // 普通函式  

setInterval(function () {

    this.s2++;

  }, 1000);}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);

setTimeout(() => console.log('s2: ', timer.s2), 3100);

// s1: 3// s2: 0

上面程式碼中,Timer函式內部設定了兩個定時器,分別使用了箭頭函式和普通函式。前者的this繫結定義時所在的作用域(即Timer函式),後者的this指向執行時所在的作用域(即全域性物件)。所以,3100 毫秒之後,timer.s1被更新了 3 次,而timer.s2一次都沒更新。

什麼是尾呼叫?

尾呼叫(Tail Call)是函數語言程式設計的一個重要概念,本身非常簡單,一句話就能說清楚,就是指某個函式的最後一步是呼叫另一個函式。但是必須是一個函式最後的一個操作是return 另一個函式才叫尾呼叫.

比如:函式mn都屬於尾呼叫,因為它們都是函式f的最後一步操作。

function f(x) {

  if (x > 0) {

    return m(x)

  }

  return n(x);}

尾呼叫優化

function f() {

  let m = 1;

  let n = 2;

  return g(m + n);}

f();

// 等同於function f() {

  return g(3);}

f();

// 等同於g(3);

如果函式g不是尾呼叫,函式f就需要儲存內部變數mn的值、g的呼叫位置等資訊。但由於呼叫g之後,函式f就結束了,所以執行到最後一步,完全可以刪除f(x)的呼叫幀,只保留g(3)的呼叫幀。

這就叫做“尾呼叫優化”

6.陣列的擴充套件

擴充套件運算子 

擴充套件運算子(spread)是三個點(...)。它好比 rest 引數的逆運算,將一個數組轉為用逗號分隔的引數序列。

該運算子主要用於函式呼叫。

function push(array, ...items) {

  array.push(...items);}

function add(x, y) {

  return x + y;}

 

const numbers = [4, 38];add(...numbers) // 42

上面程式碼中,array.push(...items)add(...numbers)這兩行,都是函式的呼叫,它們的都使用了擴充套件運算子。該運算子將一個數組,變為引數序列。

 

7.class, extends, super

ES6提供了更接近傳統語言的寫法,引入了Class(類)這個概念。新的class寫法讓物件原型的寫法更加清晰、更像面向物件程式設計的語法,也更加通俗易懂。

 

上面程式碼首先用class定義了一個“類”,可以看到裡面有一個constructor方法,這就是構造方法,而this關鍵字則代表例項物件。簡單地說,constructor內定義的方法和屬性是例項物件自己的,而constructor外定義的方法和屬性則是所有例項物件可以共享的。  extends和super和java中的類似,就不說了

8.template string

 

大家可以先看下面一段程式碼:

$("#result").append(

  "There are <b>" + basket.count + "</b> " +

  "items in your basket, " +

  "<em>" + basket.onSale +

  "</em> are on sale!"

);

我們要用一堆的'+'號來連線文字與變數,而使用ES6的新特性模板字串``後,我們可以直接這麼來寫:

$("#result").append(`

  There are <b>${basket.count}</b> items

   in your basket, <em>${basket.onSale}</em>

  are on sale!

`);

用反引號(\)來標識起始,用${}`來引用變數,而且所有的空格和縮排都會被保留在輸出之中

9.import export

假設我們有兩個js檔案: index.js和content.js,現在我們想要在index.js中使用content.js返回的結果,我們要怎麼做呢?

//index.js

import animal from './content'

//content.js

export default 'A cat'

ES6 module的其他高階用法

//content.js

export default 'A cat'  

  export function say(){

    return 'Hello!'

}    

export const type = 'dog' 

上面可以看出,export命令除了輸出變數,還可以輸出函式,甚至是類(react的模組基本都是輸出類)

 

//index.js

import { say, type } from './content' 

 let says = say()

console.log(`The ${type} says ${says}`)  //The dog says Hello

這裡輸入的時候要注意:大括號裡面的變數名,必須與被匯入模組(content.js)對外介面的名稱相同。

如果還希望輸入content.js中輸出的預設值(default), 可以寫在大括號外面。