字串與正規表達式
字串與正規表達式
Javascript 字串一直基於 16 位元字元編碼(UTF-16)進行建構。每 16 位元的序列是一個編碼單元(code unit),代表一個字元。length、charAt() 等字串屬性與方法都基於這個編碼單元構造的。
Unicode 的目標是為世界上每一個字元提供全球唯一的識別符。如果我們把字元長度限制在 16 位元,碼位數量將不足以表示如此多的字元。「全球唯一的識別符」,又稱作碼位(code point),是從 0 開始的數值。而表示字元的這些數值或碼位,我們稱之為字元編碼 (character encode)。字元編碼必須將碼位編碼為內部一致的編碼單元。對於 UTF-16 來說,碼位可以由多種編碼單元表示。
更好的 Unicode 支援,在過去 16 位元足以包含任何字元 (每 16 位元的序列是一個編碼單元,代表一個字元,在過去 16 位元足以包含任何字元),直到
Unicode引入擴展字元集,編碼規則才不得不進行變更。
UTF-16
前 $2^{16}$ 個碼位均以 16 位元的編碼單元表示,這個範圍被稱作基本多文種平面 BMP (Basic Multilingual Plan)。超出這個範圍的碼位則要歸屬於某個輔助平面(supplementary plane),UTF-16 引入了代理對,其規定用兩個 16 位元編碼單元表示一個碼位。這也就是說,字串裡的字元有兩種,一種是由一個編碼單元16表示的 BMP 字元,另一種是由兩個編碼單元32位元表示的輔助平面字元,如字元:‘𠮷’ (String.fromCodePoint(134071))
在ECMAScript 5中,所有字串的操作都基於 16 位元編碼單元。如果採用同樣的方式處理包含代理對的UTF-16編碼字元,得到的結果可能與預期不符
let text = "𠮷";
console.log(text.length); //2
console.log(/^.$/.test(text)); //false
console.log(text.charAt(0)); // ""
console.log(text.charAt(1)); // ""
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
- 變數 text 的長度事實上是 1,但它的 length 屬性卻是 2
- 變數 text 被判定為兩個字元,因此匹配單一字元的正規表達式失效
- 前後兩個 16 位元的編碼單元都不表示任何可列印的字元,因此
charAt()方法不會返回合法字串 charCodeAt()同樣不能正確地辨識字元。它會返回每個 16 位元編碼單元對應的數值
codePointAt()方法
對於 BMP 字元集中的字元,codePointAt()方法的返回值與chartCodeAt()方法相同,而對於非 BMP 字元集來說返回值則不同。字串‘𠮷a’第一個字元是非 BMP 的,包含兩個編碼單元,所以它的 length 屬性值為 3。ES6 完全支援UTF-16的codePointAt()方法,這個方法接受編碼單元的位置而非字元位置作為參數,返回與字串中給定位置對應的碼位,即一個整數值。
let text = '𠮷a'
console.log(text.length)
console.log(text.charCodeAt(0)) // 55362
console.log(text.charCodeAt(1)) // 57271
console.log(text.charCodeAt(2)) // 97
console.log(text.codePointAt(0)) // 134071
console.log(text.codePointAt(1)) // 57271
console.log(text.codePointAt(2)) // 97
在偵測一個字元佔用的編碼單元,可以寫如下的函式來偵測
function is32Bite(c){
return c.codePointAt(0)>0xFFFF;
}
console.log(is32Bite('𠮷')) // true
console.log(is32Bite('a')) // false
fromCodePoint()方法
透過一個字元的碼位返回一個字元,可以看成是String.fromCharCode()的擴展版。對於 BMP 的所有字元,兩個方法的執行結果相同。只有傳遞非 BMP 的碼位作為參數時,二者的執行結果才有可能不同。
console.log(String.fromCodePoint(134071)) // 𠮷
normalize()方法
Unicode 的另一個有趣之處是,如果我們要對不同字元進行排序或比較操作,會存在一種可能,它們是等效的。代表相同文字的字元可能存在的碼位不同。所以做比較時要使用normalize()方法來先標準化一下
只要牢記,在對比字串之前,一定先把它們標籤化為同一個形式。
let normalized = values.map(funciton(text){
return text.normalize();
});
normalized.sort(funciton(first,second){
if(first < second){
return -1;
} else if (first === second) {
return 0;
} else {
return 1;
}
})
正規表達式 u 修飾符
一個支援Unicode的修飾符u 使它從編碼單元操作模式切換成為字元模式,如此一來正規表達式就不會將代理對視為兩個字元,從而完全按照預期正常執行。如:
let text = '𠮷a'
console.log(text.length)
console.log(/^.$/.test(text)) //false
console.log(/^.$/u.test(text)) //true 使用了u修飾符後,正規表達式會匹配字元,從而就可以匹配日文文字字元
計算碼位數量
ES6 仍然不支援字串碼位數量偵測(length 仍然返回字串編碼單元的數量),但有了u修飾符後,你就可以透過正規表達式來解決這個問題。
// 長字串可能會有效率問題,可以使用字串迭代器來處理
function codePointLength(text){
let rs = text.match(/[\s\S]/gu);
return rs?rs.length:0
}
// 判斷瀏覽器是否支援 u
function hasRegExU(){
try{
var partten = new RegExp(".","u");
return true
}catch(ex){
return false
}
}
字串的子字串辨識
trim()includes()如果字串的起始部分偵測到指定文字則返回 true,否則返回 falsestartWith()如果字元的起始部分偵測到指定文字則返回 true,否則返回 falseendsWith()如果字串的結束部分偵測到指定文字則返回 true,否則返回 falserepeat()返回當前字串重複一定次數的新字串
兩個參數,第一個指定要搜尋的文字,第二個參數是可選的,指定搜尋位置的索引位置,如果你需要在一個字串中尋找一個子字串的實際位置,還是需要使用
indexOf()或lastIndexOf()方法
repeat()方法
ES6 還增加了一個repeat(),它接受一個 number 型別的參數,表示該字串的重複次數,返回值是當前字串重複一定次數後的新字串。例如在程式碼格式化工具中建立縮排層級
let indent = " ".repeat(4),
indentLevel = 0;
// 當需要增加縮排時
let newIndent = indent.repeat(++indentLevel)
正規表達式中的 y 修飾符
它會影響正規表達式搜尋過程中的 sticky 屬性,當在字串中開始字元匹配時,它會通知搜尋從正規表達式的 lastIndex 屬性開始進行。如果在指定位置沒有成功匹配,則停止繼續匹配。只有呼叫 exec() 和 test() 這些正規表達式物件的方法時才會涉及 lastIndex 屬性
正規表達式的複製
var reg1 = /ab/i,
// ES5 中拋出例外,ES6 中正常執行
reg2 = new RegExp(reg1,"g")
let re = /ab/g
console.log(re.source); // "ab"
console.log(re.flags); // "g"
樣板字面值
- 多行字串:一個正式的多行字串概念
- 基本的字串格式化:將變數的值嵌入字串的能力
- HTML 跳脫:向 HTML 插入經過安全置換後的字串的能力
樣板字面值裡不需要跳脫單引號、雙引號,如果要使用反引號則需要透過 \ 來
主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/4309