整理自阮一峯 ECMAScript 入門html
<ul>
標籤前面會有一個換行。若是你不想要這個換行,可使用trim方法消除它。$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim()); 複製代碼
let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false 複製代碼
上面代碼表示,使用第二個參數n時,endsWith的行爲與其餘兩個方法有所不一樣。它針對前n個字符,而其餘兩個方法針對從第n個位置直到字符串結束。node
repeat方法返回一個新字符串,表示將原字符串重複n次。es6
'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello" 'na'.repeat(0) // "" 複製代碼
ES2017 引入了字符串補全長度的功能。若是某個字符串不夠指定長度,會在頭部或尾部補全。padStart()用於頭部補全,padEnd()用於尾部補全。 padStart()和padEnd()一共接受兩個參數,第一個參數是字符串補全生效的最大長度,第二個參數是用來補全的字符串。正則表達式
'x'.padStart(4, 'ab') // 'abax' 'x'.padEnd(5, 'ab') // 'xabab' 複製代碼
padStart()的常見用途是爲數值補全指定位數。下面代碼生成 10 位的數值字符串。算法
'1'.padStart(10, '0') // "0000000001" '12'.padStart(10, '0') // "0000000012" '123456'.padStart(10, '0') // "0000123456" 複製代碼
ES2019 對字符串實例新增了trimStart()和trimEnd()這兩個方法。它們的行爲與trim()一致,trimStart()消除字符串頭部的空格,trimEnd()消除尾部的空格。它們返回的都是新字符串,不會修改原始字符串。編程
const s = ' abc '; s.trim() // "abc" s.trimStart() // "abc " s.trimEnd() // " abc" 複製代碼
matchAll()方法返回一個正則表達式在當前字符串的全部匹配。 若是一個正則表達式在字符串裏面有多個匹配,如今通常使用g修飾符或y修飾符,在循環裏面逐一取出。ES6增長了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"] 複製代碼
遍歷器轉爲數組是很是簡單的,使用...運算符和Array.from()方法就能夠了。安全
// 轉爲數組方法一
[...string.matchAll(regex)]
// 轉爲數組方法二
Array.from(string.matchAll(regex))
複製代碼
ES6 提供了二進制和八進制數值的新的寫法,分別用前綴0b(或0B)和0o(或0O)表示。bash
0b111110111 === 503 // true 0o767 === 503 // true 複製代碼
若是要將0b和0o前綴的字符串數值轉爲十進制,要使用Number方法。markdown
Number('0b111') // 7 Number('0o10') // 8 複製代碼
Number對象能夠將任何數據類型轉換成數值,包括 布爾值,日期,二進制數,八進制數,數字字符串(parseInt,parseFloat只能夠轉化數字字符串)。
Number 對象的方法
指數運算符(**
) 這個運算符的一個特色是右結合,而不是常見的左結合。多個指數運算符連用時,是從最右邊開始計算的。 例如:
// 至關於 3 ** (3 ** 2)
3 ** 3 ** 2
// 19683
複製代碼
指數運算符能夠與等號結合,造成一個新的賦值運算符(**=)
let a = 1.5; a **= 2; // 等同於 a = a * a; let b = 4; b **= 3; // 等同於 b = b * b * b; 複製代碼
ES6 容許爲函數的參數設置默認值,即直接寫在參數定義的後面。
function log(x, y = 'World') { console.log(x, y); } 複製代碼
tip:一般狀況下,定義了默認值的參數,應該是函數的尾參數。由於這樣比較容易看出來,到底省略了哪些參數。若是非尾部的參數設置默認值,實際上這個參數是無法省略的。
rest 參數與擴展運算符實際上是同一種東西,就是... 只不過使用場景不一樣,他們的名字也不同。
function sumRest (...m) {
{a,b,...m}
console.log(...array);
var f = v => v;
若是箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,而且使用return語句返回。 因爲大括號被解釋爲代碼塊,因此若是箭頭函數直接返回一個對象,必須在對象外面加上括號,不然會報錯。
let getTempItem = id => ({ id: id, name: "Temp" }); 複製代碼
注意點 箭頭函數體內的this對象,就是定義時所在的對象,這個this是固定的,注意它沒有本身的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。 箭頭函數轉成 ES5 的代碼以下:
// ES6 function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } // ES5 function foo() { var _this = this; setTimeout(function () { console.log('id:', _this.id); }, 100); } 複製代碼
const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;
複製代碼
const arr1 = ['a', 'b']; const arr2 = ['c']; const arr3 = ['d', 'e']; // ES5 的合併數組 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6 的合併數組 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ] 複製代碼
須要注意的是:這兩種方法都是淺拷貝,使用的時候須要注意。合併而成的新數組,它們的成員都是對原數組成員的引用,這就是淺拷貝。若是修改了引用指向的值,會同步反映到新數組。
任何定義了遍歷器(Iterator)接口的對象(參閱 Iterator 一章),均可以用擴展運算符轉爲真正的數組。
let nodeList = document.querySelectorAll('div'); let array = [...nodeList]; 複製代碼
上面代碼中,querySelectorAll方法返回的是一個NodeList對象。它不是數組,而是一個相似數組的對象。這時,擴展運算符能夠將其轉爲真正的數組,緣由就在於NodeList對象實現了 Iterator 。
數組的成員有時仍是數組,Array.prototype.flat()用於將嵌套的數組「拉平」,變成一維的數組。該方法返回一個新數組,對原數據沒有影響。
[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]
複製代碼
上面代碼中,原數組的成員裏面有一個數組,flat()方法將子數組的成員取出來,添加在原來的位置。
flat()默認只會「拉平」一層,若是想要「拉平」多層的嵌套數組,能夠將flat()方法的參數寫成一個整數,表示想要拉平的層數,默認爲1。 若是flat()的參數爲2,表示要「拉平」兩層的嵌套數組。 若是無論有多少層嵌套,都要轉成一維數組,能夠用Infinity關鍵字做爲參數。
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]
複製代碼
編程實務中,若是讀取對象內部的某個屬性,每每須要判斷一下該對象是否存在。好比,要讀取message.body.user.firstName,安全的寫法是寫成下面這樣。
const firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default'; 或 const fooInput = myForm.querySelector('input[name=foo]') const fooValue = fooInput ? fooInput.value : undefined 複製代碼
這樣的層層判斷很是麻煩,所以 ES2020 引入了「鏈判斷運算符」(optional chaining operator)?.,簡化上面的寫法。
const firstName = message?.body?.user?.firstName || 'default'; const fooValue = myForm.querySelector('input[name=foo]')?.value 複製代碼
上面代碼使用了?.運算符,直接在鏈式調用的時候判斷,左側的對象是否爲null或undefined。若是是的,就再也不往下運算,而是返回undefined。
鏈判斷運算符有三種用法。
obj?.prop // 對象屬性
obj?.[expr] // 同上
func?.(...args) // 函數或對象方法的調用
複製代碼
下面是判斷對象方法是否存在,若是存在就當即執行的例子。
iterator.return?.()
複製代碼
上面代碼中,iterator.return若是有定義,就會調用該方法,不然直接返回undefined。
對於那些可能沒有實現的方法,這個運算符尤爲有用。
if (myForm.checkValidity?.() === false) { // 表單校驗失敗 return; } 複製代碼
(1). 短路機制
a?.[++x]
// 等同於
a == null ? undefined : a[++x]
複製代碼
上面代碼中,若是a是undefined或null,那麼x不會進行遞增運算。也就是說,鏈判斷運算符一旦爲真,右側的表達式就再也不求值。 (2). delete 運算符
delete a?.b
// 等同於
a == null ? undefined : delete a.b
複製代碼
(3). 括號的影響
若是屬性鏈有圓括號,鏈判斷運算符對圓括號外部沒有影響,只對圓括號內部有影響。
(a?.b).c // 等價於 (a == null ? undefined : a.b).c 上面代碼中,?.對圓括號外部沒有影響,無論a對象是否存在,圓括號後面的.c老是會執行。
通常來講,使用?.運算符的場合,不該該使用圓括號。
(4).報錯場合
如下寫法是禁止的,會報錯。
// 構造函數
new a?.()
new a?.b()
// 鏈判斷運算符的右側有模板字符串
a?.`{b}`
a?.b`{c}`
// 鏈判斷運算符的左側是 super
super?.()
super?.foo
// 鏈運算符用於賦值運算符左側
a?.b = c
複製代碼
(5).右側不得爲十進制數值
爲了保證兼容之前的代碼,容許foo?.3:0被解析成foo ? .3 : 0,所以規定若是?.後面緊跟一個十進制數字,那麼?.再也不被當作是一個完整的運算符,而會按照三元運算符進行處理,也就是說,那個小數點會歸屬於後面的十進制數字,造成一個小數。
讀取對象屬性的時候,若是某個屬性的值是null或undefined,有時候須要爲它們指定默認值。常見作法是經過||運算符指定默認值。
const headerText = response.settings.headerText || 'Hello, world!'; const animationDuration = response.settings.animationDuration || 300; const showSplashScreen = response.settings.showSplashScreen || true; 複製代碼
上面的三行代碼都經過||運算符指定默認值,可是這樣寫是錯的。開發者的原意是,只要屬性的值爲null或undefined,默認值就會生效,可是屬性的值若是爲空字符串或false或0,默認值也會生效。
爲了不這種狀況,ES2020 引入了一個新的 Null 判斷運算符??。它的行爲相似||,可是隻有運算符左側的值爲null或undefined時,纔會返回右側的值。
const headerText = response.settings.headerText ?? 'Hello, world!'; const animationDuration = response.settings.animationDuration ?? 300; const showSplashScreen = response.settings.showSplashScreen ?? true; 複製代碼
上面代碼中,默認值只有在屬性值爲null或undefined時,纔會生效。
這個運算符的一個目的,就是跟鏈判斷運算符?.配合使用,爲null或undefined的值設置默認值。
const animationDuration = response.settings?.animationDuration ?? 300;
複製代碼
上面代碼中,response.settings若是是null或undefined,就會返回默認值300。
ES5 比較兩個值是否相等,只有兩個運算符:相等運算符(==)和嚴格相等運算符(===)。它們都有缺點,前者會自動轉換數據類型,後者的NaN不等於自身,以及+0等於-0。JavaScript 缺少一種運算,在全部環境中,只要兩個值是同樣的,它們就應該相等。
ES6 提出「Same-value equality」(同值相等)算法,用來解決這個問題。Object.is就是部署這個算法的新方法。它用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的行爲基本一致。
Object.is('foo', 'foo') // true Object.is({}, {}) // false 複製代碼
不一樣之處只有兩個:一是+0不等於-0,二是NaN等於自身。
+0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true 複製代碼
ES5 引入了Object.keys方法,返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵名。
var obj = { foo: 'bar', baz: 42 }; Object.keys(obj) // ["foo", "baz"] 複製代碼
Object.values方法返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值。
const obj = { foo: 'bar', baz: 42 }; Object.values(obj) // ["bar", 42] 複製代碼
返回數組的成員順序爲:
例如:
const obj = { 100: 'a', 2: 'b', 7: 'c' }; Object.values(obj) // ["b", "c", "a"] 複製代碼
上面代碼中,屬性名爲數值的屬性,是按照數值大小,從小到大遍歷的,所以返回的順序是b、c、a。
規則以下:
Object.values('foo') // ['f', 'o', 'o'] 複製代碼
Object.values(42) // [] Object.values(true) // [] 複製代碼
Object.entries()方法返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值對數組。
const obj = { foo: 'bar', baz: 42 }; Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ] 複製代碼
除了返回值不同,該方法的行爲與Object.values基本一致。
let obj = { one: 1, two: 2 }; for (let [k, v] of Object.entries(obj)) { console.log( `${JSON.stringify(k)}: ${JSON.stringify(v)}` ); } // "one": 1 // "two": 2 複製代碼
const obj = { foo: 'bar', baz: 42 }; const map = new Map(Object.entries(obj)); map // Map { foo: "bar", baz: 42 } 複製代碼
Object.fromEntries()方法是Object.entries()的逆操做,用於將一個鍵值對數組轉爲對象。
Object.fromEntries([ ['foo', 'bar'], ['baz', 42] ]) // { foo: "bar", baz: 42 } 複製代碼
該方法的主要目的,是將鍵值對的數據結構還原爲對象,所以特別適合將 Map 結構轉爲對象。
// 例一 const entries = new Map([ ['foo', 'bar'], ['baz', 42] ]); Object.fromEntries(entries) // { foo: "bar", baz: 42 } // 例二 const map = new Map().set('foo', true).set('bar', false); Object.fromEntries(map) // { foo: true, bar: false } 複製代碼
該方法的一個用處是配合URLSearchParams對象,將查詢字符串轉爲對象。
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux')) // { foo: "bar", baz: "qux" } 複製代碼
ES6 提供了新的數據結構 Set。它相似於數組,可是成員的值都是惟一的,沒有重複的值。 Set自己是一個構造函數,用來生成 Set 數據結構。 Set 接收一個數組或者類數組做爲參數,用來初始化。
// 例一 const set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4] // 可使用 Set 來給數組去重 // 例三 const set = new Set(document.querySelectorAll('div')); set.size // 56 複製代碼
上面的代碼也展現了去除數組重複成員的方法
// 去除數組的重複成員
[...new Set(array)]
複製代碼
也能夠用來去除字符串裏面的重複字符串
[...new Set('ababbc')].join('') // "abc" 複製代碼
Array.from 方法也能夠將 Set結構轉換爲數組,這就提供了去重的另外一種方法
const items = new Set([1, 1, 2, 3, 3, 4, 5]);
const array = Array.from(items);
複製代碼
Set 結構的四個遍歷方法
let set = new Set(['red', 'green', 'blue']); for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"] 複製代碼
Map 數據結構,它相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。也就是說,Object 結構提供了「字符串—值」的對應,Map 結構提供了「值—值」的對應,是一種更完善的 Hash 結構實現。若是你須要「鍵值對」的數據結構,Map 比 Object 更合適。
Map 函數接收數組,或者類數組(每一個成員都是一個雙元素的數組,看成Map構造函數的參數,這就是說,Set 和 Map 均可以用來生成新的 Map。
const set = new Set([ ['foo', 1], ['bar', 2] ]); const m1 = new Map(set); m1.get('foo') // 1 const m2 = new Map([['baz', 3]]); const m3 = new Map(m2); m3.get('baz') // 3 複製代碼
若是對同一個鍵屢次賦值,後面的值將覆蓋前面的值。
const map = new Map(); map .set(1, 'aaa') .set(1, 'bbb'); map.get(1) // "bbb" 複製代碼
注意,只有對同一個對象的引用,Map 結構纔將其視爲同一個鍵。這一點要很是當心。
const map = new Map(); map.set(['a'], 555); map.get(['a']) // undefined 複製代碼
上面代碼的set和get方法,表面是針對同一個鍵,但實際上這是兩個不一樣的數組實例,內存地址是不同的,所以get方法沒法讀取該鍵,返回undefined。
const map = new Map(); const list = ['a'] map.set(list, 555); map.get(list) // 555 複製代碼
將數組賦值給一個變量後,就能夠獲得想象中的結果了。
若是 Map 的鍵是一個簡單類型的值(數字、字符串、布爾值),則只要兩個值嚴格相等,Map 將其視爲一個鍵,好比0和-0就是一個鍵,布爾值true和字符串true則是兩個不一樣的鍵。另外,undefined和null也是兩個不一樣的鍵。雖然NaN不嚴格相等於自身,但 Map 將其視爲同一個鍵
let map = new Map(); map.set(-0, 123); map.get(+0) // 123 map.set(true, 1); map.set('true', 2); map.get(true) // 1 map.set(undefined, 3); map.set(null, 4); map.get(undefined) // 3 map.set(NaN, 123); map.get(NaN) // 123 複製代碼