ECMAScript 6 字符串和數值的拓展

一、模板字符串

在我們的變成過程中,有時候我們需要在字符串中編寫大量的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 };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章