前言
字符是我們編寫程序的基礎(chǔ)的基礎(chǔ)。
我們前端是最常見的字符,符號(hào),數(shù)字,英文,中文, 我們通常都是使用直接量來表示,偶爾會(huì)在正則表達(dá)等場(chǎng)景用到UTF-16碼點(diǎn)的格式,問題來了,那你知道JS有幾種字符表示方式嗎?
答案:至少6種,以字符a
為例子:
`a`
'a'
'\a'
'\141'
'\x61'
'\u0061'
'\u{0061}'
前三種都很理解, 后面這又是\
, \x
, \u
, \u{}
, 這都是什么玩意?
特別申明: 測(cè)試代碼最新版chrome上執(zhí)行。
先看總結(jié)
格式 | 示例 | 碼點(diǎn)范圍 | 注意 |
---|
\8進(jìn)制 | '\141' | 0-255 | 模板字符串中不可直接使用 |
\x兩位16進(jìn)制 | '\x61' | 0-255 | 必須兩位 |
\u四位16進(jìn)制 | '\u0061' | 0-65535 | 必須四位 |
\u{16進(jìn)制} | '\u{0061}' | 0-0x10FFFF | 碼點(diǎn)大于0xFFFF,length為2,下標(biāo)訪問值是高低位的值 |
編碼基礎(chǔ)知識(shí)
完全理解字符表示,還是需要一些簡(jiǎn)單的編碼知識(shí),我們一起來看看吧。
ASCII 碼
ASCII 碼一共定義了 128 個(gè)字符,例字母 a 是 97 (0110 0001)
。這 128 個(gè)字符只使用了 8 位二進(jìn)制數(shù)中的后面 7 位,最前面的一位統(tǒng)一規(guī)定為 0。
ASCII 碼止共定義了128個(gè)字符,其中33個(gè)字符無法顯示。0010 0000
~ 0111 1110
(32-126)是可以顯示的 ,基本都能使用鍵盤打出來, 具體參見對(duì)照表: ASCII編碼對(duì)照表。
ASCII 額外擴(kuò)展的版本 EASCII,這里就可以使用一個(gè)完整子節(jié)的 8 個(gè) bit 位表示共 256 個(gè)字符,其中就又包括了一些衍生的拉丁字母。 可以參見 extended-ascii-table?。
Unicode 和 碼點(diǎn)
Unicode是字符集, 為了兼容ASCII,Unicode規(guī)定前0-127個(gè)字符是和ASCII是一樣的,不一樣的是128-255這一部分。
我們一起看看ASCII 128-255部分:
再看看 Unicode的 128-255部分:

其給某個(gè)字符規(guī)定對(duì)應(yīng)的數(shù)值,我們經(jīng)常稱其為碼點(diǎn)。我們可以通過字符串的實(shí)例方法charCodeAt
和codePointAt
獲取,前者只能準(zhǔn)確獲取碼點(diǎn)值小于0xFFFF(65535)
的碼點(diǎn)。
'??'.codePointAt(0)
'??'.charCodeAt(0)
'a'.charCodeAt(0)
對(duì)應(yīng)的我們可以使用String的靜態(tài)方法fromCharCode
,fromCodePoint
用碼點(diǎn)獲取對(duì)應(yīng)的字符。
String.fromCodePoint(131104)
String.fromCharCode(131104)
String.fromCharCode(97)
UTF-8, UTF-16
我們平時(shí)接觸比多的就是UTF-8和UTF-16,均是Unicode字符編寫的一種實(shí)現(xiàn)。 我們JS編碼的字符串是UTF-16格式來存儲(chǔ)的和表示的。
UTF-16對(duì)于碼點(diǎn)小于0xFFFF的用2個(gè)字節(jié)(1個(gè)編碼單元)表示,大于0xFFFF
的編碼用四個(gè)字節(jié)(2個(gè)編碼單元)表示。 具體的體現(xiàn)可以表現(xiàn)在字符的長(zhǎng)度上。
"??".length
"a".length
"人".length
這里強(qiáng)調(diào) 0xFFFF
是分界線,很重要。
進(jìn)制轉(zhuǎn)換
我們可以使用數(shù)字實(shí)例toString()
10進(jìn)制轉(zhuǎn)為相對(duì)應(yīng)的進(jìn)制。
97..toString(16)
97..toString(2)
下面進(jìn)入正題, 一起來看字符的表示:
\
+ 字符
\
是一個(gè)特殊的存在,轉(zhuǎn)義字符,大多數(shù)情況下,不產(chǎn)生什么作用。 只對(duì)一些特殊的字符起作用。
從下可以看出\a
這個(gè)\
沒有任何作用,對(duì)\r
就不一樣了。

