一、模板字符串
在我們的變成過程中,有時候我們需要在字符串中編寫大量的HTML結構代碼:
const obj = {
img: 'http://www.baidu.com/test.png',
title: '離思',
subTitle: '取次花叢懶回顧,半緣修道半緣君。',
mess: ['唐','元稹']
};
let el = '<div>'+
'<img src="'+ obj.img +'">'+
'<h3>'+ obj.title +'</h3>'+
'<p>'+obj.subTitle+'</p>'+
'<div><span>'+ obj.mess[0] +'</span><span>'+ obj.mess[1] +'</span></div>'+
'</div>';
這種寫法相當繁瑣不方便,ES6引入了模板字符串解決這個問題:
const obj = {
img: 'http://www.baidu.com/test.png',
title: '離思',
subTitle: '取次花叢懶回顧,半緣修道半緣君。',
mess: ['唐','元稹']
};
let el = `
<div>
<img src="${obj.img}">
<h3>${obj.title}</h3>
<p>${obj.subTitle}</p>
<div>
<span>${obj.mess[0]}</span>
<span>${obj.mess[1]}</span>
</div>
</div>
`;
模板字符串是增強版的字符串,用反引號(``)標識,它可以當做普通字符串,也可以用來定義多行字符串,或者在字符串中嵌入變量。
注:
- 如果模板字符串中要使用反引號,那麼需要使用反斜槓轉義。
- 如果使用模板字符串表示多行字符串,所有的空格和縮進都會被保留在輸出中。
- 在模板字符串中嵌入變量,需要將變量名寫在${}中。
- ${}的大括號中可以放入任意的JavaScript表達式,可以進行運算,以及引用對象屬性。
- 如果大括號中的值不是字符串,將按照一般的規則轉爲字符串。比如,大括號中是一個對象,將默認調用對象的toString方法。
- 模板字符串甚至還能嵌套。
字符串的新增方法
傳統上,JavaScript 只有indexOf方法,可以用來確定一個字符串是否包含在另一個字符串中。ES6 又提供了三種新方法。
- includes():返回布爾值,表示是否找到了參數字符串。
- startsWith():返回布爾值,表示參數字符串是否在原字符串的頭部。
- endsWith():返回布爾值,表示參數字符串是否在原字符串的尾部。
let s = 'Hello world!';
s.startsWith('Hello');
s.endsWith('!');
s.includes('o');
這三個方法都支持第二個參數,表示開始搜索的位置。
let s = 'Hello world!';
s.startsWith('world', 6);
s.endsWith('Hello', 5);
s.includes('Hello', 6);
拓展: 爲String類添加一個loIncludes()函數,實現和includes()一樣的效果。
String.prototype.loIncludes = function(str, i) {
const newStr = this.substr(i);
const currI = newStr.indexOf(str);
if(currI < 0 || newStr == '') return false;
return true;
};
let testStr = 'hello world';
const res = testStr.loIncludes('o');
二、字符串的實例方法
1. repeat()返回一個新字符串,表示將原來的字符串重複了若干次。
'x'.repeat(3); // "xxx"
'hello'.repeat(2); // "hellohello"
'na'.repeat(0); // ""
'na'.repeat(2.9); // "nana"
'na'.repeat(Infinity); // RangeError
'na'.repeat(-1); // RangeError
2. 實例方法:trimStart(),trimEnd()。
const s = ' abc ';
s.trim(); // "abc"
s.trimStart(); // "abc "
s.trimEnd(); // " abc"
數組的拓展
一、拓展運算符
擴展運算符(spread)是三個點(…),將一個數組轉爲用逗號分隔的參數序列(用非循環的代碼方式展開一個數組)。
console.log(...[1, 2, 3]); // 1 2 3
console.log(1, ...[2, 3, 4], 5); // 1 2 3 4 5
[...document.querySelectorAll('div')]; // [<div>, <div>, <div>]
拓展運算符的應用
1. 當函數形參不確定時,可以代替函數內部的arguments對象。
如封裝一個求若干number的和的函數。
// 在舊的語法中,我們會使用arguments對象去接受數量不定的形參
function getSum() {
let sum = 0;
for(let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
getSum(40, 2, 39); // 81
getSum(88, 4); // 92
這樣處理可以完成效果,但是arguments對象本身不是數組,不能調用數組的函數,只能去循環它,如果代碼需求中需要數組處理,那麼arguments對象不能調用數組的代碼,代碼的邏輯處理就會很麻煩。所以ES6建議用拓展運算符(…)代替對於arguments對象的使用。並且把由拓展運算符聲明的形參稱爲rest參數。
// 求若干個數的和
function getSum(...arr) {
let sum = 0;
for(let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
getSum(40, 2, 39); // 81
getSum(88, 4); // 92
注意: 雖然利用rest參數有時候代碼和效果都和arguments沒什麼區別,但是rest參數是數組,可以調用數組相關函數,arguments是個對象,不能調用數組相關函數。
2. 數組複製。
數組是複合的數據類型,直接複製的話,只是複製了指向底層數據結構的指針,而不是克隆一個全新的數組。
const a1 = [1, 2];
const a2 = a1;
ES5 只能用變通方法來複制數組:
const a1 = [1, 2];
const a2 = a1.concat();
擴展運算符提供了複製數組的簡便寫法:
const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a3] = a1;
3. 合併數組。
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合併數組
const newArr1 = arr1.concat(arr2, arr3);
// ES6 的合併數組
const newArr2 = [...arr1, ...arr2, ...arr3];
4. 與解構賦值結合使用。
const [a, ...b] = [1, 2, 3, 4, 5];
const [c, ...d] = [];
const [e, ...f] = ["foo"];
// 如果將擴展運算符用於數組賦值,只能放在參數的最後一位,否則會報錯。
const [...g, h] = [1, 2, 3, 4, 5]; // 報錯
const [i, ...j, k] = [1, 2, 3, 4, 5]; // 報錯
5. 將字符串展開成一個數組。
const list = [...'hello'];
二、Array.from()函數
Array.from方法用於將兩類對象轉爲真正的數組:類似數組的對象(array-like object)和可遍歷(iterable)的對象。
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
實際應用中,常見的類似數組的對象是 DOM 操作返回的 NodeList 集合,以及函數內部的arguments對象。Array.from都可以將它們轉爲真正的數組。
Array.from方法還支持類似數組的對象。所謂類似數組的對象,本質特徵只有一點,即必須有length屬性。因此,任何有length屬性的對象,都可以通過Array.from方法轉爲數組,而此時擴展運算符就無法轉換。
Array.from({ length: 3 }); // [ undefined, undefined, undefined]
Array.from還可以接受第二個參數,作用類似於數組的map方法,用來對每個元素進行處理,將處理後的值放入返回的數組:
let obj = {
'0': 4,
'1': 30,
'2': 9,
length: 3
};
Array.from(obj, (x) => x * x); // [16, 900, 81]
三、Array.of()函數
Array.of方法用於將一組值,轉換爲數組。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
這個方法的主要目的,是彌補數組構造函數Array()的不足。因爲參數個數的不同,會導致Array()的行爲有差異。
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
Array.of基本上可以用來替代Array()或new Array(),並且不存在由於參數不同而導致的重載。它的行爲非常統一。
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]
Array.of總是返回參數值組成的數組。如果沒有參數,就返回一個空數組。
四、數組實例的copyWithin()
數組實例的copyWithin()方法,在當前數組內部,將指定位置的成員複製到其他位置(會覆蓋原有成員),然後返回當前數組。也就是說,使用這個方法,會修改當前數組。
Array.prototype.copyWithin(target, start = 0, end = this.length);
它接受三個參數:
- target(必需):從該位置開始替換數據。如果爲負值,表示倒數。
- start(可選):從該位置開始讀取數據,默認爲 0。如果爲負值,表示從末尾開始計算。
- end(可選):到該位置前停止讀取數據,默認等於數組長度。如果爲負值,表示從末尾開始計算。
這三個參數都應該是數值,如果不是,會自動轉爲數值。
[1, 2, 3, 4, 5].copyWithin(0, 3); //[4, 5, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, 3, 4); // [4, 2, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, -2, -1); // [4, 2, 3, 4, 5]
五、數組實例的 find() 和 findIndex()
數組實例的find方法,用於找出第一個符合條件的數組成員。它的參數是一個回調函數,所有數組成員依次執行該回調函數,直到找出第一個返回值爲true的成員,然後返回該成員。如果沒有符合條件的成員,則返回undefined。
[1, 4, -5, 10].find((n) => n < 0); // -5
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}); // 10
數組實例的findIndex方法的用法與find方法非常類似,返回第一個符合條件的數組成員的位置,如果所有成員都不符合條件,則返回-1。
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}); // 2
這兩個方法都可以接受第二個參數,用來綁定回調函數的this對象。
六、數組實例的 fill()
fill方法使用給定值,填充一個數組。
['a', 'b', 'c'].fill(7); // [7, 7, 7]
new Array(3).fill(7); // [7, 7, 7]
上面代碼表明,fill方法用於空數組的初始化非常方便。數組中已有的元素,會被全部抹去。
fill方法還可以接受第二個和第三個參數,用於指定填充的起始位置和結束位置。
['a', 'b', 'c'].fill(7, 1, 2); // ['a', 7, 'c']
七、數組實例的 includes()
Array.prototype.includes方法返回一個布爾值,表示某個數組是否包含給定的值,與字符串的includes方法類似。
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, NaN].includes(NaN); // true
該方法的第二個參數表示搜索的起始位置,默認爲0。如果第二個參數爲負數,則表示倒數的位置,如果這時它大於數組長度(比如第二個參數爲-4,但數組長度爲3),則會重置爲從0開始。
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
沒有該方法之前,我們通常使用數組的indexOf方法,檢查是否包含某個值。indexOf方法有兩個缺點,一是不夠語義化,它的含義是找到參數值的第一個出現位置,所以要去比較是否不等於-1,表達起來不夠直觀。二是,它內部使用嚴格相等運算符(===)進行判斷,這會導致對NaN的誤判。
[NaN].indexOf(NaN); // -1
[NaN].includes(NaN); // true
八、數組實例的 flat(),flatMap()
數組的成員有時還是數組,Array.prototype.flat()用於將嵌套的數組“拉平”,變成一維的數組。該方法返回一個新數組,對原數據沒有影響。
[1, 2, [3, 4]].flat(); // [1, 2, 3, 4]
flat()默認只會“拉平”一層,如果想要“拉平”多層的嵌套數組,可以將flat()方法的參數寫成一個整數,表示想要拉平的層數,默認爲1。
[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]
如果不管有多少層嵌套,都要轉成一維數組,可以用Infinity關鍵字作爲參數
[1, [2, [3]]].flat(Infinity); // [1, 2, 3]
flatMap()方法對原數組的每個成員執行一個函數(相當於執行Array.prototype.map()),然後對返回值組成的數組執行flat()方法。該方法返回一個新數組,不改變原數組。
// 相當於 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2]); // [2, 4, 3, 6, 4, 8]
flatMap()只能展開一層數組。
// 相當於 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]]); // [[2], [4], [6], [8]]
flatMap()方法的參數是一個遍歷函數,該函數可以接受三個參數,分別是當前數組成員、當前數組成員的位置(從零開始)、原數組。
arr.flatMap(function callback(currentValue[, index[, array]]) {
// ...
}[, thisArg]);
flatMap()方法還可以有第二個參數,用來綁定遍歷函數裏面的this。
九、數組的空位
ES6 明確會將數組中的空位轉爲undefined。
對象的拓展和對象的新增方法
一、屬性的簡潔表示法
ES6 允許在對象之中,直接寫變量。這時,屬性名爲變量名, 屬性值爲變量的值。
const foo = 'bar';
const baz = {foo};
// 等同於
// const baz = {foo: foo};
除了屬性簡寫,方法也可以簡寫。
const o = {
method() {
return "Hello!";
}
};
// 等同於
/* const o = {
method: function() {
return "Hello!";
}
}; */
二、屬性名錶達式
JavaScript 定義對象的屬性,有兩種方法。
// 方法一
obj.foo = true;
// 方法二
// obj['a' + 'bc'] = 123;
但是,如果使用字面量方式定義對象(使用大括號),在 ES5 中只能使用方法一(標識符)定義屬性。
var obj = {
foo: true,
abc: 123
};
ES6 允許字面量定義對象時,用方法二(表達式)作爲對象的屬性名,即把表達式放在方括號內。
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
表達式還可以用於定義方法名。
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
三、super關鍵詞
我們知道,this關鍵字總是指向函數所在的當前對象,ES6 又新增了另一個類似的關鍵字super,指向當前對象的原型對象。
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
console.log(this.foo);
console.log(super.foo);
}
};
// 設置obj的新原型爲proto
Object.setPrototypeOf(obj, proto);
obj.find(); // this.foo: 'world', super.foo: 'hello'
上面代碼中,對象obj.find()方法之中,通過super.foo引用了原型對象proto的foo屬性,但是this.foo返回的依舊是當前對象的foo屬性。
注意,super關鍵字表示原型對象時,只能用在對象的方法之中,用在其他地方都會報錯。
// 報錯
const obj = {
foo: super.foo
}
// 報錯
const obj = {
foo: () => super.foo
}
// 報錯
const obj = {
foo: function () {
return super.foo
}
}
四、對象的解構賦值
對象的解構賦值用於從一個對象取值,相當於將目標對象自身的所有可遍歷的(enumerable)、但尚未被讀取的屬性,分配到指定的對象上面。所有的鍵和它們的值,都會拷貝到新對象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
上面代碼中,變量z是解構賦值所在的對象。它獲取等號右邊的所有尚未讀取的鍵(a和b),將它們連同值一起拷貝過來。
由於解構賦值要求等號右邊是一個對象,所以如果等號右邊是undefined或null,就會報錯,因爲它們無法轉爲對象。
let { ...z } = null; // 運行時錯誤
let { ...z } = undefined; // 運行時錯誤
解構賦值必須是最後一個參數,否則會報錯。
let { ...x, y, z } = someObject; // 句法錯誤
let { x, ...y, ...z } = someObject; // 句法錯誤
對象的拓展運算符
對象的擴展運算符(…)用於取出參數對象的所有可遍歷屬性,拷貝到當前對象之中。
let z = { a: 3, b: 4 };
let n = { ...z };