一、ECMAScript引出
JS基礎要會再看這個,不然容易造成基礎不牢的後果,此部分
類似JS文法糖
,請相信我不會害你的
必要的預備前置知識點: JavaScript基礎以及進階知識點 -->其中 [ 進階 ] 筆記部分需要重點掌握
1、什麼是 ECMA和ECMAScript
(European Computer Manufacturers Association)中文名稱為歐洲計算機制 造商協會, 這個組織的目标是評估、開發和認可電信和計算機标準。1994 年後該 組織改名為 Ecma 國際。
ECMA
是由 Ecma 國際通過 ECMA-262 标準化的腳本程式設計語言。
ECMAScript
2、ECMA-262
Ecma 國際制定了許多标準, 而 ECMA-262 隻是其中的一個, 所有标準清單檢視 -->點我傳送
Ⅰ-ECMA-262 曆史
ECMA-262(ECMAScript)曆史版本檢視網址: -->點我傳送
版數 年份 内容 第 1 版 1997 年 制定了語言的基本文法 第 2 版 1998 年 較小改動 第 3 版 1999 年 引入正則、異常處理、格式化輸出等。IE 開始支援 第 4 版 2007 年 過于激進, 未釋出 第 5 版 2009 年 引入嚴格模式、JSON , 擴充對象、數組、原型、字元串、日期方法
第 6 版
2015 年
子產品化、面向對象文法、 Promise、箭頭函數、let、 const、數組解構指派等等
因為釋出内容很多,堪稱裡程碑,是以我們目前通常主要學這個
第 7 版 2016 年 幂運算符、數組擴充、 Async/await 關鍵字 第 8 版 2017 年 Async/await、字元串擴充 第 9 版 2018 年 對象解構指派、正則擴充 第 10 版 2019 年 擴充對象、數組方法 ES.next 動态指向下一個版本
後續學到我會進行補充
是以有些文章上提到的
注:從 ES6 開始, 每年釋出一個版本, 版本号比年份最後一位大 1
(實質上是
ES7
)、
ES2016
(實質上是
ES8
)、
ES2017
(實質上是
ES9
)、
ES2018
(實質上是
ES10
)、
ES2019
(實質上是
ES11
), 實質上都是一些不規範的概念。從ES1到ES6 , 每個标準都是花了好幾年甚至十多年才制定下來, 你一個ES6到ES7 , ES7到ES8 , 才用了一年, 按照這樣的定義下去, 那不是很快就ES20了。用正确的概念來說ES6目前涵蓋了ES2015、ES2016、ES2017、ES2018、ES2019、ES2020。
ES2020
Ⅱ-誰在維護 ECMA-262
TC39(Technical Committee 39)是推進 ECMAScript 發展的委員會。其會員都是公司( 其中主要是浏覽器廠商
:有蘋果、谷歌、微軟、因特爾等)。TC39 定期 召開會議, 會議由會員公司的代表與特邀專家出席
3、為什麼要重點學習 ES6
- ES6 的版本變動内容最多, 具有裡程碑意義
- ES6 加入許多新的文法特性, 程式設計實作更簡單、高效
- ES6 是前端發展趨勢, 就業必備技能
- 實際上ES系列的知識點基本上都要掌握,才能寫出逼格更高的代碼🐶
4、ES6 相容性
可以檢視gitHub上的這個圖–>點我傳送
二、ECMASript 6 新特性
想要檢視更權威的官方ES6文檔,可以看阮一峰的ES6文檔,本人當初對其進行了摘錄放至此處友善查閱–>ES6資料文檔摘錄
此處ES6部分筆記主要為:
,加上學習ES6時筆記、個人心得體會以及在相當一段工作時間中覺得常用或者是需要重點學習的了解整合
查閱的資料部落格整合摘錄
1、ES6更新的内容概括
表達式:聲明、解構指派
内置對象:字元串擴充、數值擴充、對象擴充、數組擴充、函數擴充、正則擴充、Symbol、Set、Map、Proxy、Reflect
語句與運算:Class、Module、Iterator
異步程式設計:Promise、Generator、Async
2、let和const指令、作用域
注意:
不存在變量提升
指令會發生“變量提升”現象, 即變量可以在聲明之前使用, 值為
var
undefined
。這種現象多多少少是有些奇怪的, 按照一般的邏輯, 變量應該在聲明語句之後才可以使用。
為了糾正這種現象,
、
let
指令改變了文法行為, 它所聲明的變量一定要在聲明後使用, 否則報錯
const
Ⅰ-概括與總結
聲明
- const指令:聲明常量
- let指令:聲明變量
作用
- 作用域
- 全局作用域
- 函數作用域:
function() {}
- 塊級作用域:
{}
- 作用範圍
在全局代碼中執行
var指令
和
const指令
隻能在代碼塊中執行
let指令
- 指派使用
聲明常量後必須立馬指派
const指令
聲明變量後可立馬指派或使用時指派
let指令
- 聲明方法:
、
var
、
const
、
let
、
function
、
class
import
重點難點下一節為指派解構的概括總結 -->點我傳送
- 不允許重複聲明
- 未定義就使用會報錯:
和
const指令
不存在變量提升
let指令
- 暫時性死區:在代碼塊内使用
和
const指令
聲明變量之前, 該變量都不可用
let指令
Ⅱ-let關鍵字指令
let 關鍵字用來聲明變量, 使用 let 聲明的變量有幾個特點:應用場景:聲明重複指派的變量時可以用這個,如果你不是要求很高的話,基本上都能用let進行聲明(var聲明的可以都用這個替代了)
- 不允許重複聲明
- 塊級作用域
- 不存在變量提升
- 不影響作用域鍊
Ⅲ-const關鍵字指令
const 關鍵字用來聲明常量 , const 聲明有以下特點:注意:
- 不允許重複聲明
值不允許修改
- 不存在變量提升
- 塊級作用域
- 聲明必須賦初始值
- 辨別符一般為大寫
對象屬性修改和數組元素變化不會觸發 const 錯誤
應用場景:聲明對象類型、确定不會再次指派的變量使用 const , 其他的可以用let實際上保證的,
const
并不是變量的值不得改動, 而是變量指向的那個記憶體位址所儲存的資料不得改動
。
對于簡單類型的資料(數值、字元串、布爾值), 值就儲存在變量指向的那個記憶體位址, 是以等同于常量。但對于複合類型的資料(主要是對象和數組), 變量指向的記憶體位址, 儲存的隻是一個指向實際資料的指針,
隻能保證這個指針是固定的(即總是指向另一個固定的位址), 至于它指向的資料結構是不是可變的, 就完全不能控制了。是以, 将一個對象聲明為常量必須非常小心。
const
Ⅳ-ES6 聲明變量的六種方法
ES5 隻有兩種聲明變量的方法:指令和
var
指令。ES6 除了添加
function
和
let
指令, 後面還會提到, 另外兩種聲明變量的方法:
const
指令和
import
指令。是以 , ES6 一共有 6 種聲明變量的方法。
class
Ⅴ-塊級作用域
① 為什麼需要塊級作用域?
ES5 隻有全局作用域和函數作用域, 沒有塊級作用域, 這帶來很多不合理的場景。
第一種場景, 内層變量可能會覆寫外層變量。
上面代碼的原意是,var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined
代碼塊的外部使用外層的
if
變量, 内部使用内層的
tmp
變量。但是, 函數 [
tmp
] 執行後, 輸出結果為
f
, 原因在于變量提升, 導緻内層的
undefined
變量覆寫了外層的
tmp
tmp
變量。
第二種場景, 用來計數的循環變量洩露為全局變量。
上面代碼中, 變量var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]);} console.log(i); // 5
隻用來控制循環, 但是循環結束後, 它并沒有消失, 洩露成了全局變量。
i
② ES6 的塊級作用域
實際上為 JavaScript 新增了塊級作用域。
let
上面的函數有兩個代碼塊, 都聲明了變量function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
, 運作後輸出 5。這表示外層代碼塊不受内層代碼塊的影響。如果兩次都使用
n
定義變量
var
n
, 最後輸出的值才是 10。
ES6 允許塊級作用域的任意嵌套。
上面代碼使用了一個五層的塊級作用域, 每一層都是一個單獨的作用域。{{{{ {let insane = 'Hello World'} console.log(insane); // 報錯 因為外層不能取到内層資料 }}}};
第四層作用域無法讀取第五層作用域的内部變量
。
内層作用域可以定義外層作用域的同名變量。
塊級作用域的出現, 實際上使得獲得廣泛應用的匿名立即執行函數表達式(匿名 IIFE)不再必要了。–>對于IIFE不懂的可以看本人JS進階筆記,點我跳轉{{{{ let insane = 'Hello World'; {let insane = 'Hello World'} }}}};
// IIFE 寫法 (function () { var tmp = ...; ... }()); // 塊級作用域寫法 { let tmp = ...; ... }
③ 塊級作用域與函數聲明
函數能不能在塊級作用域之中聲明?這是一個相當令人混淆的問題。
ES5 規定, 函數隻能在頂層作用域和函數作用域之中聲明, 不能在塊級作用域聲明。
// 情況一 if (true) { function f() {} } // 情況二 try { function f() {} } catch(e) { // ... }
上面兩種函數聲明, 根據 ES5 的規定都是非法的。
但是, 浏覽器沒有遵守這個規定, 為了相容以前的舊代碼, 還是支援在塊級作用域之中聲明函數, 是以上面兩種情況實際都能運作, 不會報錯。
ES6 引入了塊級作用域, 明确允許在塊級作用域之中聲明函數。ES6 規定, 塊級作用域之中, 函數聲明語句的行為類似于
, 在塊級作用域之外不可引用。
let
上面代碼在 ES5 中運作, 會得到“I am inside!”, 因為在function f() { console.log('I am outside!'); } (function () { // 重複聲明一次函數f if (false) { function f() { console.log('I am inside!'); }} f(); }());
内聲明的函數 [ f ] 會被提升到函數頭部, 實際運作的代碼如下。
if
ES6 就完全不一樣了, 理論上會得到“I am outside!”。因為塊級作用域内聲明的函數類似于// ES5 環境 function f() { console.log('I am outside!'); } (function () { function f() { console.log('I am inside!'); } if (false) {} f(); }());
, 對作用域之外沒有影響。但是, 如果你真的在 ES6 浏覽器中運作一下上面的代碼, 是會報錯的, 這是為什麼呢?
let
// 浏覽器的 ES6 環境 function f() { console.log('I am outside!'); } (function () { // 重複聲明一次函數f if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
上面的代碼在 ES6 浏覽器中, 都會報錯。
原來, 如果改變了塊級作用域内聲明的函數的處理規則, 顯然會對老代碼産生很大影響。為了減輕是以産生的不相容問題 , ES6 在附錄 B裡面規定, 浏覽器的實作可以不遵守上面的規定, 有自己的行為方式。
注意, 上面三條規則隻對 ES6 的浏覽器實作有效, 其他環境的實作不用遵守, 還是将塊級作用域的函數聲明當作
- 允許在塊級作用域内聲明函數。
- 函數聲明類似于
, 即會提升到全局作用域或函數作用域的頭部。
var
- 同時, 函數聲明還會提升到所在的塊級作用域的頭部。
let
處理。
根據這三條規則, 浏覽器的 ES6 環境中, 塊級作用域内聲明的函數, 行為類似于
聲明的變量。上面的例子實際運作的代碼如下。
var
考慮到環境導緻的行為差異太大, 應該避免在塊級作用域内聲明函數。如果确實需要, 也應該寫成函數表達式, 而不是函數聲明語句。// 浏覽器的 ES6 環境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); }} f(); }()); // Uncaught TypeError: f is not a function
另外, 還有一個需要注意的地方。ES6 的塊級作用域必須有大括号, 如果沒有大括号 , JavaScript 引擎就認為不存在塊級作用域。// 塊級作用域内部的函數聲明語句, 建議不要使用 { let a = 'secret'; function f() { return a; } } // 塊級作用域内部, 優先使用函數表達式 { let a = 'secret'; let f = function () { return a; }; }
上面代碼中, 第一種寫法沒有大括号, 是以不存在塊級作用域, 而// 第一種寫法, 報錯 if (true) let x = 1; // 第二種寫法, 不報錯 if (true) { let x = 1; }
let
隻能出現在目前作用域的頂層, 是以報錯。第二種寫法有大括号, 是以塊級作用域成立。
函數聲明也是如此, 嚴格模式下, 函數隻能聲明在目前作用域的頂層。
// 不報錯 'use strict'; if (true) { function f() {} } // 報錯 'use strict'; if (true) function f() {}
3、指派解構
ES6 允許按照一定模式,
從數組和對象中提取值, 對變量進行指派
, 這被稱為解構(Destructuring)。
本質上, 這種寫法屬于“
”, 隻要等号兩邊的模式相同, 左邊的變量就會被賦予對應的值
模式比對
Ⅰ-概括總結
- 字元串解構:
const [a, b, c, d, e] = "hello"
- 數值解構:
const { toString: s } = 123
- 布爾解構:
const { toString: b } = true
- 對象解構
- 形式:
const { x, y } = { x: 1, y: 2 }
- 預設:
const { x, y = 2 } = { x: 1 }
- 改名:
const { x, y: z } = { x: 1, y: 2 }
- 數組解構
- 規則:資料結構具有
可采用數組形式的解構指派
Iterator接口
- 形式:
const [x, y] = [1, 2]
- 預設:
const [x, y = 2] = [1]
- 函數參數解構
- 數組解構:
function Func([x = 0, y = 1]) {}
- 對象解構:
function Func({ x = 0, y = 1 } = {}) {}
應用場景
- 交換變量值:
[x, y] = [y, x]
- 傳回函數多個值:
const [x, y, z] = Func()
- 定義函數參數:
Func([1, 2])
- 提取JSON資料:
const { name, version } = packageJson
- 定義函數參數預設值:
function Func({ x = 1, y = 2 } = {}) {}
- 周遊Map結構:
for (let [k, v] of Map) {}
- 輸入子產品指定屬性和方法:
const { readFile, writeFile } = require("fs")
****
重點難點
下一節為字元串的拓展概括 -->點我傳送
- 比對模式:隻要等号兩邊的模式相同, 左邊的變量就會被賦予對應的值
- 解構指派規則:隻要等号右邊的值不是對象或數組, 就先将其轉為對象
- 解構預設值生效條件:屬性值嚴格等于
undefined
- 解構遵循比對模式
- 解構不成功時變量的值等于
undefined
和
undefined
無法轉為對象, 是以無法進行解構
null
Ⅱ-基本用法
① 基本用法舉例
以前, 為變量指派, 隻能直接指定值。let a = 1; let b = 2; let c = 3;
ES6 允許寫成下面這樣。
上面代碼表示, 可以從數組中提取值, 按照對應位置, 對變量指派。
本質上, 這種寫法屬于“模式比對”, 隻要等号兩邊的模式相同, 左邊的變量就會被賦予對應的值。下面是一些使用嵌套數組進行解構的例子。
如果解構不成功, 變量的值就等于let [foo, [[bar], baz]] = [1, [[2], 3]];//foo : 1 bar : 2 baz : 3 let [ , , third] = ["foo", "bar", "baz"];//third : "baz" let [x, , y] = [1, 2, 3];//x : 1 y : 3 let [head, ...tail] = [1, 2, 3, 4];//head : 1 tail : [2, 3, 4] let [x, y, ...z] = ['a'];//x : "a" y : undefined z : []
。
undefined
以上兩種情況都屬于解構不成功,let [foo] = []; let [bar, foo] = [1];
的值都會等于
foo
undefined
。
另一種情況是不完全解構, 即等号左邊的模式, 隻比對一部分的等号右邊的數組。這種情況下, 解構依然可以成功。
let [x, y] = [1, 2, 3];//x : 1 y : 2 let [a, [b], d] = [1, [2, 3], 4];//a : 1 b : 2 d : 4
上面兩個例子, 都屬于不完全解構, 但是可以成功。
如果等号的右邊不是數組(或者嚴格地說, 不是可周遊的結構, 參見《Iterator》一章), 那麼将會報錯。
上面的語句都會報錯, 因為等号右邊的值, 要麼轉為對象以後不具備 Iterator 接口(前五個表達式), 要麼本身就不具備 Iterator 接口(最後一個表達式)。// 報錯 let [foo] = 1; let [foo] = false; let [foo] = NaN; let [foo] = undefined; let [foo] = null; let [foo] = {};
。
對于 Set 結構, 也可以使用數組的解構指派
事實上, 隻要某種資料結構具有 Iterator 接口, 都可以采用數組形式的解構指派。let [x, y, z] = new Set(['a', 'b', 'c']); x // "a"
上面代碼中,function* fibs() { let a = 0; let b = 1; while (true) { yield a; [a, b] = [b, a + b]; } } let [first, second, third, fourth, fifth, sixth] = fibs(); sixth // 5
是一個 Generator 函數(詳見《Generator 函數》), 原生具有 Iterator 接口。解構指派會依次從這個接口擷取值。
fibs
② 預設值
解構指派允許指定預設值。注意 , ES6 内部使用嚴格相等運算符(let [foo = true] = [];//foo = true let [x, y = 'b'] = ['a']; // x='a', y='b' let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
), 判斷一個位置是否有值。是以, 隻有當一個數組成員嚴格等于
===
, 預設值才會生效。
undefined
上面代碼中, 如果一個數組成員是let [x = 1] = [undefined];//x = 1 let [x = 1] = [null];//x = null
, 預設值就不會生效, 因為
null
不嚴格等于
null
undefined
。
如果預設值是一個表達式, 那麼這個表達式是惰性求值的, 即隻有在用到的時候, 才會求值。
上面代碼中, 因為function f() { console.log('aaa');} let [x = f()] = [1];
能取到值, 是以函數 [ f ] 根本不會執行。上面的代碼其實等價于下面的代碼。
x
預設值可以引用解構指派的其他變量, 但該變量必須已經聲明。let x; if ([1] === undefined) { x = f()} else { x = [1]; }
上面最後一個表達式之是以會報錯, 是因為let [x = 1, y = x] = []; // x=1; y=1 let [x = 1, y = x] = [2]; // x=2; y=2 let [x = 1, y = x] = [1, 2]; // x=1; y=2 let [x = y, y = 1] = []; // ReferenceError: y is not defined
用
x
做預設值時,
y
還沒有聲明。
y
③ ES6小知識點: 連續指派解構
+重命名
連續指派解構
此寫法也是本人常用寫法,挺好用的let obj = {a:{b:1}} const {a} = obj; //傳統解構指派 const {a:{b}} = obj; //連續解構指派 const {a:{b:value}} = obj; //連續解構指派+重命名
Ⅲ-對象的指派解構
此處應用的非常多
,需要多查閱
① 基本用法
解構不僅可以用于數組, 還可以用于對象。
對象的解構與數組有一個重要的不同。
數組的元素是按次序排列的, 變量的取值由它的位置決定;而對象的屬性沒有次序, 變量必須與屬性同名, 才能取到正确的值
上面代碼的第一個例子, 等号左邊的兩個變量的次序, 與等号右邊兩個同名屬性的次序不一緻, 但是對取值完全沒有影響。第二個例子的變量沒有對應的同名屬性, 導緻取不到值, 最後等于let { bar, foo } = { foo: 'aaa', bar: 'bbb' };//foo = "aaa" ; bar = "bbb" let { baz } = { foo: 'aaa', bar: 'bbb' };//baz = undefined
undefined
。
如果解構失敗, 變量的值等于
undefined
。
上面代碼中, 等号右邊的對象沒有
屬性, 是以變量
foo
取不到值, 是以等于
foo
undefined
。
對象的解構指派, 可以很友善地将現有對象的方法, 指派到某個變量。
上面代碼的例一将// 例一 let { log, sin, cos } = Math; // 例二 const { log } = console; log('hello') // hello
對象的對數、正弦、餘弦三個方法, 指派到對應的變量上, 使用起來就會友善很多。例二将
Math
指派到
console.log
log
變量。
如果變量名與屬性名不一緻, 必須寫成下面這樣–>
取别名
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
。
與數組一樣, 解構也可以用于嵌套結構的對象。
注意, 這時let obj = { p: ['Hello', { y: 'World' }] }; let { p: [x, { y }] } = obj; //x == "Hello" //y == "World"
是模式, 不是變量, 是以不會被指派。如果
p
也要作為變量指派, 可以寫成下面這樣。
p
下面是另一個例子。let obj = { p: [ 'Hello', { y: 'World' }] }; let { p, p: [x, { y }] } = obj; //x == "Hello" //y == "World" //p == ["Hello", {y: "World"}]
上面代碼有三次解構指派, 分别是對const node = { loc: { start: { line: 1, column: 5 } } }; let { loc, loc: { start }, loc: { start: { line }} } = node; //line == 1 //loc == Object {start: Object} //start == Object {line: 1, column: 5}
、
loc
、
start
三個屬性的解構指派。注意, 最後一次對
line
屬性的解構指派之中, 隻有
line
是變量,
line
和
loc
start
都是模式, 不是變量。
下面是嵌套指派的例子。–>
:
注意:外部包着一層()
如果解構模式是嵌套的對象, 而且子對象所在的父屬性不存在, 那麼将會報錯。let obj = {}; let arr = []; ({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true }); //因為 JavaScript 引擎會将`{x}`了解成一個代碼塊, 進而發生文法錯誤。`隻有不将大括号寫在行首`, 避免 JavaScript 将其解釋為代碼塊, 才能解決這個問題。 //obj == {prop:123} //arr == [true]
上面代碼中, 等号左邊對象的// 報錯 let {foo: {bar}} = {baz: 'baz'};
屬性, 對應一個子對象。該子對象的
foo
屬性, 解構時會報錯。原因很簡單, 因為
bar
這時等于
foo
undefined
, 再取子屬性就會報錯。
注意, 對象的解構指派可以取到繼承的屬性。
上面代碼中, 對象const obj1 = {}; const obj2 = { foo: 'bar' }; Object.setPrototypeOf(obj1, obj2);//Object.setPrototypeOf() 方法設定一個指定的對象的原型 ( 即, 内部[[Prototype]]屬性)到另一個對象或 null const { foo } = obj1; foo // "bar"
的原型對象是
obj1
。
obj2
屬性不是
foo
自身的屬性, 而是繼承自
obj1
obj2
的屬性, 解構指派可以取到這個屬性。
注:
詳解,不知道此方法的同學們看這裡 -->點我傳送
Object.setPrototypeOf()
② 預設值
對象的解構也可以指定預設值。預設值生效的條件是, 對象的屬性值嚴格等于var {x = 3} = {};//x == 3 var {x, y = 5} = {x: 1}; //x == 1 //y == 5 var {x: y = 3} = {};//y == 3 var {x: y = 3} = {x: 5};//y == 5 var { message: msg = 'Something went wrong' } = {};//msg == "Something went wrong"
。
undefined
上面代碼中, 屬性var {x = 3} = {x: undefined};//x == 3 var {x = 3} = {x: null};//x == null
等于
x
, 因為
null
與
null
不嚴格相等, 是以是個有效的指派, 導緻預設值
undefined
不會生效。–>[原因上面講過](#② 預設值)
3
③ 注意點
(1)如果要将一個已經聲明的變量用于解構指派, 必須非常小心。上面代碼的寫法會報錯, 因為 JavaScript 引擎會将// 錯誤的寫法 let x; {x} = {x: 1}; // SyntaxError: syntax error
了解成一個代碼塊, 進而發生文法錯誤。
{x}
, 避免 JavaScript 将其解釋為代碼塊, 才能解決這個問題。
隻有不将大括号寫在行首
上面代碼将整個解構指派語句,// 正确的寫法 let x; ({x} = {x: 1});
放在一個圓括号裡面, 就可以正确執行
。關于圓括号與解構指派的關系, 參見下文。
(2)解構指派允許等号左邊的模式之中, 不放置任何變量名。是以, 可以寫出非常古怪的指派表達式。
({} = [true, false]); ({} = 'abc'); ({} = []);
上面的表達式雖然毫無意義, 但是文法是合法的, 可以執行。
(3)
。
由于數組本質是特殊的對象, 是以可以對數組進行對象屬性的解構
上面代碼對數組進行對象解構。數組let arr = [1, 2, 3]; let {0 : first, [arr.length - 1] : last} = arr; //first == 1 //last == 3
的 鍵對應的值是
arr
,
1
就是
[arr.length - 1]
鍵, 對應的值是
2
。方括号這種寫法, 屬于“屬性名表達式”(詳見《對象的擴充》)。
3
Ⅳ-字元串的指派結構
字元串也可以解構指派。這是因為此時, 字元串被轉換成了一個類似數組的對象。類似數組的對象都有一個const [a, b, c, d, e] = 'hello'; //a == "h" ;b == "e" ; c == "l" ; d == "l" ;e == "o"
屬性, 是以還可以對這個屬性解構指派。
length
Ⅴ-數值和布爾值的解構指派
解構指派時, 如果等号右邊是數值和布爾值, 則會先轉為對象。上面代碼中, 數值和布爾值的包裝對象都有let {toString: s} = 123; s === Number.prototype.toString // true let {toString: s} = true; s === Boolean.prototype.toString // true
屬性, 是以變量
toString
s
都能取到值。
解構指派的規則是, 隻要等号右邊的值不是對象或數組, 就先将其轉為對象。由于
和
undefined
無法轉為對象, 是以對它們進行解構指派, 都會報錯。
null
let { prop: x } = undefined; // TypeError let { prop: y } = null; // TypeError
Ⅵ-函數參數的解構指派
函數的參數也可以使用解構指派。上面代碼中, 函數function add([x, y]){ return x + y; } add([1, 2]); // 3
的參數表面上是一個數組, 但在傳入參數的那一刻, 數組參數就被解構成變量
add
和
x
。對于函數内部的代碼來說, 它們能感受到的參數就是
y
和
x
y
。
下面是另一個例子。
函數參數的解構也可以使用預設值。[[1, 2], [3, 4]].map(([a, b]) => a + b); // [ 3, 7 ]
上面代碼中, 函數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
等于預設值。
注意, 下面的寫法會得到不一樣的結果。
上面代碼是為函數function move({x, y} = { x: 0, y: 0 }) { return [x, y]; } move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, undefined] move({}); // [undefined, undefined] move(); // [0, 0]
的參數指定預設值, 而不是為變量
move
和
x
指定預設值, 是以會得到與前一種寫法不同的結果。
y
就會觸發函數參數的預設值。
undefined
[1, undefined, 3].map((x = 'yes') => x); // [ 1, 'yes', 3 ]
Ⅶ-圓括号問題
解構指派雖然很友善, 但是解析起來并不容易。對于編譯器來說,
一個式子到底是模式, 還是表達式
, 沒有辦法從一開始就知道, 必須解析到(或解析不到)等号才能知道。
由此帶來的問題是, 如果模式中出現圓括号怎麼處理。ES6 的規則是, 隻要有可能導緻解構的歧義, 就不得使用圓括号。
但是, 這條規則實際上不那麼容易辨識, 處理起來相當麻煩。是以, 建議隻要有可能, 就不要在模式中放置圓括号。
① 不能使用圓括号的情況
以下三種解構指派不得使用圓括号。
(1)
語句
變量聲明
上面 6 個語句都會報錯,// 全部報錯 let [(a)] = [1]; let {x: (c)} = {}; let ({x: c}) = {}; let {(x: c)} = {}; let {(x): c} = {}; let { o: ({ p: p }) } = { o: { p: 2 } };
因為它們都是變量聲明語句
, 模式不能使用圓括号。
(2)函數參數
函數參數也屬于變量聲明, 是以不能帶有圓括号。
(3)指派語句的模式// 報錯 function f([(z)]) { return z; } // 報錯 function f([z,(x)]) { return x; }
上面代碼将整個模式放在圓括号之中, 導緻報錯。// 全部報錯 ({ p: a }) = { p: 42 }; ([a]) = [5];
上面代碼将一部分模式放在圓括号之中, 導緻報錯。// 報錯 [({ p: a }), { x: c }] = [{}, {}];
② 可以使用圓括号的情況
可以使用圓括号的情況隻有一種:指派語句的非模式部分, 可以使用圓括号。上面三行語句都可以正确執行, 因為[(b)] = [3]; // 正确 ({ p: (d) } = {}); // 正确 [(parseInt.prop)] = [3]; // 正确
;其次它們的圓括号都不屬于模式的一部分。第一行語句中, 模式是取數組的第一個成員, 跟圓括号無關;第二行語句中, 模式是
首先它們都是指派語句, 而不是聲明語句
, 而不是
p
;第三行語句與第一行語句的性質一緻。
d
Ⅷ-具體應用場景舉例
變量的解構指派用途很多
① 交換變量的值
上面代碼交換變量let x = 1; let y = 2; [x, y] = [y, x];
和
x
的值, 這樣的寫法不僅簡潔, 而且易讀, 語義非常清晰。
y
② 從函數傳回多個值
函數隻能傳回一個值, 如果要傳回多個值, 隻能将它們放在數組或對象裡傳回。有了解構指派, 取出這些值就非常友善。// 傳回一個數組 function example() { return [1, 2, 3]; } let [a, b, c] = example(); // 傳回一個對象 function example() { return { foo: 1,bar: 2}; } let { foo, bar } = example();
③ 函數參數的定義
解構指派可以友善地将一組參數與變量名對應起來。// 參數是一組有次序的值 function f([x, y, z]) { ... } f([1, 2, 3]); // 參數是一組無次序的值 function f({x, y, z}) { ... } f({z: 3, y: 2, x: 1});
④ 提取 JSON 資料
解構指派對提取 JSON 對象中的資料, 尤其有用。上面代碼可以快速提取 JSON 資料的值。let jsonData = { id: 42, status: "OK", data: [867, 5309] }; let { id, status, data: number } = jsonData; console.log(id, status, number); // 42, "OK", [867, 5309]
⑤ 函數參數的預設值
指定參數的預設值, 就避免了在函數體内部再寫jQuery.ajax = function (url, { async = true, beforeSend = function () {}, cache = true, complete = function () {}, crossDomain = false, global = true, // ... more config } = {}) { // ... do stuff };
這樣的語句。
var foo = config.foo || 'default foo';
⑥ 周遊 Map 結構
任何部署了 Iterator 接口的對象, 都可以用循環周遊。Map 結構原生支援 Iterator 接口, 配合變量的解構指派, 擷取鍵名和鍵值就非常友善。
for...of
如果隻想擷取鍵名, 或者隻想擷取鍵值, 可以寫成下面這樣。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) { // ... }
⑦ 輸入子產品的指定方法
加載子產品時, 往往需要指定輸入哪些方法。解構指派使得輸入語句非常清晰。
4、字元串的拓展
Ⅰ-概括總結
- Unicode表示法:
表示Unicode字元(
大括号包含
或
\u{0xXX}
)
\u{0XXX}
- 字元串周遊:可通過
周遊字元串
for-of
- 字元串模闆:可單行可多行可插入變量的增強版字元串
- 标簽模闆:函數參數的特殊調用
- String.raw():傳回把字元串所有變量替換且對斜杠進行轉義的結果
- String.fromCodePoint():傳回碼點對應字元
- codePointAt():傳回字元對應碼點(
的逆操作)
String.fromCodePoint()
- normalize():把字元的不同表示方法統一為同樣形式, 傳回
(Unicode正規化)
新字元串
- repeat():把字元串重複n次, 傳回
新字元串
- matchAll():傳回正規表達式在字元串的所有比對
- includes():是否存在指定字元串
- startsWith():是否存在字元串頭部指定字元串
- endsWith():是否存在字元串尾部指定字元串
- 以上擴充方法均可作用于由
的
4個位元組儲存
上
Unicode字元
Ⅱ-模闆字元串
模闆字元串(template string)是增強版的字元串, 用反引号[ ` ]辨別。它可以當作普通字元串使用, 也可以用來定義多行字元串, 或者在字元串中嵌入變量。
嵌入變量使用[
]:如果大括号中的值不是字元串, 将按照一般的規則轉為字元串。比如, 大括号中是一個對象, 将預設調用對象的
${變量名}
方法。如果大括号内部是一個字元串, 将會原樣輸出。
toString
① 字元串中可以出現換行符
字元串中可以出現換行符:如果使用模闆字元串表示多行字元串, 所有的空格和縮進都會被保留在輸出之中。//代碼中, 所有模闆字元串的空格和換行, 都是被保留的, 比如`<ul>`标簽前面會有一個換行。如果你不想要這個換行, 可以使用`trim`方法消除它。 $('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim());
② 可以使用 ${xxx} 形式輸出變量
function authorize(user, action) { if (!user.hasPrivilege(action)) { throw new Error( // 傳統寫法為 // 'User ' // + user.name // + ' is not authorized to do ' // + action // + '.' `User ${user.name} is not authorized to do ${action}.`); } }
③ 大括号内部可以放入任意的 JavaScript 表達式
括号内部可以放入任意的 JavaScript 表達式, 可以進行運算, 以及引用對象屬性。let x = 1; let y = 2; `${x} + ${y} = ${x + y}`// "1 + 2 = 3" `${x} + ${y * 2} = ${x + y * 2}`// "1 + 4 = 5" let obj = {x: 1, y: 2}; `${obj.x + obj.y}`// "3"
④ 模闆字元串之中還能調用函數。
function fn() { return "Hello World";} `foo ${fn()} bar` // foo Hello World bar
⑤ 字元串嵌套
上面代碼中, 模闆字元串的變量之中, 又嵌入了另一個模闆字元串, 使用方法如下。const tmpl = addrs => ` <table> ${addrs.map(addr => ` <tr><td>${addr.first}</td></tr> <tr><td>${addr.last}</td></tr> `).join('')} </table> `;
如果需要引用模闆字元串本身, 在需要時執行, 可以寫成函數。const data = [ { first: '<Jane>', last: 'Bond' }, { first: 'Lars', last: '<Croft>' }, ]; console.log(tmpl(data)); /**下面是列印結果 <table> <tr><td><Jane></td></tr> <tr><td>Bond</td></tr> <tr><td>Lars</td></tr> <tr><td><Croft></td></tr> </table> */
上面代碼中, 模闆字元串寫成了一個函數的傳回值。執行這個函數, 就相當于執行這個模闆字元串了。let func = (name) => `Hello ${name}!`; func('Jack') // "Hello Jack!"
Ⅲ-标簽模闆
模闆字元串的功能, 不僅僅是上面這些。它可以緊跟在一個函數名後面, 該函數将被調用來處理這個模闆字元串。這被稱為“”功能(tagged template`)。 -->反正我是很少用到,可閱讀性較差
标簽模闆
alert`hello` // 等同于 alert(['hello'])
① 簡單執行個體
标簽模闆其實不是模闆, 而是函數調用的一種特殊形式。
[标簽]指的就是函數
, 緊跟在後面的模闆字元串就是它的參數。
但是, 如果模闆字元裡面有變量, 就不是簡單的調用了, 而是會将模闆字元串先處理成多個參數, 再調用函數。
上面代碼中, 模闆字元串前面有一個辨別名let a = 5; let b = 10; tag`Hello ${ a + b } world ${ a * b }`; // 等同于 tag(['Hello ', ' world ', ' ' ], 15, 50);
, 它是一個函數。整個表達式的傳回值, 就是
tag
tag
函數處理模闆字元串後的傳回值。
函數
依次會接收到多個參數。
tag
function tag(stringArr, value1, value2){ // ... } // 等同于 function tag(stringArr, ...values){ // ... }
函數的第一個參數是一個數組,
tag
, 也就是說, 變量替換隻發生在數組的第一個成員與第二個成員之間、第二個成員與第三個成員之間, 以此類推。
該數組的成員是模闆字元串中那些沒有變量替換的部分
函數的其他參數, 都是模闆字元串各個變量被替換後的值。由于本例中, 模闆字元串含有兩個變量, 是以
tag
會接受到
tag
和
value1
兩個參數。
value2
函數所有參數的實際值如下。
tag
也就是說,
- 第一個參數:
['Hello ', ' world ', '']
- 第二個參數: 15
- 第三個參數:50
tag
函數實際上以下面的形式調用。
我們可以按照需要編寫
函數的代碼。下面是
tag
函數的一種寫法, 以及運作結果。
tag
let a = 5; let b = 10; function tag(s, v1, v2) { console.log(s[0]); console.log(s[1]); console.log(s[2]); console.log(v1); console.log(v2); return "OK"; } tag`Hello ${ a + b } world ${ a * b}`; // "Hello " // " world " // "" // 15 // 50 // "OK"
② 稍微複雜的栗子
上面這個例子展示了, 如何将各個參數按照原來的位置拼合回去。let total = 30; let msg = passthru`The total is ${total} (${total*1.05} with tax)`; function passthru(literals) { let result = ''; let i = 0; while (i < literals.length) { result += literals[i++]; if (i < arguments.length) { result += arguments[i]; } } return result; } msg // "The total is 30 (31.5 with tax)"
函數采用 rest 參數的寫法如下。
passthru
“标簽模闆”的一個重要應用, 就是過濾 HTML 字元串, 防止使用者輸入惡意内容。function passthru(literals, ...values) { let output = ""; let index; for (index = 0; index < values.length; index++) { output += literals[index] + values[index]; } output += literals[index] return output; }
上面代碼中,let message = SaferHTML`<p>${sender} has sent you a message.</p>`; function SaferHTML(templateData) { let s = templateData[0]; for (let i = 1; i < arguments.length; i++) { let arg = String(arguments[i]); // Escape special characters in the substitution. s += arg.replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">"); // Don't escape special characters in the template. s += templateData[i]; } return s; }
變量往往是使用者提供的, 經過
sender
函數處理, 裡面的特殊字元都會被轉義。
SaferHTML
let sender = '<script>alert("abc")</script>'; // 惡意代碼 let message = SaferHTML`<p>${sender} has sent you a message.</p>`; message // <p><script>alert("abc")</script> has sent you a message.</p>
③ 用作多語言轉換(國際化處理)
标簽模闆的另一個應用, 就是多語言轉換(國際化處理)。模闆字元串本身并不能取代 Mustache 之類的模闆庫, 因為沒有條件判斷和循環處理功能, 但是通過标簽函數, 你可以自己添加這些功能。i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!` // "歡迎通路xxx , 您是第xxxx位通路者!"
除此之外, 你甚至可以使用标簽模闆, 在 JavaScript 語言之中嵌入其他語言。// 下面的hashTemplate函數 // 是一個自定義的模闆處理函數 let libraryHtml = hashTemplate` <ul> #for book in ${myBooks} <li><i>#{book.title}</i> by #{book.author}</li> #end </ul> `;
上面的代碼通過jsx` <div> <input ref='input' onChange='${this.handleChange}' defaultValue='${this.state.value}' /> ${this.state.value} </div> `
jsx
函數, 将一個 DOM 字元串轉為 React 對象。
下面則是一個假想的例子, 通過
函數, 在 JavaScript 代碼之中運作 Java 代碼。
java
模闆處理函數的第一個參數(模闆字元串數組), 還有一個java` class HelloWorldApp { public static void main(String[] args) { System.out.println("Hello World!"); // Display the string. } } ` HelloWorldApp.main();
屬性。
raw
上面代碼中,console.log`123` // ["123", raw: Array[1]]
接受的參數, 實際上是一個數組。該數組有一個
console.log
raw
屬性, 儲存的是轉義後的原字元串。
請看下面的例子。
上面代碼中,tag`First line\nSecond line` function tag(strings) { console.log(strings.raw[0]); // strings.raw[0] 為 "First line\\nSecond line" // 列印輸出 "First line\nSecond line" }
函數的第一個參數
tag
, 有一個
strings
屬性, 也指向一個數組。該數組的成員與
raw
數組完全一緻。比如,
strings
數組是
strings
, 那麼
["First line\nSecond line"]
數組就是
strings.raw
。兩者唯一的差別, 就是字元串裡面的斜杠都被轉義了。比如 , strings.raw 數組會将
["First line\\nSecond line"]
視為
\n
和
\\
兩個字元, 而不是換行符。這是為了友善取得轉義之前的原始模闆而設計的。
n
更多筆記看上方對應具體知識點筆記
更多筆記看上方對應具體知識點筆記
ES系列筆記過大,防止gitee網站上進入加載時間過長,隻截取小部分,建議下載下傳後閱讀,本筆記完整版本應接近8W字