ES6 中字符串的擴展

1. 字符的Unicode表示法

JavaScript容許採用 \uxxxx 形式表示一個字符,其中 xxxx 表示字符的 Unicode 碼點。正則表達式

"\u0061" // 表示小寫字母"a"

可是這種表示法只限於碼點在 \u0000-\uFFFF 之間的字符,有些字符的 Unicode 編碼超出了這個範圍,那麼就必須使用2個雙字節的形式表示。數組

"\uD842\uDFB7" // "𠮷" 注意不是吉祥的"吉"
"\u5409" // "吉" 這個纔是吉祥的"吉"

ES5 中若是在 \u 後面超過 oxFFFF 的數值,如 "\u0061我" 輸出結果爲 a我"\u0061我"JS 引擎看來就是 "\u0061+'我'" 後面的經過字符串拼接拼接上。函數

ES6 對這一點作出了改進,只要將碼點放入大括號,就能正確解讀該字符。測試

例如 \u20BB7 表示的是 "𠮷" ,在 ES5"\u20BB7"JS 引擎解析成 "7" ,這是由於 \u20BB 是一個不可打印的字符,因此只會顯示一個空格,後面拼接上一個 7 .this

ES6"\u{20BB7}" 的解析結果爲 "𠮷" .編碼

2. codePointAt()

JavaScript內部,字符以 UTF-16 的格式存儲,每一個字符固定爲2個字節(範圍 \u0000-\uFFFF),可是有些字符的碼點是大於 0xFFFF 的,JavaScript會認爲它是兩個字符。設計

var s1 = "你好";
var s2 = "𠮷";

s1.length // 2
s2.length // 2

這是由於 𠮷 的碼點大於 0xFFFFJS 引擎認爲它是兩個字符,即佔四個字節。code

s2.charCodeAt(0) // 55362
s2.charCodeAt(0) // 57271
s2.codePointAt(0) // 134071=0X20BB7
s2.codePointAt(1) // 57271

能夠看出 charCodeAt 方法一次只能返回兩個字節的值,而 codePointAt(0) 能夠返回四個字節的值,codePointAt(1)charCodeAt(1) 返回的值相同。orm

var s = "𠮷a";
s.codePointAt(0) // "13401" 對應"𠮷" 
s.codePointAt(2) // "61" 對應"a"

能夠看到在傳入 2 時才能獲得第二個字符 a , 能夠經過 for...of 循環來解決這個問題,由於它會正確識別32位的 UTF-16 字符。對象

var s = "𠮷a";
for (let ch of s) {
    console.log(ch.codePointAt(0).toString(16));
}
// 20bb7
// 61

codePointAt() 方法還能夠用來測試一個字符是2個字節仍是4個字節。

function is32Bit(c) {
    return c.codePointAt(0) > 0xFFFF;
}

is32Bit("𠮷"); // true
is32Bit("a"); // false

3. String.fromCodePoint()

ES5 提供了 String.fromCharCode 方法,用於從碼點返回對應字符,可是這個方法不能識別32位的 UTF-16 字符(Unicode 編號大於 0xFFFF)。

String.fromCharCode(0x20061); // 'a'

String.fromCharCode 方法不能識別大於 0xFFFF 的碼點,因此 0x20BB7 就發生了溢出,最高位2被捨棄,最後返回碼點 U+0061 對應的字符 a

ES6 提供了 String.fromCodePoint 方法,能夠識別大於 0xFFFF 的字符,做用上與 codePointAt 方法正好相反。

String.fromCodePoint(0x78, 0x1f680, 0x79) ==== 'x\uD83D\UDE80y';

String.fromCharCode 方法若是有多個參數,那麼它們就會拼接成一個字符串。

注意: fromCodePoint 方法定義在 String 對象上,而 codePointAt 方法定義在字符串的實例對象上。

4. 字符串的遍歷器接口

ES6 爲字符串添加了遍歷器接口,使得字符串能夠由 for...of 循環遍歷。

for (let codePoint of 'foo') {
    console.log(codePoint);
}
// 'f'
// 'o'
// 'o'

前面已經提到過,利用 for...of 能夠識別大於 0xFFFF 的碼點,傳統的 for 循環沒法識別大於 0xFFFF 的碼點。

var text = String.fromCodePoint(0x20BB7);

for (let i = 0, length = text.length; i < length; i++) {
    console.log(text[i]);
}
// ''
// '' 輸出兩個不可打印的字符

for (let i of text) {
    console.log(i);
}
// '𠮷'

在上面的代碼中 0x20BB7 只有一個字符,可是 for 循環認爲它包含 2 個字符,而 for...of 循環會正確識別出這個字符。

5. at()(提案)