更多轉(zhuǎn)義字符知識(shí),參見 轉(zhuǎn)義字符-維基。
\
+ 八進(jìn)制
其能表示的碼點(diǎn)范圍值為 0-255。
這里提個(gè)問題, 這里顯示的ASCII的字符,還是Unicode的字符。
- 比如字符
a
為 97, 可以使用charCodeAt獲取,
'a'.charCodeAt(0)
- 轉(zhuǎn)為8進(jìn)制為
97..toString(8) = 141
\141
console.log('\141')
我們看一些特殊碼點(diǎn)的字符,因?yàn)榇a點(diǎn)為31和127的字符,不能被顯示或者表示。
'\37'
'\177'
至于為什么 \177
變?yōu)榱?nbsp;\x7F
, 是不是有點(diǎn)疑惑,其實(shí)也很簡(jiǎn)單。 當(dāng)程序檢查其值,不在可顯示范圍的時(shí)候,直接反向計(jì)算其原值,并轉(zhuǎn)為16進(jìn)制值,并使用\x兩位十六進(jìn)制
格式表示。
'\177'
127.toString(16)
關(guān)于表示碼點(diǎn)上限(255):
'\377'
'\400'
回答開始的提問:
我們這里顯示的肯定是Unicode字符啊,前面提過了, JS字符編碼采用的是UTF-16啊。 可以用碼點(diǎn)在128-255的一個(gè)字符試試, 那就碼點(diǎn)254的字符吧:
擴(kuò)展 ASCII 254: 
Unicode 254: 't'
'\376'
所以大家要明白,我們這各種字符表示方法,表示的都是Unicode字符。
關(guān)于\x兩位十六進(jìn)制
格式,我們開講。
\x
+ 兩位十六進(jìn)制
我們可以用0x
表示16進(jìn)制的數(shù)字,所以\x
大家也很好理解,是16進(jìn)制。
0x61
'a'.charCodeAt(0)
97..toString(16)
'\x61'
兩位十六進(jìn)制碼點(diǎn), 0x00
~ 0xFF
(0~255) , 和 \八進(jìn)制碼
一樣,不可顯示的碼點(diǎn)字符,直接顯示其編碼
'\x1F'
'\x20'
'\x7e'
'\x7f'
'\x80'
到這里你可能問,你這里,那么這不能顯示,那不能顯示,有沒有一個(gè)表啊。有的, 參見 Unicode碼表, 這里記錄了 0x0000 到 0xFFFF的碼點(diǎn)的字符,一般情況準(zhǔn)夠用。
0-255碼點(diǎn)范圍內(nèi), 0x00 到 0x1F(0-31)
, 0x80 到 0x9F(128-159)
是無法顯示的或者看不見的。 
'\x9F'
'\xA1'
這個(gè)結(jié)果,在不通瀏覽器,可能輸出還不一樣。 盡量采用最新版本的chrome去驗(yàn)證。
雖然輸出的顯示有些區(qū)別,但是都表示這玩意不能表示一個(gè)字符,請(qǐng)自重。
360瀏覽器:

firefox:

chrome:

\u
+ 四位十六進(jìn)制
這里固定是4位,少一位都不行。
"\u0061"
"\u061"

