字符串和正則表達式
字符串和正則表達式
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) ,utf16引入了代理對,其規定用兩個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插入經過安全置換後的字符串的能力
模板字面量里不需要轉義單、雙引號,如果要使用反撇號則需要通過\來轉義。有變量可以使用${變量名}佔位(如果使用一個未定義的變量,總會拋出錯誤),模板字面量本身也是javascript表達式,所以你可以在一個模板字面量里嵌入另一個,如下
let name = "Nicholas",
message = `Hello ${
`my name is ${name}`
}`;
console.log(message);
標籤函數的使用
function tag(literals,...substitutions){
// 返回一個字符串
}
// 舉個栗子
let count = 10,
price = 0.25
message = passthru`${count}items cost $${count*price.toFixed(2)}.`
如果你有一個名為 passthru() 函數,那麼作為一個模板字面量標籤,它會接受3個參數:
首先是一個literals數組:相當於兩個變量位符把字符串切成了三段
- 第一佔位符前面:空字符''
- 第一個和第二個佔位符中間的
items cost $ - 第二個後面'.'
第二個參數就是count解釋的值,傳參為10,它也成為了substitutions數組的第一個元素,最後一個參數是count*price.toFixed(2)解釋的值2.5作為substitutions數組的第二個元素。substitutions的元素個數總是比literals的長度少1。
function passthru(literals,...substitutions){
let result = '';
// 根據substition的數量來確定循環的次數
for(let i=0;i<substitutions.length;i++){
result+=literals[i];
result+=substitutions[i];
}
// 合併最後一個literal
result+=literals[literals.length-1];
return result;
}
String.raw()
模板標籤同樣可以訪問原生字符串信息,也就是說通過模板標籤可以訪問到字符轉義被轉換成等價字符前的原生字符串,最簡單的例子是使用內建的String.raw()標籤函數
let message1 = `Multiline\nstring`,
message2 = String.raw`Multiline\nstring`;
console.log(message1); // "Multiline
// string"
console.log(message2); // "Multiline\\nstring"
原生字符串信息同樣被傳入模板標籤,標籤函數的第一個參數是一個數組,它有一個額外的屬性raw,是一個包含每一個字面值的原生等價信息的數組。如
literals[0]總有一個等價的literals.raw[0],它包含著它的原生字符串信息。
主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/4309