1 ECMAScript 6簡介javascript
1.1.ESMAScript和JavaScript的關係html
1.2 ES6 與 ECMAScript 2015 的關係java
1.3 TC39委員會git
1.4 ECMAScript 的歷史es6
1.5 瀏覽器支持github
2 let 和 const 命令正則表達式
2.1 let 命令編程
2.2 塊級做用域json
2.3 globalthis(沒搞懂)數組
3.1 基本用法
3.2 對象的解構賦值
3.3 字符串的解構賦值
3.4 數值和布爾值的解構賦值
3.5 函數參數的解構賦值
3.6 圓括號
3.7 用途
6.1 RegExp 構造函數
6.2 字符串的正則方法
6.3 u 修飾符
6.4 RegExp.prototype.unicode 屬性
6.5 y 修飾符
6.6 RegExp.prototype.sticky 屬性
6.7 RegExp.prototype.flags 屬性
6.8 s 修飾符:dotAll 模式
6.9 後行斷言
6.10 Unicode 屬性類
6.11 具名組匹配
6.12 String.prototype.matchAll
7.1 二進制和八進制表示法
7.2 Number.isFinite(), Number.isNaN()
7.3 Number.parseInt(), Number.parseFloat()
7.4 Number.isInteger()
7.5 Number.EPSILON
7.6 安全整數和 Number.isSafeInteger()
7.7 Math 對象的擴展
指數運算符
函數的擴展
數組的擴展
對象的擴展
對象的新增方法
Symbol
Set 和 Map 數據結構
Proxy
Reflect
Promise 對象
Iterator 和 for...of 循環
Generator 函數的語法
Generator 函數的異步應用
async 函數
Class 的基本語法
Class 的繼承
Module 的語法
Module 的加載實現
編程風格
讀懂規格
異步遍歷器
ArrayBuffer
最新提案
Decorator
1.1 ESMAScript和JavaScript的關係
前者是後者的規格,後者是前者的一種實現(另外的 ECMAScript 方言還有 JScript 和 ActionScript)。平常場合,這兩個詞是能夠互換的。
1.2 ES6 與 ECMAScript 2015 的關係
ES6 既是一個歷史名詞,也是一個泛指,含義是 5.1 版之後的 JavaScript 的下一代標準,涵蓋了 ES201五、ES201六、ES2017 等等,而 ES2015 則是正式名稱,特指該年發佈的正式版本的語言標準。本書中提到 ES6 的地方,通常是指 ES2015 標準,但有時也是泛指「下一代 JavaScript 語言」。
1.3 任何人均可以向標準委員會(又稱 TC39 委員會)提案,要求修改語言標準。ECMAScript 當前的全部提案,能夠在 TC39 的官方網站GitHub.com/tc39/ecma262查看。
1.4 ECMAScript 的歷史
ECMAScript 1.0 是 1997 年發佈的,3.0 版是一個巨大的成功,在業界獲得普遍支持,成爲通行標準。2000 年,ECMAScript 4.0 開始醞釀。這個版本最後沒有經過,可是它的大部份內容被 ES6 繼承了。所以,ES6 制定的起點實際上是 2000 年。2007 年 10 月,ECMAScript 4.0 版草案發布,原本預計次年 8 月發佈正式版本。可是,各方對因而否經過這個標準,發生了嚴重分歧。2008 年 7 月,ECMA 開會決定,停止 ECMAScript 4.0 的開發,將其中涉及現有功能改善的一小部分,發佈爲 ECMAScript 3.1。會後不久,ECMAScript 3.1 就更名爲 ECMAScript 5。2009 年 12 月,ECMAScript 5.0 版正式發佈。Harmony 項目則一分爲二,一些較爲可行的設想定名爲 JavaScript.next 繼續開發,後來演變成 ECMAScript 6;一些不是很成熟的設想,則被視爲 JavaScript.next.next。ES5 與 ES3 基本保持兼容,較大的語法修正和新功能加入,將由 JavaScript.next 完成。當時,JavaScript.next 指的是 ES6,第六版發佈之後,就指 ES7。2011 年 6 月,ECMAScript 5.1 版發佈,而且成爲 ISO 國際標準(ISO/IEC 16262:2011)。2013 年 3 月,ECMAScript 6 草案凍結,再也不添加新功能。新的功能設想將被放到 ECMAScript 7。2013 年 12 月,ECMAScript 6 草案發布。而後是 12 個月的討論期,聽取各方反饋。2015 年 6 月,ECMAScript 6 正式經過,成爲國際標準。從 2000 年算起,這時已通過去了 15 年。
1.5 瀏覽器支持
各大瀏覽器的最新版本,對 ES6 的支持能夠查看kangax.github.io/compat-table/es6/。添加帶圖片的dialog
2.1 let 命令
不存在變量提高
暫時性死區
不容許重複聲明
let
聲明的變量只在它所在的代碼塊有效。let
聲明的i
只在本輪循環有效,因此每一次循環的i
其實都是一個新的變量。而var聲明的i,指向的是同一個i。for
循環還有一個特別之處,就是設置循環變量的那部分是一個父做用域,而循環體內部是一個單獨的子做用域。
for
循環的計數器,就很合適使用let
命令。
var
命令會發生「變量提高」現象,即變量能夠在聲明以前使用,值爲undefined
。let
所聲明的變量必定要在聲明後使用,不然報錯。
ES6 明確規定,若是區塊中存在let
和const
命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些變量,就會報錯。
若是一個變量根本沒有被聲明,使用typeof
反而不會報錯。使用let聲明,在聲明以前使用typeof會報錯。
let
不容許在相同做用域內,重複聲明同一個變量。
2.2 塊級做用域
全局做用域、函數做用域、塊級做用域(新增)
爲何須要塊級做用域?第一種場景,內層變量可能會覆蓋外層變量。第二種場景,用來計數的循環變量泄露爲全局變量。
ES6 的塊級做用域
容許塊級做用域的任意嵌套。內層做用域能夠定義外層做用域的同名變量。
塊級做用域與函數聲明
const
實際上保證的,並非變量的值不得改動,而是變量指向的那個內存地址所保存的數據不得改動。若是真的想將對象凍結,應該使用Object.freeze
方法。
const foo = Object.freeze({});
聲明變量的方法:var、function(ES5)、const、let、import、class(ES6)
2.3 globalthis(沒搞懂)
3.1 基本用法
能夠從數組中提取值,按照對應位置,對變量賦值。若是解構不成功,變量的值就等於undefined
。不徹底解構依然能夠成功。
報錯,由於等號右邊的值,要麼轉爲對象之後不具有 Iterator 接口(前五個表達式),要麼自己就不具有 Iterator 接口(最後一個表達式)。
解構賦值容許指定默認值。只有當一個數組成員嚴格(===
)等於undefined
,默認值纔會生效。
若是默認值是一個表達式,那麼這個表達式是惰性求值的,即只有在用到默認值的時候,纔會求值。
默認值能夠引用解構賦值的其餘變量,但該變量必須已經聲明。
let [x = 1, y = x] = [];
3.2 對象的解構賦值
對象的解構與數組有一個重要的不一樣。數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。變量沒有對應的同名屬性,致使取不到值,解構失敗,最後等於undefined
。
對象的解構賦值,能夠很方便地將現有對象的方法,賦值到某個變量。
若是變量名與屬性名不一致,必須寫成下面這樣。
是匹配的模式,纔是變量。真正被賦值的是變量,而不是模式。let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
foobazbazfoo
與數組同樣,解構也能夠用於嵌套結構的對象。
若是解構模式是嵌套的對象,並且子對象所在的父屬性不存在,那麼將會報錯。
注意,對象的解構賦值能夠取到繼承的屬性。
對象的解構也能夠指定默認值。默認值生效的條件是,對象的屬性值嚴格等於undefined
。
注意:若是要將一個已經聲明的變量用於解構賦值(對象),將整個解構賦值語句,放在一個圓括號裏面,就能夠正確執行。
解構賦值容許等號左邊的模式之中,不放置任何變量名。
因爲數組本質是特殊的對象,所以能夠對數組進行對象屬性的解構。對象名是index
3.3 字符串的解構賦值
字符串也能夠解構賦值。字符串被轉換成相似數組的對象。
相似數組的對象都有一個length
屬性,所以還能夠對這個屬性解構賦值。
3.4 數值和布爾值的解構賦值
解構賦值的規則是,只要等號右邊的值不是對象或數組,就先將其轉爲對象,例如對象會有tostring屬性。undefined
和null
沒法轉爲對象,因此對它們進行解構賦值,都會報錯。
3.5 函數參數的解構賦值
function move({x = 0, y = 0} = {})//爲變量x、y指定默認值,當解構失敗時,x、y等於默認值
function move({x, y} = { x: 0, y: 0 }) //爲函數參數指定默認值,當沒有傳入函數參數時(例:move()),傳入參數{0,0}
undefined
就會觸發函數參數的默認值。undefined
3.6 圓括號
如下三種解構賦值不得使用圓括號。
(1)變量聲明語句
(2)函數參數:函數參數也屬於變量聲明,所以不能帶有圓括號。
(3) 賦值語句的模式
可使用圓括號的狀況只有一種:賦值語句的非模式部分,可使用圓括號。
[(b)] = [3]; // 正確
({ p: (d) } = {}); // 正確
[(parseInt.prop)] = [3]; // 正確
首先它們都是賦值語句,而不是聲明語句;其次它們的圓括號都不屬於模式的一部分。第一行語句中,模式是取數組的第一個成員,跟圓括號無關;第二行語句中,模式是p
,而不是d
;第三行語句與第一行語句的性質一致。
3.7 用途
(1)交換變量的值
(2)從函數返回多個值
函數只能返回一個值,若是要返回多個值,只能將它們放在數組或對象裏返回。有了解構賦值,取出這些值就很是方便。
(3)函數參數的定義
解構賦值能夠方便地將一組參數與變量名對應起來。
(4)提取 JSON 數據
區別 | JSON | Javascript |
含義 | 僅僅是一種數據格式 | 表示類的實例 |
傳輸 | 能夠跨平臺數據傳輸,速度快 | 不能傳輸 |
表現 | 1.鍵值對方式,鍵必須加雙引號 2.值不能是方法函數,不能是undefined/NaN(這是最主要的區別) |
1.鍵值對方式,鍵不加引號 2.值能夠是函數、對象、字符串、數字、boolean 等 |
相互轉換 | Json轉換Js對象 1.JSON.parse(JsonStr);(不兼容IE7) 2.eval("("+jsonStr+")");(兼容全部瀏覽器,但不安全,會執行json裏面的表達式?) |
js對象轉換Json JSON.stringify(jsObj); |
其餘 | 調用JSON官網的JS,實現parse和stringify在各個瀏覽器的兼容:總而言之你能夠理解爲JSON是嚴格的JS對象數據格式,是JavaScript原生格式,JSON屬性名稱必須有雙引號,若是值是字符串,也必須是雙引號;他從屬於JS,而且在處理JSON數據時可直接使用JS內置API。 |
(5)函數參數的默認值
(6)遍歷 Map 結構
任何部署了 Iterator 接口的對象,均可以用for...of
循環遍歷。Map 結構原生支持 Iterator 接口,配合變量的解構賦值,獲取鍵名(key)和鍵值(value)就很是方便。
for (let [key, value] of map) { console.log(key + " is " + value); }
// 獲取鍵名 for (let [key] of map) { // ... } // 獲取鍵值 for (let [,value] of map) { // ... }
(7)輸入模塊的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");
4.1 字符的 Unicode 表示法
ES6 增強了對 Unicode 的支持,容許採用\uxxxx
形式表示一個字符,其中xxxx
表示字符的 Unicode 碼點。這種表示法只限於碼點在\u0000
~\uFFFF
之間的字符。超出這個範圍的字符,必須用兩個雙字節的形式表示。若是直接在\u
後面跟上超過0xFFFF
的數值(好比\u20BB7
),JavaScr大括號表示法與四字節的 UTF-16 編碼是等價的。ipt 會理解成\u20BB+7
。因爲\u20BB
是一個不可打印字符,因此只會顯示一個空格,後面跟着一個7
。若是將超出範圍的整個碼點放入大括號,就能正確解讀該字符。有了這種表示法以後,JavaScript 共有 6 種方法能夠表示一個字符。
'\z' === 'z' // true '\172' === 'z' // true '\x7A' === 'z' // true '\u007A' === 'z' // true '\u{7A}' === 'z' // true
4.2 字符串的遍歷器接口
ES6 爲字符串添加了遍歷器接口(詳見《Iterator》一章),使得字符串能夠被for...of
循環遍歷。這個遍歷器最大的優勢是能夠識別大於0xFFFF(例:0x20BB7
)
的碼點,傳統的for
循環沒法識別這樣的碼點,會認爲它包含兩個字符(都不可打印),而for...of
循環會正確識別出這一個字符。
4.3 直接輸入u2028和u2029
可是,JavaScript 規定有5個字符,不能在字符串裏面直接使用,只能使用轉義形式。
舉例來講,字符串裏面不能直接包含反斜槓,必定要轉義寫成\\或者\\u005c
。
麻煩在於 JSON 格式容許字符串裏面直接使用 U+2028(行分隔符)和 U+2029(段分隔符)。這樣一來,服務器輸出的 JSON 被JSON.parse
解析,就有可能直接報錯。
ES2019 容許 JavaScript 字符串直接輸入 U+2028(行分隔符)和 U+2029(段分隔符)。const PS = eval("'\u2029'");
4.4 JSON.stringify() 的改造
根據標準,JSON 數據必須是 UTF-8 編碼。UTF-8 標準規定,0xD800
到0xDFFF
之間的碼點,不能單獨使用,必須配對使用。這是爲了表示碼點大於0xFFFF
的字符的一種變通方法。JSON.stringify()
的問題在於,它可能返回0xD800
到0xDFFF
之間的單個碼點。爲了確保返回的是合法的 UTF-8 字符,ES2019 改變了JSON.stringify()
的行爲。若是遇到0xD800
到0xDFFF
之間的單個碼點,或者不存在的配對形式,它會返回轉義字符串,留給應用本身決定下一步的處理。
JSON.stringify('\u{D834}') // ""\\uD834"" JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""
4.5 模板字符串
傳統輸出模板:
$('#result').append( 'There are <b>' + basket.count + '</b> ' + 'items in your basket, ' + '<em>' + basket.onSale + '</em> are on sale!' );
ES6 引入了模板字符串解決這個問題。模板字符串(template string)是加強版的字符串,用反引號(`)標識。它能夠看成普通字符串使用,也能夠用來定義多行字符串,或者在字符串中嵌入變量。
$('#result').append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale! `);
若是在模板字符串中須要使用反引號,則前面要用反斜槓轉義。
let greeting = `\`Yo\` World!`;
若是使用模板字符串表示多行字符串,全部的空格和縮進都會被保留在輸出之中。若是你不想要這個換行,可使用trim
方法消除它。
$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim());
模板字符串中嵌入變量,須要將變量名寫在${}
之中。大括號內部能夠放入任意的 JavaScript 表達式,能夠進行運算,以及引用對象屬性。模板字符串之中還能調用函數。
// 字符串中嵌入變量 let name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?`
function fn() { return "Hello World"; }
`foo ${fn()} bar` // foo Hello World bar
let x = 1, y = 2;
`${x} + ${y} = ${x + y}`// "1 + 2 = 3"
若是大括號中的值不是字符串,將按照通常的規則轉爲字符串。好比,大括號中是一個對象,將默認調用對象的toString
方法。
若是模板字符串中的變量沒有聲明,將報錯。
若是大括號內部是一個字符串,將會原樣輸出。
模板字符串甚至還能嵌套:
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>
若是須要引用模板字符串自己,在須要時執行,能夠寫成函數。好比將模板字符串寫成了一個函數的返回值。執行這個函數,就至關於執行這個模板字符串了。
4.6 模板編譯
沒有看懂
4.7 標籤模板
標籤指的是函數名,緊跟在後面的模板字符串是它的參數,該函數將被調用來處理這個模板字符串。例如alert`123`
函數tag
依次會接收到多個參數。
function tag(stringArr, value1, value2){ // ... } // 等同於 function tag(stringArr, ...values){ // ... }
let a = 5, b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同於
tag(['Hello ', ' world ', ''], 15, 50);
第一個參數是literals數組,括號中整個參數稱arguments,arguments[0] = literals
傳入函數的參數是literals數組,arguments是js內置的對象。每次取literals和arguments裏各一個參數,將各個參數按照原來的位置拼合回去。像是literals[0]+argumnts[1]+literals[1]+arguments[2].....
「標籤模板」的一個重要應用,就是過濾 HTML 字符串,防止用戶輸入惡意內容。
模板處理函數的第一個參數(模板字符串數組),還有一個raw
屬性。(例:console.log`123`
)console.log
接受的參數,其實是一個數組。該數組有一個raw
屬性,也指向一個數組。保存的是轉義後的原字符串。字符串裏面的斜槓都被轉義了。輸出參數字符串。
4.8 模板字符串的限制
模板字符串默認會將字符串轉義,致使沒法嵌入其餘語言。模板字符串會將\u00FF
和\u{42}
看成 Unicode 字符進行轉義,因此解析相似\unicode會報錯。ES2018 放鬆了對標籤模板裏面的字符串轉義的限制。若是遇到不合法的字符串轉義,就返回undefined
,而不是報錯,而且從raw
屬性上面能夠獲得原始字符串。
注意,這種對字符串轉義的放鬆,只在標籤模板解析字符串時生效,不是標籤模板的場合(普通模板賦值),依然會報錯。
5.1 String.fromCodePoint()
ES5 提供了String.fromCharCode()
方法,用於從 Unicode 碼點返回對應字符,可是這個方法不能識別碼點大於0xFFFF
的字符。會捨棄最高位返回。0x20BB7(捨棄最高位2)最後返回碼點U+0BB7
對應的字符
ES6 提供了String.fromCodePoint()
方法,能夠識別大於0xFFFF
的字符。在做用上,正好與下面的codePointAt()
方法相反。若是String.fromCodePoint
方法有多個參數,則它們會被合併成一個字符串返回。
注意,fromCodePoint
方法定義在String
對象上,而codePointAt
方法定義在字符串的實例對象上。
5.2 String.raw()
該方法返回一個斜槓都被轉義(即斜槓前面再加一個斜槓)的字符串,每每用於模板字符串的處理方法。若是原字符串的斜槓已經轉義,那麼String.raw()
會進行再次轉義。
String.raw()
本質上是一個正常的函數,只是專用於模板字符串的標籤函數。若是寫成正常函數的形式,它的第一個參數,應該是一個具備raw
屬性的對象,且raw
屬性的值應該是一個數組,對應模板字符串解析後的值。
// `foo${1 + 2}bar` // 等同於 String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"
5.3 實例方法:codePointAt()
JavaScript 內部,字符以 UTF-16 的格式儲存,每一個字符固定爲2
個字節。對於那些須要4
個字節儲存的字符(Unicode 碼點大於0xFFFF
的字符),JavaScript 會認爲它們是兩個字符。
漢字「𠮷」(注意,這個字不是「吉祥」的「吉」)的碼點是0x20BB7,
對於這種4
個字節的字符,JavaScript 不能正確處理,s.length字符串長度會誤判爲2
,並且charAt()
方法沒法讀取整個字符,charCodeAt()
方法只能分別返回前兩個字節和後兩個字節的十進制值。(??有點問題,不也是返回兩個字節的值嗎?這兩個字節的值和另一個方法返回的有什麼不同)
ES6 提供了codePointAt()
方法,可以正確處理 4 個字節儲存的字符,返回一個字符的碼點。
總之,codePointAt()
方法會正確返回 32 位的 UTF-16 字符的碼點。對於那些兩個字節儲存的常規字符,它的返回結果與charCodeAt()
方法相同。
codePointAt()
方法返回的是碼點的十進制值,若是想要十六進制的值,可使用toString()
方法轉換一下。
let s = '𠮷a'; s.codePointAt(0) // 134071 s.codePointAt(1) // 57271 s.codePointAt(2) // 97
s.codePointAt(0).toString(16) // "20bb7" s.codePointAt(2).toString(16) // "61"
codePointAt()
方法的參數,仍然是不正確的。解決這個問題的一個辦法是使用for...of
循環,由於它會正確識別 32 位的 UTF-16 字符。codePointAt()for...of
另外一種方法也能夠,使用擴展運算符()將字符變爲數組,利用forEach進行展開運算。for (let ch of s) { console.log(ch.codePointAt(0).toString(16)); } // 20bb7 // 61...
let arr = [...'𠮷a']; // arr.length === 2 arr.forEach( ch => console.log(ch.codePointAt(0).toString(16)) ); // 20bb7 // 61
codePointAt()
方法是測試一個字符由兩個字節仍是由四個字節組成的最簡單方法。
function is32Bit(c) { return c.codePointAt(0) > 0xFFFF; } is32Bit("𠮷") // true is32Bit("a") // false
5.4 實例方法:normalize()
ES6 提供字符串實例的normalize()
方法,用來將字符的不一樣表示方法統一爲一樣的形式,這稱爲 Unicode 正規化。
許多歐洲語言有語調符號和重音符號。爲了表示它們,Unicode 提供了兩種方法。一種是直接提供帶重音符號的字符,好比Ǒ
(\u01D1)。另外一種是提供合成符號(combining character),即原字符與重音符號的合成,兩個字符合成一個字符,好比O
(\u004F)和ˇ
(\u030C)合成Ǒ
(\u004F\u030C)。
這兩種表示方法,在視覺和語義上都等價,可是 JavaScript 不能識別。
'\u01D1'==='\u004F\u030C' //false
'\u01D1'.normalize() === '\u004F\u030C'.normalize() // true
normalize
方法能夠接受一個參數來指定normalize
的方式,參數的四個可選值以下。
NFC
,默認參數,表示「標準等價合成」,返回多個簡單字符的合成字符。所謂「標準等價」指的是視覺和語義上的等價。NFD
,表示「標準等價分解」,即在標準等價的前提下,返回合成字符分解的多個簡單字符。NFKC
,表示「兼容等價合成」,返回合成字符。所謂「兼容等價」指的是語義上存在等價,但視覺上不等價,好比「囍」和「喜喜」。(這只是用來舉例,normalize
方法不能識別中文。)NFKD
,表示「兼容等價分解」,即在兼容等價的前提下,返回合成字符分解的多個簡單字符。不過,normalize
方法目前不能識別三個或三個以上字符的合成。這種狀況下,仍是隻能使用正則表達式,經過 Unicode 編號區間判斷。
5.5 實例方法:includes(), startsWith(), endsWith()
JavaScript 只有indexOf
方法,能夠用來肯定一個字符串是否包含在另外一個字符串中。ES6 又提供了三種新方法。
這三個方法都支持第二個參數,表示開始搜索的位置。使用第二個參數n
時,endsWith
的行爲與其餘兩個方法有所不一樣。它針對前n
個字符,而其餘兩個方法針對從第n
個位置直到字符串結束。
5.6 實例方法:repeat()
repeat
方法返回一個新字符串,表示將原字符串重複n
次。參數若是是小數,會被取整(2.9=3)。若是repeat
的參數是負數或者Infinity
,會報錯。可是,若是參數是 0 到-1 之間的小數,則等同於 0,這是由於會先進行取整運算。0 到-1 之間的小數,取整之後等於-0
,repeat
視同爲 0。參數NaN
等同於 0。若是repeat
的參數是字符串,則會先轉換成數字。
5.7 實例方法:padStart(),padEnd()
ES2017 引入了字符串補全長度的功能。若是某個字符串不夠指定長度,會在頭部或尾部補全。padStart()
用於頭部補全,padEnd()
用於尾部補全。方法一共接受兩個參數,第一個參數是字符串補全生效的最大長度,第二個參數是用來補全的字符串。若是省略第二個參數,默認使用空格補全長度。
若是原字符串的長度,等於或大於最大長度,則字符串補全不生效,返回原字符串。
若是用來補全的字符串與原字符串,二者的長度之和超過了最大長度,則會截去超出位數的補全字符串。
padStart()
的常見用途是爲數值補全指定位數。
'1'.padStart(10, '0') // "0000000001" '12'.padStart(10, '0') // "0000000012" '123456'.padStart(10, '0') // "0000123456"
另外一個用途是提示字符串格式。
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12" '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
5.8 實例方法:trimStart(),trimEnd()
ES2019 對字符串實例新增了trimStart()
和trimEnd()
這兩個方法。它們的行爲與trim()
一致,trimStart()
消除字符串頭部的空格,trimEnd()
消除尾部的空格。它們返回的都是新字符串,不會修改原始字符串。除了空格鍵,這兩個方法對字符串頭部(或尾部)的 tab 鍵、換行符等不可見的空白符號也有效。瀏覽器還部署了額外的兩個方法,trimLeft()
是trimStart()
的別名,trimRight()
是trimEnd()
的別名。
5.9 實例方法:matchAll()
matchAll()
方法返回一個正則表達式在當前字符串的全部匹配。
6.1 RegExp 構造函數
在 ES5 中,RegExp
構造函數的參數有兩種狀況。
第一種狀況是,參數是字符串,這時第二個參數表示正則表達式的修飾符(flag)。
var regex = new RegExp('xyz', 'i');
第二種狀況是,參數是一個正則表示式,這時會返回一個原有正則表達式的拷貝。可是,ES5 不容許此時使用第二個參數添加修飾符,不然會報錯。
var regex = new RegExp(/xyz/i);
ES6 改變了第二種狀況。若是RegExp
構造函數第一個參數是一個正則對象,也可使用第二個參數指定修飾符。並且,返回的正則表達式會忽略原有的正則表達式的修飾符,只使用新指定的修飾符,即覆蓋原有的修飾符。
6.2 字符串的正則方法
字符串對象共有 4 個方法,可使用正則表達式:match()
、replace()
、search()
和split()
。
ES6 將這 4 個方法,在語言內部所有調用RegExp
的實例方法,從而作到全部與正則相關的方法,全都定義在RegExp
對象上。
String.prototype.match
調用 RegExp.prototype[Symbol.match]
String.prototype.replace
調用 RegExp.prototype[Symbol.replace]
String.prototype.search
調用 RegExp.prototype[Symbol.search]
String.prototype.split
調用 RegExp.prototype[Symbol.split]
6.3 u 修飾符
ES6 對正則表達式添加了u
修飾符,含義爲「Unicode 模式」,用來正確處理大於\uFFFF
的 Unicode 字符。也就是說,會正確處理四個字節的 UTF-16 編碼。
/^\uD83D/u.test('\uD83D\uDC2A') // false /^\uD83D/.test('\uD83D\uDC2A') // true
ES5 不支持四個字節的 UTF-16 編碼,會將其識別爲兩個字符,致使第二行代碼結果爲true
。加了u
修飾符之後,ES6 就會識別其爲一個字符,因此第一行代碼結果爲false
。
一旦加上u
修飾符號,就會修改下面這些正則表達式的行爲。
(1)點字符
點(.
)字符在正則表達式中,含義是除了換行符之外的任意單個字符。對於碼點大於0xFFFF
的 Unicode 字符,點字符不能識別,必須加上u
修飾符。
var s = '𠮷'; /^.$/.test(s) // false /^.$/u.test(s) // true
上面代碼表示,若是不添加u
修飾符,正則表達式就會認爲字符串爲兩個字符,從而匹配失敗。
(2)Unicode 字符表示法
ES6 新增了使用大括號表示 Unicode 字符,這種表示法在正則表達式中必須加上u
修飾符,才能識別當中的大括號,不然會被解讀爲量詞。
/\u{61}/.test('a') // false /\u{61}/u.test('a') // true /\u{20BB7}/u.test('𠮷') // true
上面代碼表示,若是不加u
修飾符,正則表達式沒法識別\u{61}
這種表示法,只會認爲這匹配 61 個連續的u
。
3)量詞
使用u
修飾符後,全部量詞都會正確識別碼點大於0xFFFF
的 Unicode 字符。
/a{2}/.test('aa') // true /a{2}/u.test('aa') // true /𠮷{2}/.test('𠮷𠮷') // false /𠮷{2}/u.test('𠮷𠮷') // true
4)預約義模式
u
修飾符也影響到預約義模式,可否正確識別碼點大於0xFFFF
的 Unicode 字符。
/^\S$/.test('𠮷') // false /^\S$/u.test('𠮷') // true
上面代碼的\S
是預約義模式,匹配全部非空白字符。只有加了u
修飾符,它才能正確匹配碼點大於0xFFFF
的 Unicode 字符。
利用這一點,能夠寫出一個正確返回字符串長度的函數。
function codePointLength(text) { var result = text.match(/[\s\S]/gu); return result ? result.length : 0; } var s = '𠮷𠮷'; s.length // 4 codePointLength(s) // 2
(5)i 修飾符(????這和i修飾符有什麼關係)
有些 Unicode 字符的編碼不一樣,可是字型很相近,好比,\u004B
與\u212A
都是大寫的K
。
/[a-z]/i.test('\u212A') // false /[a-z]/iu.test('\u212A') // true
上面代碼中,不加u
修飾符,就沒法識別非規範的K
字符。
(6)轉義
沒有u
修飾符的狀況下,正則中沒有定義的轉義(如逗號的轉義\,
)無效,而在u
模式會報錯。
/\,/ // /\,/ /\,/u // 報錯
上面代碼中,沒有u
修飾符時,逗號前面的反斜槓是無效的,加了u
修飾符就報錯。
6.4 RegExp.prototype.unicode 屬性
正則實例對象新增unicode
屬性,表示是否設置了u
修飾符。
const r1 = /hello/; const r2 = /hello/u; r1.unicode // false r2.unicode // true
上面代碼中,正則表達式是否設置了u
修飾符,能夠從unicode
屬性看出來。
6.5 y 修飾符
除了u
修飾符,ES6 還爲正則表達式添加了y
修飾符,叫作「粘連」(sticky)修飾符。y
修飾符的做用與g
修飾符相似,也是全局匹配,後一次匹配都從上一次匹配成功的下一個位置開始。不一樣之處在於,g
修飾符只要剩餘位置中存在匹配就可,而y
修飾符確保匹配必須從剩餘的第一個位置開始匹配。實際上,y
修飾符號隱含了頭部匹配的標誌^
。
單單一個y
修飾符對match
方法,只能返回第一個匹配,必須與g
修飾符聯用,才能返回全部匹配。y須要屢次調用,g不需屢次調用
'a1a2a3'.match(/a\d/y) // ["a1"] 'a1a2a3'.match(/a\d/gy) // ["a1", "a2", "a3"]
y
修飾符的一個應用,是從字符串提取 token(詞元),y
修飾符確保了匹配之間不會有漏掉的字符。
const TOKEN_Y = /\s*(\+|[0-9]+)\s*/y; const TOKEN_G = /\s*(\+|[0-9]+)\s*/g;
tokenize(TOKEN_Y, '3x + 4') // [ '3' ] tokenize(TOKEN_G, '3x + 4') // [ '3', '+', '4' ]
上面代碼中,g
修飾符會忽略非法字符,而y
修飾符不會,這樣就很容易發現錯誤。
6.6 RegExp.prototype.sticky
與y
修飾符相匹配,ES6 的正則實例對象多了sticky
屬性,表示是否設置了y
修飾符。
var r = /hello\d/y; r.sticky // true
6.7 RegExp.prototype.flags 屬性
ES6 爲正則表達式新增了flags
屬性,會返回正則表達式的修飾符。
// ES5 的 source 屬性,返回正則表達式的正文 例:/abc/ig.source // "abc" // ES6 的 flags 屬性,返回正則表達式的修飾符 例:/abc/ig.flags // 'gi'
6.8 s 修飾符:dotAll 模式
正則表達式中,點(.
)是一個特殊字符,表明任意的單個字符,可是有兩個例外。一個是四個字節的 UTF-16 字符,這個能夠用u
修飾符解決;另外一個是行終止符。
如下四個字符屬於「行終止符」。
\n
)\r
)/foo.bar/.test('foo\nbar') // false 不匹配\n
/foo[^]bar/.test('foo\nbar') // true 變換一下寫法能夠成功,但不符合直覺
ES2018 引入s
修飾符,使得.
能夠匹配任意單個字符。
正則表達式還引入了一個屬性,返回一個布爾值,表示該正則表達式是否處在模式。const re = /foo.bar/s;
re.test('foo\nbar') // true 這被稱爲dotAll
模式,即點(dot)表明一切字符。
dotAllre.dotAll //true dotAlldotAll
re.flags // 's'
/s
修飾符和多行修飾符/m
不衝突,二者一塊兒使用的狀況下,.
匹配全部字符,而^
和$
匹配每一行的行首和行尾。
6.9 後行斷言
JavaScript 語言的正則表達式,只支持先行斷言和先行否認斷言,不支持後行斷言和後行否認斷言。ES2018 引入後行斷言,V8 引擎 4.9 版(Chrome 62)已經支持。
「先行斷言」指的是,x
只有在y
前面才匹配,必須寫成/x(?=y)/
。好比,只匹配百分號以前的數字,要寫成/\d+(?=%)/
。
「先行否認斷言」指的是,x
只有不在y
前面才匹配,必須寫成/x(?!y)/
。好比,只匹配不在百分號以前的數字,要寫成/\d+(?!%)/
。
「後行斷言」指的是,x
只有在y
後面才匹配,必須寫成/(?<=y)x/
。好比,只匹配美圓符號以後的數字,要寫成/(?<=\$)\d+/
。
「後行否認斷言」指的是,x
只有不在y
後面才匹配,必須寫成/(?<!y)x/
。好比,只匹配不在美圓符號後面的數字,要寫成/(?<!\$)\d+/
。
「後行斷言」的實現,須要先匹配/(?<=y)x/
的x
,而後再回到左邊,匹配y
的部分。這種「先右後左」的執行順序,與全部其餘正則操做相反,致使了一些不符合預期的行爲。
(?????沒懂)其次,「後行斷言」的反斜槓引用,也與一般的順序相反,必須放在對應的那個括號以前。
/(?<=(o)d\1)r/.exec('hodor') // null /(?<=\1d(o))r/.exec('hodor') // ["r", "o"]
上面代碼中,若是後行斷言的反斜槓引用(\1
)放在括號的後面,就不會獲得匹配結果,必須放在前面才能夠。由於後行斷言是先從左到右掃描,發現匹配之後再回過頭,從右到左完成反斜槓引用。
6.10 Unicode 屬性類
ES2018 引入了一種新的類的寫法\p{...}
和\P{...}
,\p{…}容許正則表達式匹配符合 Unicode 某種屬性的全部字符。\P{…}
是\p{…}
的反向匹配,即匹配不知足條件的字符。
這兩種類只對 Unicode 有效,因此使用的時候必定要加上u
修飾符。若是不加u
修飾符,正則表達式使用\p
和\P
會報錯
Unicode 屬性類要指定屬性名和屬性值。
\p{UnicodePropertyName=UnicodePropertyValue}
對於某些屬性,能夠只寫屬性名,或者只寫屬性值。
\p{UnicodePropertyName} \p{UnicodePropertyValue}
const regexGreekSymbol = /\p{Script=Greek}/u; regexGreekSymbol.test('π') // true
上面代碼中,\p{Script=Greek}
指定匹配一個希臘文字母,因此匹配π
成功。
Unicode 的各類屬性很是多,因此這種新的類的表達能力很是強。
const regex = /^\p{Decimal_Number}+$/u; 指定匹配全部十進制字符,能夠看到各類字型的十進制字符都會匹配成功 regex.test('𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼') // true
'²³¹¼½¾''㉛㉜㉝''ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ' \p{Number}
甚至能匹配羅馬數字。\p{Number}
上面代碼中,屬性類指定匹配全部十進制字符,能夠看到各類字型的十進制字符都會匹配成功。
6.11 具名組匹配
正則表達式使用圓括號進行組匹配。
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/;
上面代碼中,正則表達式裏面有三組圓括號。使用exec
方法,就能夠將這三組匹配結果提取出來。
組匹配的一個問題是,每一組的匹配含義不容易看出來,並且只能用數字序號(好比matchObj[1]
)引用,要是組的順序變了,引用的時候就必須修改序號。
ES2018 引入了具名組匹配,容許爲每個組匹配指定一個名字,既便於閱讀代碼,又便於引用。
const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/; //使用具名組匹配以前的regex
const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; //具名組匹配在圓括號內部,模式的頭部添加「問號 + 尖括號 + 組名」
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31
這樣就能夠在exec
方法返回結果的groups
屬性上引用該組名,若是組的順序變了,也不用改變匹配後的處理代碼。同時,數字序號(matchObj[1]
)依然有效。
若是具名組as沒有匹配,那麼對應的groups
對象屬性會是undefined
。而且as
這個鍵名在groups
是始終存在的。
6.12 解構賦值和替換
有了具名組匹配之後,可使用解構賦值直接從匹配結果上爲變量賦值。
let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar'); //對象替換
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u; //字符串替換時,使用$<組名>
引用具名組。 '2015-01-02'.replace(re, '$<day>/$<month>/$<year>') $<組名>// '02/01/2015',replace方法的第二個參數是字符串,也能夠是函數
replace
方法的第二個參數也能夠是函數,該函數的參數序列以下。
'2015-01-02'.replace(re, ( matched, // 整個匹配結果 2015-01-02 capture1, // 第一個組匹配 2015 capture2, // 第二個組匹配 01 capture3, // 第三個組匹配 02 position, // 匹配開始的位置 0 S, // 原字符串 2015-01-02 groups // 具名組構成的一個對象 {year, month, day} ) => { let {day, month, year} = groups; return `${day}/${month}/${year}`; });
具名組匹配在原來的基礎上,新增了最後一個函數參數:具名組構成的一個對象。函數內部能夠直接對這個對象進行解構賦值。
若是要在正則表達式內部引用某個「具名組匹配」,可使用\k<組名>
的寫法,相似反向引用。
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/; //用法相似 \k<> = \1
const RE_TWICE = /^(?<word>[a-z]+)!\1$/;
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/; //能夠同時使用
6.12 String.prototype.matchAll
若是一個正則表達式在字符串裏面有多個匹配,如今通常使用g
修飾符或y
修飾符,在循環裏面逐一取出。
var regex = /t(e)(st(\d?))/g; var string = 'test1test2test3'; var matches = []; var match; while (match = regex.exec(string)) { matches.push(match); } matches // [ // ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"], // ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"], // ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"] // ]
上面代碼中,while
循環取出每一輪的正則匹配,一共三輪。
目前有一個提案,增長了String.prototype.matchAll
方法,能夠一次性取出全部匹配。不過,它返回的是一個遍歷器(Iterator),而不是數組。
const string = 'test1test2test3'; // g 修飾符加不加均可以 const regex = /t(e)(st(\d?))/g; for (const match of string.matchAll(regex)) { console.log(match); } // ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"] // ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"] // ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]
上面代碼中,因爲string.matchAll(regex)
返回的是遍歷器,因此能夠用for...of
循環取出。相對於返回數組,返回遍歷器的好處在於,若是匹配結果是一個很大的數組,那麼遍歷器比較節省資源。
遍歷器轉爲數組是很是簡單的,使用...
運算符和Array.from
方法就能夠了。
// 轉爲數組方法一 [...string.matchAll(regex)] // 轉爲數組方法二 Array.from(string.matchAll(regex));
7.1 二進制和八進制表示法
ES6 提供了二進制和八進制數值的新的寫法,分別用前綴0b
(或0B
)和0o
(或0O
)表示。從 ES5 開始,在嚴格模式之中,八進制就再也不容許使用前綴0
表示,ES6 進一步明確,要使用前綴0o
表示。
若是要將0b
和0o
前綴的字符串數值轉爲十進制,要使用Number
方法。
Number('0b111') // 7 Number('0o10') // 8
7.2 Number.isFinite(), Number.isNaN()
ES6 在Number
對象上,新提供了Number.isFinite()
和Number.isNaN()
兩個方法。
Number.isFinite()
用來檢查一個數值是否爲有限的(finite),即不是Infinity
。
Number.isNaN()
用來檢查一個值是否爲NaN
。
它們與傳統的全局方法isFinite()
和isNaN()
的區別在於,傳統方法先調用Number()
將非數值的值轉爲數值,再進行判斷,而這兩個新方法只對數值有效,Number.isFinite()
對於非數值一概返回false
, Number.isNaN()
只有對於NaN
才返回true
,非NaN
一概返回false
。
isFinite("25") // true
Number.isFinite("25") // false
isNaN("NaN") // true
Number.isNaN("NaN") // false
7.3 Number.parseInt(), Number.parseFloat()
ES6 將ES5的全局方法parseInt()
和parseFloat()
,移植到Number
對象上面,行爲徹底保持不變。
這樣作的目的,是逐步減小全局性方法,使得語言逐步模塊化。
Number.parseInt === parseInt // true Number.parseFloat === parseFloat // true
Number.parseFloat('123.45#') === parseFloat('123.45#') // 123.45
7.4 Number.isInteger()
Number.isInteger()
用來判斷一個數值是否爲整數。
JavaScript 內部,整數和浮點數採用的是一樣的儲存方法,因此 25 和 25.0 被視爲同一個值。
若是參數不是數值,Number.isInteger
返回false
。
注意,因爲 JavaScript 採用 IEEE 754 標準,數值存儲爲64位雙精度格式,數值精度最多能夠達到 53 個二進制位(1 個隱藏位與 52 個有效位)。若是數值的精度超過這個限度,第54位及後面的位就會被丟棄,這種狀況下,
Number.isInteger
可能會誤判。若是一個數值的絕對值小於Number.MIN_VALUE
(5E-324),即小於 JavaScript 可以分辨的最小值,會被自動轉爲 0。這時,Number.isInteger
也會誤判。
總之,若是對數據精度的要求較高,不建議使用
Number.isInteger()
判斷一個數值是否爲整數。
7.5 Number.EPSILON
ES6 在Number
對象上面,新增一個極小的常量Number.EPSILON
。根據規格,它表示 1 與大於 1 的最小浮點數之間的差。
對於 64 位浮點數來講,大於 1 的最小浮點數至關於二進制的1.00..001
,小數點後面有連續 51 個零。這個值減去 1 以後,就等於 2 的 -52 次方。
Number.EPSILON === Math.pow(2, -52) // true
引入一個這麼小的量的目的,在於爲浮點數計算,設置一個偏差範圍。咱們知道浮點數計算是不精確的。0.1 + 0.2
與0.3
獲得的結果是false
。
Number.EPSILON
能夠用來設置「可以接受的偏差範圍」。好比,偏差範圍設爲 2 的-50 次方(即Number.EPSILON * Math.pow(2, 2)
),即若是兩個浮點數的差小於這個值,咱們就認爲這兩個浮點數相等。
所以,Number.EPSILON
的實質是一個能夠接受的最小偏差範圍。
7.6 安全整數和 Number.isSafeInteger()
JavaScript 可以準確表示的整數範圍在-2^53
到2^53
之間(不含兩個端點),超過這個範圍,沒法精確表示這個值。
Math.pow(2, 53) === Math.pow(2, 53) + 1
Math.pow(2, 53) // 9007199254740992
// true
上面代碼中,超出 2 的 53 次方以後,一個數就不精確了。
ES6 引入了Number.MAX_SAFE_INTEGER
和Number.MIN_SAFE_INTEGER
這兩個常量,用來表示這個範圍的上下限。
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
Number.MIN_SAFE_INTEGER === Math.pow(-2, 53) - 1
Number.isSafeInteger()
則是用來判斷一個整數是否落在這個範圍以內。這個函數的實現很簡單,就是跟安全整數的兩個邊界值比較一下。
實際使用這個函數時,須要注意。驗證運算結果是否落在安全整數的範圍內,不要只驗證運算結果,而要同時驗證參與運算的每一個值。只要運算的數超出了精度範圍,致使在計算機內部,以9007199254740992
的形式儲存。即便結果在安全整數範圍內,也是錯誤的結果。
7.7 Math 對象的擴展
ES6 在 Math 對象上新增了 17 個與數學相關的方法。全部這些方法都是靜態方法,只能在 Math 對象上調用。
Math.trunc()方法用於去除一個數的小數部分,返回整數部分。
對於非數值,Math.trunc
內部使用Number
方法將其先轉爲數值(例如true爲1,false爲0,null爲0)。
對於空值和沒法截取整數的值,返回NaN(例如NaN、字符串、undefined)
。
對於沒有部署這個方法的環境,能夠用下面的代碼模擬。
Math.trunc = Math.trunc || function(x) { return x < 0 ? Math.ceil(x) : Math.floor(x); };
Math.sign()
方法用來判斷一個數究竟是正數、負數、仍是零。對於非數值,會先將其轉換爲數值。對於那些沒法轉爲數值的值,會返回NaN
。
它會返回五種值。
+1
;-1
;0
;-0
;NaN
。Math.cbrt()方法用於計算一個數的立方根。對於非數值,Math.cbrt
方法內部也是先使用Number
方法將其轉爲數值。
Math.cbrt(-1) // -1 Math.cbrt(8) // 2 Math.cbrt(2) // 1.2599210498948734
Math.clz32()
方法將參數轉爲 32 位無符號整數的形式,而後返回這個 32 位值裏面有多少個前導 0。
Math.clz32(0) // 32 0 的二進制形式全爲 0,因此有 32 個前導 0 Math.clz32(1) // 31 1 的二進制形式是0b1
,只佔 1 位,因此 32 位之中有 31 個前導 0; Math.clz32(1000) // 22 1000 的二進制形式是0b1111101000
,一共有 10 位,因此 32 位之中有 22 個前導 0。 Math.clz32(0b01000000000000000000000000000000) // 1 Math.clz32(0b00100000000000000000000000000000) // 20b10b1111101000
左移運算符(<<
)與Math.clz32
方法直接相關。
Math.clz32(1) // 31 Math.clz32(1 << 1) // 30 Math.clz32(1 << 29) // 2
對於小數,Math.clz32
方法只考慮整數部分。
對於空值或其餘類型的值,Math.clz32
方法會將它們先轉爲數值0(true爲1),而後再計算。
Math.imul()
方法返回兩個數以 32 位帶符號整數形式相乘的結果,返回的也是一個 32 位的帶符號整數。
Math.imul(2, 4) // 8 Math.imul(-1, 8) // -8 Math.imul(-2, -2) // 4
若是隻考慮最後 32 位,大多數狀況下,Math.imul(a, b)
與a * b
的結果是相同的,即該方法等同於(a * b)|0
的效果(超過 32 位的部分溢出)。之因此須要部署這個方法,是由於 JavaScript 有精度限制,超過 2 的 53 次方的值沒法精確表示。這就是說,對於那些很大的數的乘法,低位數值每每都是不精確的,Math.imul
方法能夠返回正確的低位數值。
(0x7fffffff * 0x7fffffff)|0 // 0
上面這個乘法算式,返回結果爲 0。可是因爲這兩個二進制數的最低位都是 1,因此這個結果確定是不正確的,由於根據二進制乘法,計算結果的二進制最低位應該也是 1。這個錯誤就是由於它們的乘積超過了 2 的 53 次方,JavaScript 沒法保存額外的精度,就把低位的值都變成了 0。Math.imul
方法能夠返回正確的值 1。
Math.imul(0x7fffffff, 0x7fffffff) // 1
Math.fround()
方法返回一個數的32位單精度浮點數形式。主要做用是將64位雙精度浮點數轉爲32位單精度浮點數。若是小數的精度超過24個二進制位,返回值就會不一樣於原值,不然返回值不變(即與64位雙精度值一致)。對於 NaN
和 Infinity
,此方法返回原值。對於其它類型的非數值,Math.fround
方法會先將其轉爲數值,再返回單精度浮點數。
Math.hypot()
方法返回全部參數的平方和的平方根。
Math.hypot(3, 4); // 5 Math.hypot(3, 4, 5); // 7.0710678118654755 Math.hypot(3, 4, '5'); // 7.0710678118654755 Math.hypot(-3); // 3
上面代碼中,3 的平方加上 4 的平方,等於 5 的平方。
若是參數不是數值,Math.hypot
方法會將其轉爲數值。空值返回0,只要有一個參數沒法轉爲數值,就會返回 NaN。
7.8 對數方法
ES6 新增了 4 個對數相關方法。
(1) Math.expm1(x)返回 ex - 1,即Math.exp(x) - 1
。
(2)Math.log1p(x)方法返回1 + x
的天然對數,即Math.log(1 + x)
。若是x
小於-1,返回NaN
。1+x爲0,則返回-Infinity
(3)Math.log10(x)
返回以 10 爲底的x
的對數。若是x
小於 0,則返回 NaN。
(4)Math.log2(x)
返回以 2 爲底的x
的對數。若是x
小於 0,則返回 NaN。
7.9 雙曲函數方法
ES6 新增了 6 個雙曲函數方法。
Math.sinh(x)
返回x
的雙曲正弦(hyperbolic sine)Math.cosh(x)
返回x
的雙曲餘弦(hyperbolic cosine)Math.tanh(x)
返回x
的雙曲正切(hyperbolic tangent)Math.asinh(x)
返回x
的反雙曲正弦(inverse hyperbolic sine)Math.acosh(x)
返回x
的反雙曲餘弦(inverse hyperbolic cosine)Math.atanh(x)
返回x
的反雙曲正切(inverse hyperbolic tangent)7.10 指數運算符
ES2016 新增了一個指數運算符(**
)。
這個運算符的一個特色是右結合,而不是常見的左結合。多個指數運算符連用時,是從最右邊開始計算的。
// 至關於 2 ** (3 ** 2) 2 ** 3 ** 2 // 512
指數運算符能夠與等號結合,造成一個新的賦值運算符(**=
)。
a **= 2; // 等同於 a = a * a; b **= 3; // 等同於 b = b * b * b;
注意,V8 引擎的指數運算符與Math.pow
的實現不相同,對於特別大的運算結果,二者會有細微的差別。
Math.pow(99, 99) // 3.697296376497263e+197 99 ** 99 // 3.697296376497268e+197
上面代碼中,兩個運算結果的最後一位有效數字是有差別的。
8.1 函數參數的默認值基本用法
ES6 以前,不能直接爲函數的參數指定默認值,只能採用變通的方法。
若是參數賦值了,可是對應的布爾值爲,則該賦值不起做用
爲了不這個問題,一般須要先判斷一下參數是否被賦值(typeof y === 'undifined'),若是沒有,再等於默認值。y = y || 'World'; //缺點是yfalsey
ES6 容許爲函數的參數設置默認值,即直接寫在參數定義的後面。
function log(x, y = 'World')
除了簡潔,ES6 的寫法還有兩個好處:首先,閱讀代碼的人,能夠馬上意識到哪些參數是能夠省略的,不用查看函數體或文檔;其次,有利於未來的代碼優化,即便將來的版本在對外接口中,完全拿掉這個參數,也不會致使之前的代碼沒法運行。
參數變量是默認聲明的,因此不能用let
或const在函數體中
再次聲明。
使用參數默認值時,函數參數中不能有同名參數,會報錯,沒有定義默認值時使用不報錯。
參數默認值是惰性求值的。每次調用函數,都會從新計算參數的值。
8.2 與解構賦值默認值結合使用
!要注意結合的概念,分辨到底有沒有函數參數的默認值,仍是隻有對象的解構賦值默認值,使用對象的解構賦值,在傳入參數但沒有定義對象值時會起做用,若是使用了函數參數默認值,即使沒有傳參數,也會使用解構賦值默認值。
參數默認值能夠與解構賦值的默認值,結合起來使用。
function foo({x, y = 5})
foo({}) // undefined 5
foo() // TypeError: Cannot read property 'x' of undefined
上面代碼只使用了對象的解構賦值默認值,沒有使用函數參數的默認值。只有當函數foo
的參數是一個對象時,變量x
和y
纔會經過解構賦值生成。若是函數foo
調用時沒提供參數,變量x
和y
就不會生成,從而報錯。經過提供函數參數的默認值,就能夠避免這種狀況。
function foo({x, y = 5} = {})
foo() // undefined 5
上面代碼指定,若是沒有提供參數,函數foo
的參數默認爲一個空對象。
8.3 參數默認值的位置
一般狀況下,定義了默認值的參數,應該是函數的尾參數。由於這樣比較容易看出來,到底省略了哪些參數。若是非尾部的參數設置默認值,實際上這個參數是無法省略的。沒法只省略該參數,而不省略它後面的參數,除非顯式輸入undefined
。若是傳入undefined
,將觸發該參數等於默認值,null
則沒有這個效果。
8.4 函數的 length 屬性
指定了默認值之後,函數的length
屬性,將返回沒有指定默認值的參數個數。也就是說,指定了默認值後,length
屬性將失真。後文的 rest 參數也不會計入length
屬性。
(function(...args) {}).length // 0
若是設置了默認值的參數不是尾參數,即默認值參數放在前面,那麼length
屬性也再也不計入這後面的參數了。
8.5 做用域
一旦設置了參數的默認值,函數進行聲明初始化時,參數會造成一個單獨的做用域,在函數中可用。等到初始化結束,這個做用域就會消失。這種語法行爲,在不設置參數默認值時,是不會出現的。
例如function(x,y=x),這個時候的y的值,和傳入的x參數相等,即使有全局變量x,不會指向全局變量。而function(y=x),這個時候y的值會受到全局變量的影響,全局變量不存在則會報錯。即使函數體裏面從新定義了x,y也不會改變。
參數造成一個單獨做用域。實際執行的是,因爲暫時性死區的緣由,這行代碼會報錯」x 未定義「。var x = 1; function foo(x = x)
x = xlet x = x
若是參數的默認值是一個函數,該函數的做用域也遵照這個規則。
總結就是,有參數默認值,則存在全局做用域、函數內部的局部做用域,函數參數之間的單獨做用域。
8.6 應用
8.6.1
利用參數默認值,能夠指定某一個參數不得省略,若是省略就拋出一個錯誤。
function throwIfMissing() { throw new Error('Missing parameter'); } function foo(mustBeProvided = throwIfMissing()) { return mustBeProvided; } foo() // Error: Missing parameter
上面代碼的foo
函數,若是調用的時候沒有參數,就會調用默認值throwIfMissing
函數,從而拋出一個錯誤。
從上面代碼還能夠看到,參數mustBeProvided
的默認值等於throwIfMissing
函數的運行結果(注意函數名throwIfMissing
以後有一對圓括號),這代表參數的默認值不是在定義時執行,而是在運行時執行。若是參數已經賦值,默認值中的函數就不會運行。
另外,能夠將參數默認值設爲undefined
,代表這個參數是能夠省略的。
function foo(optional = undefined) { ··· }
8.6.2 rest 參數
ES6 引入 rest 參數(形式爲...變量名
),用於獲取函數的多餘參數,這樣就不須要使用arguments
對象了。rest 參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。arguments
對象不是數組,而是一個相似數組的對象。因此爲了使用數組的方法,必須使用Array.prototype.slice.call
先將其轉爲數組。rest 參數就不存在這個問題,它就是一個真正的數組,數組特有的方法均可以使用。
// arguments變量的寫法 function sortNumbers() { return Array.prototype.slice.call(arguments).sort(); } // rest參數的寫法 const sortNumbers = (...numbers) => numbers.sort();
注意,rest 參數以後不能再有其餘參數(即只能是最後一個參數),不然會報錯。
函數的length
屬性,不包括 rest 參數。
(function(a) {}).length // 1
8.6.3 嚴格模式
從 ES5 開始,函數內部能夠設定爲嚴格模式。
ES2016 作了一點修改,規定只要函數參數使用了默認值、解構賦值、或者擴展運算符,那麼函數內部就不能顯式設定爲嚴格模式,不然會報錯。
這樣規定的緣由是,函數內部的嚴格模式,同時適用於函數體和函數參數。可是,函數執行的時候,先執行函數參數,而後再執行函數體。這樣就有一個不合理的地方,只有從函數體之中,才能知道參數是否應該以嚴格模式執行,可是參數卻應該先於函數體執行。雖然能夠先解析函數體代碼,再執行參數代碼,可是這樣無疑就增長了複雜性。所以,標準索性禁止了這種用法,只要參數使用了默認值、解構賦值、或者擴展運算符,就不能顯式指定嚴格模式。
兩種方法能夠規避這種限制。第一種是設定全局性的嚴格模式,這是合法的。
第二種是把函數包在一個無參數的當即執行函數裏面。
8.6.4 name 屬性
函數的name
屬性,返回該函數的函數名。
須要注意的是,ES6 對這個屬性的行爲作出了一些修改。若是將一個匿名函數賦值給一個變量,ES5 的name
屬性,會返回空字符串,而 ES6 的name
屬性會返回實際的函數名。
若是將一個具名函數賦值給一個變量,則 ES5 和 ES6 的name
屬性都返回這個具名函數本來的名字。
Function
構造函數返回的函數實例,name
屬性的值爲anonymous
。
(new Function).name // "anonymous"
bind
返回的函數,name
屬性值會加上bound
前綴。
function foo() {}; foo.bind({}).name // "bound foo" (function(){}).bind({}).name // "bound "
8.6.5 箭頭函數
ES6 容許使用「箭頭」(=>
)定義函數。
若是箭頭函數不須要參數或須要多個參數,就使用一個圓括號表明參數部分。
若是箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,而且使用return
語句返回。
因爲大括號會被引擎解釋爲代碼塊,因此若是箭頭函數直接返回一個對象,必須在返回的對象外面加上括號,不然會報錯。
若是箭頭函數只有一行語句,且不須要返回值,能夠採用下面的寫法,就不用寫大括號了。
let fn = () => void doesNotReturn();
箭頭函數能夠與變量解構結合使用。
const full = ({ first, last }) => first + ' ' + last;
箭頭函數使得表達更加簡潔,簡化回調函數。
使用注意點:
箭頭函數有幾個使用注意點。
(1)函數體內的this
對象,就是定義時所在的對象,而不是使用時所在的對象。箭頭函數致使this
老是指向函數定義生效時所在的對象。
(2)不能夠看成構造函數,也就是說,不可使用new
命令,不然會拋出一個錯誤。
(3)不可使用arguments
對象,該對象在函數體內不存在。若是要用,能夠用 rest 參數代替。
(4)不可使用yield
命令,所以箭頭函數不能用做 Generator 函數。
上面四點中,第一點尤爲值得注意。this
對象的指向是可變的,可是在箭頭函數中,它是固定的。
箭頭函數可讓this
指向固定化,這種特性頗有利於封裝回調函數。
this
指向的固定化,並非由於箭頭函數內部有綁定this
的機制,實際緣由是箭頭函數根本沒有本身的this
,致使內部的this
就是外層代碼塊的this
。正是由於它沒有this
,因此也就不能用做構造函數。
除了this
,如下三個變量在箭頭函數之中也是不存在的,指向外層函數的對應變量:arguments
、super
、new.target
。
因爲箭頭函數沒有本身的this
,因此固然也就不能用call()
、apply()
、bind()
這些方法去改變this
的指向。內部的this
指向外部的this
。
不適用場合:
因爲箭頭函數使得this
從「動態」變成「靜態」,下面兩個場合不該該使用箭頭函數。
第一個場合是定義對象的方法,且該方法內部包括this
。obj.a是一個箭頭函數定義的方法,裏面用到this,這會使得this
指向全局對象,這是由於對象不構成單獨的做用域,致使a箭頭函數定義時的做用域就是全局做用域。
第二個場合是須要動態this
的時候,也不該使用箭頭函數。好比動態獲取dom元素定義的click函數若是是箭頭函數,裏面的this
就是全局對象。若是改爲普通函數,this
就會動態指向被點擊的按鈕對象。
另外,若是函數體很複雜,有許多行,或者函數內部有大量的讀寫操做,不單純是爲了計算值,這時也不該該使用箭頭函數,而是要使用普通函數,這樣能夠提升代碼可讀性。
嵌套的箭頭函數,箭頭函數內部,還能夠再使用箭頭函數。
8.6 尾調用優化
尾調用是函數式編程的一個重要概念,就是指某個函數的最後一步是隻調用另外一個函數,不進行任何賦值操做,必定要返回這個函數。尾調用不必定出如今函數尾部,只要是最後一步操做便可。所以一個函數內部能夠有多個尾調用(if else)。
尾調用優化
尾調用之因此與其餘調用不一樣,就在於它的特殊的調用位置。
咱們知道,函數調用會在內存造成一個「調用記錄」,又稱「調用幀」(call frame),保存調用位置和內部變量等信息。若是在函數A
的內部調用函數B
,那麼在A
的調用幀上方,還會造成一個B
的調用幀。等到B
運行結束,將結果返回到A
,B
的調用幀纔會消失。若是函數B
內部還調用函數C
,那就還有一個C
的調用幀,以此類推。全部的調用幀,就造成一個「調用棧」(call stack)。
尾調用因爲是函數的最後一步操做,因此不須要保留外層函數的調用幀,由於調用位置、內部變量等信息都不會再用到了,只要直接用內層函數的調用幀,取代外層函數的調用幀就能夠了。(???)
尾調用優化即只保留內層函數的調用幀,在函數結束時調用下一個函數,會只保留下一個函數的調用幀。若是全部函數都是尾調用,那麼徹底能夠作到每次執行時,調用幀只有一項,這將大大節省內存。這就是「尾調用優化」的意義。
注意,只有再也不用到外層函數的內部變量,內層函數的調用幀纔會取代外層函數的調用幀,不然就沒法進行「尾調用優化」。
注意,目前只有 Safari 瀏覽器支持尾調用優化,Chrome 和 Firefox 都不支持。
尾遞歸
函數調用自身,稱爲遞歸。若是尾調用自身,就稱爲尾遞歸。
遞歸很是耗費內存,由於須要同時保存成千上百個調用幀,很容易發生「棧溢出」錯誤(stack overflow)。但對於尾遞歸來講,因爲只存在一個調用幀,因此永遠不會發生「棧溢出」錯誤。
function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } factorial(5) // 120
上面代碼是一個階乘函數,計算n
的階乘,最多須要保存n
個調用記錄,複雜度 O(n) 。
若是改寫成尾遞歸,只保留一個調用記錄,複雜度 O(1) 。
function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5, 1) // 120
還有一個比較著名的例子,就是計算 Fibonacci 數列,也能充分說明尾遞歸優化的重要性。
非尾遞歸的 Fibonacci 數列實現以下。
function Fibonacci (n) { if ( n <= 1 ) {return 1}; return Fibonacci(n - 1) + Fibonacci(n - 2); } Fibonacci(10) // 89 Fibonacci(100) // 超時 Fibonacci(500) // 超時
因而可知,「尾調用優化」對遞歸操做意義重大,因此一些函數式編程語言將其寫入了語言規格。ES6 亦是如此,第一次明確規定,全部 ECMAScript 的實現,都必須部署「尾調用優化」。這就是說,ES6 中只要使用尾遞歸,就不會發生棧溢出(或者層層遞歸形成的超時),相對節省內存。
遞歸函數的改寫
尾遞歸的實現,每每須要改寫遞歸函數,確保最後一步只調用自身。作到這一點的方法,就是把全部用到的內部變量改寫成函數的參數。
這樣作的缺點就是不太直觀,第一眼很難看出來。
兩個方法能夠解決這個問題。方法一是在尾遞歸函數以外,再提供一個正常形式的函數。
函數式編程有一個概念,叫作柯里化(currying),意思是將多參數的函數轉換成單參數的形式。這裏也可使用柯里化。
嚴格模式
ES6 的尾調用優化只在嚴格模式下開啓,正常模式是無效的。
這是由於在正常模式下,函數內部有兩個變量,能夠跟蹤函數的調用棧。
func.arguments
:返回調用時函數的參數。func.caller
:返回調用當前函數的那個函數。尾調用優化發生時,函數的調用棧會改寫,所以上面兩個變量就會失真。嚴格模式禁用這兩個變量,因此尾調用模式僅在嚴格模式下生效。
尾遞歸優化的實現
本身實現尾遞歸優化。它的原理很是簡單。尾遞歸之因此須要優化,緣由是調用棧太多,形成溢出,那麼只要減小調用棧,就不會溢出。怎麼作能夠減小調用棧呢?就是採用「循環」換掉「遞歸」。
下面是一個正常的遞歸函數。
function sum(x, y) { if (y > 0) { return sum(x + 1, y - 1); } else { return x; } } sum(1, 100000) // Uncaught RangeError: Maximum call stack size exceeded(…)
上面代碼中,sum
是一個遞歸函數,參數x
是須要累加的值,參數y
控制遞歸次數。一旦指定sum
遞歸 100000 次,就會報錯,提示超出調用棧的最大次數。
蹦牀函數(trampoline)能夠將遞歸執行轉爲循環執行。
function trampoline(f) { while (f && f instanceof Function) { f = f(); } return f; }
上面就是蹦牀函數的一個實現,它接受一個函數f
做爲參數。只要f
執行後返回一個函數,就繼續執行。注意,這裏是返回一個函數,而後執行該函數,而不是函數裏面調用函數,這樣就避免了遞歸執行,從而就消除了調用棧過大的問題。
而後,要作的就是將原來的遞歸函數,改寫爲每一步返回另外一個函數。
function sum(x, y) { if (y > 0) { return sum.bind(null, x + 1, y - 1); } else { return x; } }
上面代碼中,sum
函數的每次執行,都會返回自身的另外一個版本。
如今,使用蹦牀函數執行sum
,就不會發生調用棧溢出。
trampoline(sum(1, 100000)) // 100001
蹦牀函數並非真正的尾遞歸優化,下面的實現纔是。
function tco(f) { var value; var active = false; var accumulated = []; return function accumulator() { accumulated.push(arguments); if (!active) { active = true; while (accumulated.length) { value = f.apply(this, accumulated.shift()); } active = false; return value; } }; } var sum = tco(function(x, y) { if (y > 0) { return sum(x + 1, y - 1) } else { return x } }); sum(1, 100000) // 100001
上面代碼中,tco
函數是尾遞歸優化的實現,它的奧妙就在於狀態變量active
。默認狀況下,這個變量是不激活的。一旦進入尾遞歸優化的過程,這個變量就激活了。而後,每一輪遞歸sum
返回的都是undefined
,因此就避免了遞歸執行;而accumulated
數組存放每一輪sum
執行的參數,老是有值的,這就保證了accumulator
函數內部的while
循環老是會執行。這樣就很巧妙地將「遞歸」改爲了「循環」,然後一輪的參數會取代前一輪的參數,保證了調用棧只有一層。
函數參數的尾逗號
ES2017 容許函數的最後一個參數有尾逗號(trailing comma)。
此前,函數定義和調用時,都不容許最後一個參數後面出現逗號。
若是像上面這樣,將參數寫成多行(即每一個參數佔據一行),之後修改代碼的時候,想爲函數clownsEverywhere
添加第三個參數,或者調整參數的次序,就勢必要在原來最後一個參數後面添加一個逗號。這對於版本管理系統來講,就會顯示添加逗號的那一行也發生了變更。這看上去有點冗餘,所以新的語法容許定義和調用時,尾部直接有一個逗號。
這樣的規定也使得,函數參數與數組和對象的尾逗號規則,保持一致了。
Function.prototype.toString()
ES2019 對函數實例的toString()
方法作出了修改。
toString()
方法返回函數代碼自己,之前會省略註釋和空格。
原始代碼包含註釋,函數名和圓括號之間有空格,可是toString()
方法都把它們省略了。
修改後的toString()
方法,明確要求返回如出一轍的原始代碼。
catch 命令的參數省略
JavaScript 語言的try...catch
結構,之前明確要求catch
命令後面必須跟參數,接受try
代碼塊拋出的錯誤對象。
命令後面帶有參數。} catch (err) { // 處理錯誤} //catcherr
不少時候,catch
代碼塊可能用不到這個參數。可是,爲了保證語法正確,仍是必須寫。ES2019 作出了改變,容許catch
語句省略參數。
} catch { // ...}
9 擴展運算符
擴展運算符(spread)是三個點(...
)。它比如 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。rest是將多餘的參數放到一個數組裏。
擴展運算符與正常的函數參數能夠結合使用,很是靈活。
function f(v, w, x, y, z) { } const args = [0, 1]; f(-1, ...args, 2, ...[3]);
擴展運算符後面還能夠放置三元表達式 ...(x > 0 ? ['a'] : []),
若是擴展運算符後面是一個空數組,則不產生任何效果。
注意,只有函數調用時,擴展運算符才能夠放在圓括號中,不然會報錯。
因爲擴展運算符能夠展開數組,因此再也不須要apply
方法,將數組轉爲函數的參數了。
因爲 JavaScript 不提供求數組最大元素的函數,因此只能套用Math.max
函數,將數組轉爲一個參數序列,而後求最大值。有了擴展運算符之後,就能夠直接用Math.max
了。
ES5 寫法中,push
方法的參數不能是數組,因此只好經過apply
方法變通使用push
方法。有了擴展運算符,就能夠直接將數組傳入push
方法。
(1)複製數組
數組是複合的數據類型,直接複製的話,只是複製了指向底層數據結構的指針,而不是克隆一個全新的數組。ES5 只能用變通方法來複制數組,a1
會返回原數組的克隆,再修改a2
就不會對a1
產生影響。
const a2 = a1.concat();
擴展運算符提供了複製數組的簡便寫法。
const a2 = [...a1];
(2)合併數組
擴展運算符提供了數組合並的新寫法。
arr1.concat(arr2, arr3);
不過,這兩種方法都是淺拷貝,使用的時候須要注意。
const a1 = [{ foo: 1 }]; const a2 = [{ bar: 2 }]; const a3 = a1.concat(a2); const a4 = [...a1, ...a2]; a3[0] === a1[0] // true a4[0] === a1[0] // true //上面代碼中,a3
和a4
是用兩種不一樣方法合併而成的新數組,可是它們的成員都是對原數組成員的引用,這就是淺拷貝。若是修改了原數組的成員,會同步反映到新數組。
a3a4
(3)與解構賦值結合
擴展運算符能夠與解構賦值結合起來,用於生成數組。
若是將擴展運算符用於數組賦值,只能放在參數的最後一位,不然會報錯。const [..start,end] = [1,2,3,4]
(4)字符串
擴展運算符還能夠將字符串轉爲真正的數組,可以正確識別四個字節的 Unicode 字符。[...'hello']
// [ "h", "e", "l", "l", "o" ]
凡是涉及到操做四個字節的 Unicode 字符的函數,都有問題。所以,最好都用擴展運算符改寫。例如字符串的reverse
操做。
(5)實現了 Iterator 接口的對象
任何定義了遍歷器(Iterator)接口的對象,均可以用擴展運算符轉爲真正的數組。
querySelectorAll
方法返回的是一個對象。它不是數組,而是一個相似數組的對象。這時,擴展運算符能夠將其轉爲真正的數組,緣由就在於這個
對象實現了 Iterator 。
那些沒有部署 Iterator 接口的相似數組的對象,擴展運算符就沒法將其轉爲真正的數組。這時,能夠改成使用Array.from
方法將arrayLike
轉爲真正的數組。
(6)Map 和 Set 結構,Generator 函數
擴展運算符內部調用的是數據結構的 Iterator 接口,所以只要具備 Iterator 接口的對象,均可以使用擴展運算符,好比 Map 結構。
10 對象的新增方法
Object.is(NaN, NaN) // true
Object.assign(target, source1, source2);
//同名屬性:後面的屬性會覆蓋前面的屬性。
//只有一個參數會返回該參數,若該參數不是對象會被轉爲對象。因爲undefined
和null
沒法轉成對象,因此若是它們做爲參數,就會報錯。但若是它們不在首位,會被跳過。
//字符串會以undefinednull(以字符數組的形式)合入目標對象,數值和布爾值都會被忽略。object.assign({},'abc') = {'0':'a','1':'b','2':'c'}
//Object.assign
拷貝的屬性是有限制的,只拷貝源對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false
)。
//Object.assign
方法實行的是淺拷貝,而不是深拷貝。也就是說,若是源對象某個屬性的值是對象,那麼目標對象拷貝獲得的是這個對象的引用。這個對象的任何變化,都會反映到目標對象上面。Object.assignenumerable: falseObject.assign
Object.defineProperty({}, key, value)
常見用途(1)爲對象添加屬性爲對象添加方法爲屬性指定默認值
Object.getOwnPropertyDescriptors()
Set
new Set()
過濾數組重複值、參數爲字符串時將字符串拆爲字符數組,認爲NaN與自身相等,每一個對象都不同{} {}
Set實例屬性:Set.prototype.constructor、Set.prototype.size
Set實例方法:add、delete、has、clear
Array.from將Set結構轉爲數組,可用於去除重複數組元素
Set
的遍歷順序就是插入順序,調用時就能保證按照添加順序調用
Set 有4個遍歷方法:keys、values、entries、forEach
Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),因此keys
方法和values
方法的行爲徹底一致。
Set 可用for...of遍歷,可用forEach(函數,this)
擴展運算符和set結合可用於去除重複數組元素,如此轉爲數組後可以使用map、filter方法
使用 Set 能夠很容易地實現並集、交集和差集
在遍歷操做中同步改變set結構:
set = new Set([...set].map(val => val * 2)); set = new Set(Array.from(set, val => val * 2));
WeakSet
成員只能是對象。
WeakSet 裏面的引用,都不計入垃圾回收機制。所以,WeakSet 適合臨時存放一組對象,以及存放跟對象綁定的信息。只要這些對象在外部消失,它在 WeakSet 裏面的引用就會自動消失。
WeakSet 內部有多少個成員,取決於垃圾回收機制有沒有運行,所以不可預測,因此不可遍歷。
const arr = new WeakSet([3,4]) //報錯:參數的成員不是對象,是數字。
方法:add、delete、has
Map
JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),可是傳統上只能用字符串看成鍵。Map 數據結構相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。
方法:set、get、has、delete、size、clear(無返回值)
遍歷方法:keys、values、entries、forEach
不只僅是數組,任何具備 Iterator 接口、且每一個成員都是一個雙元素的數組的數據結構(詳見《Iterator》一章)均可以看成Map
構造函數的參數。這就是說,Set
和Map
均可以用來生成新的 Map。
若是對同一個鍵屢次賦值,後面的值將覆蓋前面的值。
若是讀取一個未知的鍵,則返回undefined
。
Map 的鍵其實是跟內存地址綁定的,只要內存地址不同,就視爲兩個鍵。這就解決了同名屬性碰撞(clash)的問題,咱們擴展別人的庫的時候,若是使用對象做爲鍵名,就不用擔憂本身的屬性與原做者的屬性同名。
若是 Map 的鍵是一個簡單類型的值(數字、字符串、布爾值),則只要兩個值嚴格相等,Map 將其視爲一個鍵,好比0
和-0
就是一個鍵,布爾值true
和字符串true
則是兩個不一樣的鍵。另外,undefined
和null
也是兩個不一樣的鍵。雖然NaN
不嚴格相等於自身,但 Map 將其視爲同一個鍵。
Map 的遍歷順序就是插入順序。
Map 結構轉爲數組結構,比較快速的方法是使用擴展運算符(...
)。
結合數組的map
方法、filter
方法,能夠實現 Map 的遍歷和過濾(Map 自己沒有map
和filter
方法):將Map轉爲數組使用map、filter方法再傳給新Map的參數
數組、對象、json轉map
WeakMap
WeakMap
與Map
的區別有兩點。
首先,WeakMap
只接受對象做爲鍵名(null
除外),不接受其餘類型的值做爲鍵名。
其次,WeakMap
的鍵名所指向的對象,不計入垃圾回收機制。
proxy
get
方法用於攔截某個屬性的讀取操做,能夠接受三個參數,依次爲目標對象、屬性名和 proxy 實例自己(嚴格地說,是操做行爲所針對的對象