ES5 中爲字符串對象提供了 charAt 方法,返回字符串給定位置的字符,該方法不能識別碼點大於 0xFFFF 的字符。

'abc'.charAt(0) // 'a'
'𠮷'.charAt(0) // '\uD842',一個不可打印的字符

charAt 方法只能返回 UTFF-16 編碼中的第一個字節,目前有一個提案提出字符串實例的 at 方法,能夠識別 Unicode 編號大於 0xFFFF 的字符,返回正確的字符。

'𠮷'.charAt(0) // '𠮷',能夠正確返回

6. normalize()

許多歐洲語言有重音符號和語調符號,爲了表示它們, Unicode 提供了兩種方法:

  1. 直接提供帶重音符號的字符
  2. 利用合成符號(原字符+重音符號)

可是合成符號在 JS 引擎看來其實是兩個字符,合成字符並不等於帶重音符號的字符。

normalize 方法解決了這個問題,將字符的不一樣表示方法統一爲一樣的形式,這稱爲 Unicode 正規化。

不過 nomalize 方法目前不能識別三個或三個以上字符的合成,這種狀況下仍是隻能利用正則表達式,經過 Unicode 編號區間判斷。

7. includes(),startWith(),endsWith()

ES5:

  • indexOf 肯定一個字符串是否包含在另外一個字符串中。

ES6:

  • includes 返回布爾值,表示是否找到了傳入的參數字符串
  • startWith 返回布爾值,表示參數字符串是否在源字符串的頭部
  • endWith 返回布爾值,表示參數字符串是否在源字符串的尾部
var s = "hello JS";

s.includes("ell"); // true
s.startWith("h"); // true
s.endWith("S"); // true

這三個方法都支持第二個參數,表示開始搜索的位置。

var s = "hello JS";

s.includes("JS", 6); // true, 從第個7個(從0開始)字符位置開始
s.startWith("JS", 6); // true, 從第7個(從0開始)字符位置開始
s.endWith("hello", 5); // false, 前5個字符(下標爲0,1,2,3,4)

8. repeat()

功能:返回一個新字符串,將原字符串重複傳入的參數次。

'x'.repeat(2); // "xx"

若是傳入的參數是小數,會被取整。

  1. 大於0時想下取整
'x'.repeat(2.9) // 'xx'
  1. 大於-1小於0時等於0
'x'.repeat(-0.9) // ''
  1. 小於-1時報錯
'x'.repeat(-2) // RangeError

若是傳入的參數不是數字,會先將其準換爲數字。

'x'.repeat('x'); // ''
'x'.repeat('2'); // 'xx'

9. padStart(),padEnd()(ES2017)

ES2017 引入了字符串補全長度的功能,若是某個字符串長度不夠指定長度,會在頭部或尾部補全。

  • padStart() 用於頭部補全
  • padEnd() 用於尾部補全

上面兩個方法都接收兩個參數,第一個參數用來指定字符串的最小長度,第二個參數是用來補全的字符串。

'x'.padStart(5, 'ab'); // 'ababx'
'x'.padEnd(5, 'ab'); // 'xabab'

若是原字符串的長度等於或大於指定的最小長度,則返回原字符串。

'xxx'.padStart(2, 'ab'); // 'xxx'
'xxx'.padEnd(2, 'ab'); // 'xxx'

若是補全的字符串與原字符串的長度之和大於指定的最小長度,則會截去超出位數的補全字符串。

'xxx'.padStart(5, '01234'); // '01xxx'

若是省略第二個參數,則會用空格來補全。

'x'.padStart(4); // '    x'

padStart() 的兩種經常使用用途:

  1. 爲數值補全位數
'1'.padStart(10,'0'); // '0000000001'
  1. 提示字符串格式
'12'.padStart(10, 'YYYY-MM-DD'); // 'YY-MM-12'

10. 模板字符串

模板字符串是加強版的字符串,用反引號 `` ` 來標識,主要有如下三種用法。

  1. 看成普通字符串
`hello ES6`
  1. 定義多行字符串

    全部的空格和縮進都會保留在輸出中。

`

hello 
ES6

` 
// 全部的空格和換行都會被保留,輸出結果: 
"

hello
ES6

"

trim() 方法能夠消除模板字符串反引號 `` ` 和模板字符串內容之間的空格和換行。

​ 注意:模板字符串中的字符串之間的空格和換行是不受影響的

`
hello 
ES6
`.trim(); 
//輸出結果: 
"
hello
ES6
"

​ 用單引號或雙引號定義的字符串是不能有換行符的,不然會報錯。