還是4位,如果多了, 截取前4位,后面的直接追加。看個(gè)例子,非常好理解。
'\u0061'
'\u00610'
這里也就反應(yīng)了問題,碼點(diǎn)大于0xFFFF,大于4位16進(jìn)制的字符怎么表示???
ES6考慮了這個(gè)問題,于是推出了 \u{
+ 十六進(jìn)制
+ }
, 看下個(gè)章節(jié)。
我們之前說過,UTF-16是Unicode的一種實(shí)現(xiàn),Unicode的代理區(qū) 0xD800
-0xDFFF
, 其不代表任何字符。同理,我們采用\u
+ 四位十六進(jìn)制
方式,如果碼點(diǎn)在這個(gè)區(qū)間,返回 ?
或者原字符(瀏覽器不同,可能返回不同), 當(dāng)然其他的碼點(diǎn)也可能還沒設(shè)定值或者是不可打印的。
'\uD800'
'\uDFFF'
實(shí)際上 UTF-16也就是利用了代理區(qū),把碼點(diǎn)大于0xFFFF
字符分為高低兩部分, 索引值0獲得的值實(shí)際上是高位部分,索引值1獲得的是低位部分。
var text = "??";
text[0]
text[1]
更多utf-16編碼的知識(shí),我們后續(xù)跟上。
\u{
+ 十六進(jìn)制
+ }
ES6新增的能力。 這個(gè)多了一個(gè){}
包裹。
這個(gè)應(yīng)該是可以一統(tǒng)江湖,可以表示碼點(diǎn)低于0xFFFF的字符,也可表示碼點(diǎn)大于0xFFFF的字符。
"\u{20020}"
"\u{0061}"
"\u{061}"
"\u{61}"
"\u{9}"
而且其還沒有強(qiáng)制四位的限制,簡(jiǎn)直爽的沒法沒邊。
缺點(diǎn)嘛,那就是兼容性了,就讓時(shí)間去磨平吧。
ES6的模板字符串
ES6的模板字符串,可以說是爽歪歪,我們就也算其一種新的字符表示方式吧。
我們也是可以使用\u
, \u{}
, \x
格式的
`我\u{61}`
`我\x61`
`我\u0061`
`我\a`
到這里,你們沒覺得少了了點(diǎn)什么嗎? 沒錯(cuò) \8進(jìn)制
,是不被允許的
`我\141`

如果,你非得用,那就 ${''}
包裹一下:
`我${'\141'}`
實(shí)際的應(yīng)用
匹配中文的正則
我們總不能去羅列,多義采取的是[\u4e00-\u9fa5]
這種格式去匹配:
var regZH = /[\u4e00-\u9fa5]/g;
regZH.test("a");
regZH.test("人");
regZH.test("??");
這里注意了,只能識(shí)別常見中文。
, 畢竟碼點(diǎn)的范圍就那么大點(diǎn)。
去掉空白字符
看看MDN上 String.prototype.trim
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
};
}
我們一起來看一下著名的core-js對(duì)trim的空白字符的理解 whitespaces.js
module.exports = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002' +
'\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF';
我們還是輸出一下吧:
'\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002' +
'\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF'
???'
CSS content屬性 和 CSS 字體圖標(biāo)
現(xiàn)代瀏覽器,已經(jīng)支持中文,但是建議還是使用\16進(jìn)制編碼
格式 。
這里使用的是 \16進(jìn)制
, 不需要u
也不需要{}
, 支持碼點(diǎn)大于0xFFFF的字符
div.me::before {
content: "\6211"; // 我
padding-right: 10px;
}
div.me::before {
content: "\20020:"; // ??
padding-right: 10px;
}
CSS顏色色值
字體顏色,背景顏色,邊框顏色等等,其有一種表示方式就是6位16進(jìn)制,當(dāng)然也有簡(jiǎn)寫形式。
.title {
color: #FFF
}
字符個(gè)數(shù)統(tǒng)計(jì)
這個(gè)是利用了UTF-16編碼特性。 因?yàn)?code style="font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 0.87em; word-break: break-word; border-radius: 2px; overflow-x: auto; background-color: rgb(255, 245, 245); color: rgb(255, 80, 44); padding: 0.065em 0.4em;">\uD800-\DFFF是代理區(qū),具體UTF-16編碼的東西,單獨(dú)來講解。 也可以看見文章 為什么一定是?, 里面有完整的解釋。
const spRegexp = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
function chCounts(str){
return str.replace(spRegexp, '_').length;
}
chCounts("??")
"??".length
ES6中的方式就多一些了:
Array.from("??").length
[..."??"].length
文件類型識(shí)別
具體可以參見阿寶哥的 JavaScript 如何檢測(cè)文件的類型?
const isPNG = check([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
const realFileElement = document.querySelector("#realFileType");
async function handleChange(event) {
const file = event.target.files[0];
const buffers = await readBuffer(file, 0, 8);
const uint8Array = new Uint8Array(buffers);
realFileElement.innerText = `${file.name}文件的類型是:${
isPNG(uint8Array) ? "image/png" : file.type
}`;
}
其他
- emoji圖標(biāo)
- 編碼轉(zhuǎn)換,比如utf-8轉(zhuǎn)base64
- 等等
轉(zhuǎn)自https://juejin.cn/post/7033184851494699022
該文章在 2025/2/19 10:33:51 編輯過