"
hello
ES6
"
// 輸出結果:
//Uncaught SyntaxError: Invalid or unexpected token
  1. 在字符串中嵌入變量

    在模板字符串中嵌入變量須要將變量名放在 ${} 中, {} 中實際上能夠是任意的 JavaScript 表達式,能夠進行運算,引用對象屬性等,若是大括號中的值不是字符串,則會按照必定的規則將其轉換爲字符串。

var name = 'zhangsan', age = 18;
`hello, my name is ${name}, I am ${age} years old`
// 輸出結果:
hello, my name is zhangsan, I aam 18 years old

11. 標籤模板

模板字符串能夠緊跟在一個函數名後面,這個函數將會被調用來處理這個模板字符串,這被稱爲「標籤模板」功能。

alert `123`
// 等同於
alert (123)

標籤模板實際上不是模板,而是函數調用的一種特殊形式,「標籤」指的就是函數,緊跟在它後面的模板字符串就是它的參數。

若是模板字符串中有變量,就再也不是簡單的調用了,而是將模板字符串先處理成多個參數,再調用函數。

var a = 5;
var b = 10;

tag `hello ${ a + b } world ${ a * b }`;
// 等同於
tag(["hello ", " world ", ""], 15, 50);

tag 函數的第一個參數是一個數組,該數組的成員是模板字符串中那些沒有變量替換的部分。

tag 函數全部參數的實際值以下:

  • 第一個參數: ["hello ", " world ", ""]
  • 第二個參數: 15
  • 第三個參數: 50

標籤模板的兩個重要應用

  1. 過濾HTML字符串,防止用戶輸入惡意內容

    function saferHTML(templateData) {
        var s = templateData[0]; // templateData=["<p>"," has sent you a message.</p>"]
        for (var i = 1; i < arguments.length; i++) {
            var arg = String(arguments[i]); // argument[1]="<script>alert('abc')</script>"
    
            s += arg.replace(/&/g, "&amp;")
                .replace(/</g, "&lt;")
                .replace(/>/g, "&gt");
    
            s += templateData[i];
        }
        return s;
    }
    
    var sender = "<script>alert('abc')</script>"; // 惡意代碼
    var message = saferHTML`<p>${sender} has sent you a message.</p>`;
    
    輸出結果:
    // <p>&lt;script&gtalert('abc')&lt;/script&gt has sent you a message.</p>

    咱們通常要保證用戶輸入的內容中不能含有可執行的 JS 代碼,這是爲了防止那些黑客將這些代碼植入到咱們的程序中對咱們的程序進行攻擊。

    所以,咱們要將 script 標籤等一切可引入 JS 代碼的方式都過濾掉,上面的程序中只考慮 script 標籤可引入 JS 代碼這一種方式。

  2. 多語言轉換

    這裏的語言不只指中文,英文這種語言之間的轉換,還指在 JS 中還能夠運行其它的計算機語言。

    固然模板字符串自己並不具備這樣的功能,這種功能的完成是依靠一些標籤模板來完成的。

i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`
// 輸出結果:
// "歡迎訪問xxx, 您是第xxx位訪問者!"

i18n 函數能夠將英文轉換爲中文。

jsx`
    <div>
        <input 
            ref = 'input'
            onChange = '${this.handleChange}' 
            defaultValue = '${this.state.value}'
        />
        ${this.state.value}
    </div>
`

上面的代碼是經過 jsx 函數將一個 DOM 字符串轉換爲 React 對象。

模板處理函數的第一個參數(模板字符串數組)還有一個raw屬性,raw屬性中保存的是轉義後的原字符串。

tag`First line\nSecond line`

function tag (string) {
    console.log(string.raw[0]);
}
// 輸出結果:
// "First line \\nSecond line" 保存的是轉義後的字符串
// 而string = "First line\nSecond line"

stringstring.raw 惟一的區別就在於 string.raw 裏面保存的是轉義後的字符串。這是爲了方便取得轉義以前的原始模板而設計的。

12. String.raw()

ES6String 對象提供了一個 raw 方法。

String.raw() 方法每每用來充當模板字符串的處理函數,返回一個連反斜線都會轉義的字符串。

String.raw`hello\n{2+3}`;
// "hello\\n5"

String.raw() 方法也能夠看成正常的函數使用,可是第一個參數必須是一個就有raw 屬性的對象,而且 raw 屬性的值必須是一個數組。

13. 模板字符串的限制

一句話總結就是模板字符串會將字符串進行轉義,好比

function latex {
    ...
}
    
let document = latex`
\newcommand{\unicode}{\textbf{Unicode!}}` // 報錯

這是由於 \uLaTex 中具備特殊的含義,可是 JS 將它們進行了轉義。

爲了解決這個問題,有個提案提出放鬆對標籤模板裏字符串轉義的限制,如遇到不合法的字符串轉義,就返回 undefined ,而不是報錯。

相關文章
相關標籤/搜索