持續更新的github筆記,連接地址:Front-End-Basicsjavascript
此篇文章的筆記地址:字符到底發生了什麼變化html
ES6走走看看系列,特別鳴謝奇舞讀書會~java
看正文以前,先思考一下,爲何你看的ES6各類權威指南里提到的
𠮷
會有那麼多問題,它length是2,charAt出來是亂碼……git
計算機內部處理的信息,都是一個些二進制值,每個二進制位(bit)有0和1兩種狀態。 一個字節(byte)有八個二進制位,也就是說,一個字節一共能夠用來表示256種不一樣的狀態,每個狀態對應一個符號,就是256個符號,從00000000
到11111111
。轉換成十六進制,一個字節就是0x00
到OxFF
。github
先祭出一張圖,建議放大看正則表達式
(1) ASCII 碼 數組
上個世紀60年代,美國製定了一套字符編碼,對英語字符與二進制位之間的關係,作了統一規定。這被稱爲 ASCII 碼(美國信息交換標準代碼),一直沿用至今。安全
ASCII 碼一共規定了128個字符的編碼,只佔用了一個字節的後面7位,最前面的一位統一規定爲0。bash
第一部分:0~31(0x00~0x1F)及127(共33個)是控制字符或通訊專用字符,有些能夠顯示在屏幕上,有些則不能顯示,但能看到其效果(如換行、退格)以下表:函數
第二部分:是由20~7E共95個,這95個字符是用來表示阿拉伯數字、英文字母大小寫和下劃線、括號等符號,均可以顯示在屏幕上以下表:
(2) 非ASCII 編碼
英語用128個符號編碼就夠了,可是世界上可不僅有英語這一種語言,先不說漢語,就是那些不說英語的歐洲國家,128個符號是不夠的。
一些歐洲國家就決定,利用字節中閒置的最高位編入新的符號,這些歐洲國家使用的編碼體系,能夠表示最多256個符號。你們你加你的,我加個人。所以,哪怕它們都使用256個符號的編碼方式,表明的字母卻不同。
1981年IBM PC ROM256個字符的字符集,即IBM擴展字符集,這128個擴充字符是由IBM制定的,並不是標準的ASCII碼.這些字符是用來表示框線、音標和其它歐洲非英語系的字母。以下圖:
在Windows 1.0(1985年11月發行)中,Microsoft沒有徹底放棄IBM擴展字符集,但它已退居第二重要位置。由於遵循了ANSI草案和ISO標準,純Windows字符集被稱做「ANSI字符集」。
因而可知擴展ASCII再也不是國際標準。
而對於亞洲國家的文字,使用的符號就更多了,漢字就多達10萬左右(《中華辭海》共收漢字87019個,日本《今昔文字鏡》收錄漢字超15萬)。一個字節只能表示256種符號,確定是不夠的,就必須使用多個字節表達一個符號。好比,簡體中文常見的編碼方式是 GB2312(中華人民共和國國家標準簡體中文字符集),使用兩個字節表示一個漢字,因此理論上最多能夠表示 256 x 256 = 65536 個符號。其實GB 2312標準共收錄6763個漢字,它所收錄的漢字已經覆蓋中國大陸99.75%的使用頻率。
(3) Unicode
以前的編碼,你們在本身的國家使用都挺好的。世界上存在着多種編碼方式,同一個二進制數字能夠被解釋成不一樣的符號,因此一旦不一樣國家進行數據傳輸,結果就只有亂碼了。
若是有一種編碼,將世界上全部的符號都歸入其中。每個符號都給予一個獨一無二的編碼,那麼亂碼問題就會消失。這就是 Unicode,就像它的名字所表示的,這是一種全部符號的編碼。
Unicode,定義很簡單,用一個碼點(code point)映射一個字符。碼點值的範圍是從U+0000到U+10FFFF,能夠表示超過110萬個符號。
Unicode 最新版本的是 11.0,總共137,374個字符,這麼看來,仍是挺夠用的。
Unicode最前面的65536個字符位,稱爲基本平面(BMP-—Basic Multilingual Plane),它的碼點範圍是從U+0000到U+FFFF。最多見的字符都放在這個平面,這是Unicode最早定義和公佈的一個平面。 剩下的字符都放在補充平面(Supplementary Plane),碼點範圍從U+010000一直到U+10FFFF,共16個。
須要注意的是,Unicode 只是一個符號集,它只規定了符號的二進制代碼,卻沒有規定這個二進制代碼應該如何存儲。
// 例以下面的字符對應的碼點
A的碼點 U+0041
a的碼點 U+0061
©的碼點 U+00A9
☃的碼點 U+2603
💩的碼點 U+1F4A9
複製代碼
正是由於上面說的,沒有規定怎麼存儲,因此出現了Unicode 的多種存儲方式,不一樣的實現致使了Unicode 在很長一段時間內沒法推廣,並且原本英文字母只用一個字節存儲就夠了,若是 Unicode 統一規定,每一個符號用三個或四個字節表示,那麼每一個英文字母前都必然有二到三個字節是0,這對於存儲來講是極大的浪費,文本文件的大小會所以大出二三倍,這是沒法接受的。
在這個時候每每須要一個強大的外力推進,你們訴諸於利益,共同實現一個目標。因此,真正意義上的互聯網普及了,地球變成了村子,交流愈來愈多,亂碼是怎麼能行。
(4) UTF-八、UTF-1六、UTF-32
UTF(Unicode transformation format)Unicode轉換格式,是服務於Unicode的,用於將一個Unicode碼點轉換爲特定的字節序列。 上面三種都是 Unicode 的實現方式之一。 UTF-16(字符用兩個字節或四個字節表示)和 UTF-32(字符用四個字節表示),不過UTF-8 是在互聯網上使用最廣的一種 Unicode 的實現方式。
UTF-8
1992年開始設計,1993年首次被正式介紹,1996年UTF-8標準尚未正式落實前,微軟的CAB(MS Cabinet)規格就明確允許在任何地方使用UTF-8編碼系統。但有關的編碼器實際上歷來沒有實現這方面的規格。2003年11月UTF-8被RFC 3629從新規範,只能使用原來Unicode定義的區域,U+0000到U+10FFFF,也就是說最多四個字節(以前可使用一至六個字節爲每一個字符編碼)
UTF-8 是一種變長的編碼方式。它可使用1~4個字節表示一個符號,根據不一樣的符號而變化字節長度。越是經常使用的字符,字節越短,最前面的128個字符,只使用1個字節表示,與ASCII碼徹底相同(也就是所說的兼容ASCII碼)。在英文下這樣就比UTF-16 和 UTF-32節省空間。
UTF-8 的編碼規則很簡單,只有二條:
1)對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的 Unicode 碼。所以對於英語字母,UTF-8 編碼和 ASCII 碼是相同的。
2)對於n字節的符號(n > 1),第一個字節的前n位都設爲1,第n + 1位設爲0,後面字節的前兩位一概設爲10。剩下的沒有說起的二進制位,所有爲這個符號的 Unicode 碼。
UTF-16
基本平面的字符佔用2個字節,輔助平面的字符佔用4個字節。也就是說,UTF-16的編碼長度要麼是2個字節(U+0000到U+FFFF),要麼是4個字節(U+010000到U+10FFFF)。
這裏涉及到一個怎麼判斷兩個字節是一個字符,仍是兩個字節加兩個字節組成的四個字節是一個字符?
解決方法是:在基本平面內,從U+D800到U+DFFF是一個空段,即這些碼點不對應任何字符。所以,這個空段能夠用來映射輔助平面的字符。
具體來講,輔助平面的字符位共有220個,也就是說,對應這些字符至少須要20個二進制位。UTF-16將這20位拆成兩半,前10位映射在U+D800到U+DBFF(空間大小210),稱爲高位(H),後10位映射在U+DC00到U+DFFF(空間大小210),稱爲低位(L)。這意味着,一個輔助平面的字符,被拆成兩個基本平面的字符表示(代理對的概念)。
因此,當咱們遇到兩個字節,發現它的碼點在U+D800到U+DBFF之間,就能夠判定,緊跟在後面的兩個字節的碼點,應該在U+DC00到U+DFFF之間,這四個字節必須放在一塊兒解讀。
UTF-16編碼介於UTF-32與UTF-8之間,同時結合了定長和變長兩種編碼方法的特色。
UTF-32
UTF-32 最直觀的編碼方法,每一個碼點使用四個字節表示,字節內容一一對應碼點。
UTF-32的優勢在於,轉換規則簡單直觀,查找效率高。缺點在於浪費空間,一樣內容的英語文本,它會比ASCII編碼大三倍。這個缺點很致命,致使實際上沒有人使用這種編碼方法,HTML 5標準就明文規定,網頁不得編碼成UTF-32。
(5) UCS UCS-2
國際標準化組織(ISO)的ISO/IEC JTC1/SC2/WG2工做組是1984年成立的,想要作統一字符集,並與1989年開始着手構建UCS(通用字符集),也叫ISO 10646標準,固然另外一個想作統一字符集的是1988年成立的Unicode團隊,等到他們發現了對方的存在,很快就達成一致:世界上不須要兩套統一字符集(幸好知道的早啊)。
1991年10月,兩個團隊決定合併字符集。也就是說,從今之後只發布一套字符集,就是Unicode標準,而且修訂此前發佈的字符集,UCS的碼點將與Unicode徹底一致。(兩個標準同時是存在)
UCS的開發進度快於Unicode,1990年就公佈了第一套編碼方法UCS-2,使用2個字節表示已經有碼點的字符。(那個時候只有一個平面,就是基本平面,因此2個字節就夠用了。)UTF-16編碼遲至1996年7月才公佈,明確宣佈是UCS-2的超集,即基本平面字符沿用UCS-2編碼,輔助平面字符定義了4個字節的表示方法。
二者的關係簡單說,就是UTF-16取代了UCS-2,或者說UCS-2整合進了UTF-16。因此,如今只有UTF-16,沒有UCS-2。
UCS-2 使用2個字節表示已經有碼點的字符,第一個字節在前,就是"大尾方式"(Big endian),第二個字節在前就是"小尾方式"(Little endian)。
那麼很天然的,就會出現一個問題:計算機怎麼知道某一個文件到底採用哪種方式編碼?
Unicode 規範定義,每個文件的最前面分別加入一個表示編碼順序的字符,這個字符的名字叫作"零寬度非換行空格"(zero width no-break space),用FEFF表示。這正好是兩個字節,並且FF比FE大1。
若是一個文本文件的頭兩個字節是FE FF,就表示該文件採用大尾方式;若是頭兩個字節是FF FE,就表示該文件採用小尾方式。
最上面給出的圖中字符的發展歷史和JavaScript的誕生時間對比下,能夠知道JavaScript若是要想用Unicode字符集,比較恰的選擇是UCS-2編碼方法,UTF-8,UTF-16都來的晚了一些,UCS-4卻是有的,可是英文字符原本一個字節就能夠的,如今也要用4個字節,仍是挺嚴重的事情的。96年那個時候,電腦廣泛配置內存 8MB-16MB,硬盤850MB—1.2GB。
ECMAScript 6 以前,JavaScript字符編碼方式使用UCS-2,是致使以後JavaScript對位於輔助平面的字符(超過兩個字節的字符)操做出現異常狀況的根本緣由。
ECMAScript 6 強制使用UTF-16字符串編碼來解決字符超過兩個字節時出現異常的問題,並按照這種字符編碼來標準化字符串操做。
// 存在的問題
const text = '😂';
console.log(text.length) //打印 2 ,實際上是一個Emoji表情符
console.log(/^.$/.test(text)) // false , 正則匹配也出了問題,說不是一個字符
console.log(/^..$/.test(text)) // true , 是兩個字符
console.log(text.charAt(0)) // � 先後兩個字節碼位都是落在U+D800到U+DFFF這個空段,打印不出東西
console.log(text.charAt(1)) // �
console.log(text.charCodeAt(0)) // 55357 轉成十六進制 0xd83d
console.log(text.charCodeAt(1) //56834 轉成十六進制 0xde02
// 通過查詢Unicode的字符表,😂的碼位是U+1f602
console.log('\u1f602' === '😂') //false
console.log('\ud83d\ude02' === '😂') // true
複製代碼
擴展:� 的Unicode碼點是 U+FFFD,一般用來表示Unicode轉換時沒法識別的字符(也就是亂碼)
(1) 爲解決charCodeAt()
方法獲取字符碼位錯誤的問題,新增codePointAt()
方法
codePointAt()
方法徹底支持UTF-16,參數接收的是編碼單元的位置而非字符位置,返回與字符串中給定位置對應的碼位,即一個整數。
對於BMP字符集中的字符,codePointAt()方法的返回值跟charCodeAt()相同,而對於非BMP字符集來講,返回值不一樣。
const text = '😂';
console.log(text.charCodeAt(0)) // 位置0處的一個編碼單元 55357
console.log(text.charCodeAt(1)) // 位置1處的一個編碼單元 56834
console.log(text.codePointAt(0)) // 位置0處的編碼單元開始的碼位,此例是從這個編碼單位開始的兩個編碼單元組合的字符(四個字節),因此會打印出全部碼位,即四字節的碼位 128514 即0x1f602,大於0xffff,也證實了是佔四個字節的存儲空間。
console.log(text.codePointAt(1)) // 位置1處的編碼單元開始的碼位 56834
複製代碼
(2) 爲解決超過兩個字節的碼點與字符轉換問題,新增了fromCodePoint()
方法
// 打印😂
console.log(String.fromCharCode(128514)) // 打印失敗
console.log(String.fromCharCode(55357,56834)) // 參數能夠接收一組序列數字,表示 Unicode 值。打印成功 😂
console.log(String.fromCodePoint(128514)) // 打印成功 😂
console.log(String.fromCodePoint(0x1f602)) // 能夠接收不一樣進制的參數,打印成功 😂
複製代碼
(3) 爲解決正則表達式沒法正確匹配超過兩個字節的字符問題,ES6定義了一個支持Unicode的 u
修飾符
const text = '😂';
console.log(/^.$/.test(text)) // false , 正則匹配出了問題,說不是一個字符
console.log(/^..$/.test(text)) // true , 是兩個字符
console.log(/^.$/u.test(text)) // true, 加入 u 修飾符,匹配正確
複製代碼
注意:u修飾符是語法層面的變動,在不支持ES6的JavaScript的引擎中使用它會致使語法錯誤,可使用RegExp構造函數和try……catch來檢測,避免發生語法錯誤
(4) 爲解決超過\uffff碼點的字符沒法直接用碼點表示的問題,引入了\u{xxxxx}
console.log('\u1f602' === '😂') //false
console.log('\ud83d\ude02' === '😂') // true
console.log('\u{1f602}' === '😂') // true
複製代碼
(5) 解決字符串中有四個字節的字符的length問題
const text = '笑哭了😂';
// 解決一
// 上線UTF-16若是是在輔助平面(佔4個字節)的話,會有代理對,U+D800-U+DBFF和U+DC00-U+DFFF
var surrogatePair = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; // 匹配UTF-16的代理對
function firstGetRealLength(string) {
return string
// 把代理對改成一個BMP的字符,而後獲取長度
.replace(surrogatePair, '_')
.length;
}
firstGetRealLength(text); // 4
// 解決二(推薦)
// 字符串是可迭代的,能夠用Array.from()來轉化成數組計算length
function secondGetRealLength(string) {
return Array.from(string).length;
}
secondGetRealLength(text); // 4
// 解決三
// 使用正則新增長的修飾符u
function thirdGetRealLength(string) {
let result = text.match(/[\s\S]/gu);
return result?result.length:0;
}
thirdGetRealLength(text); // 4
複製代碼
(5) 解決字符串中有四個字節的字符的字符串反轉問題
const text = '笑哭了😂';
function reverse(string) {
return string.split('').reverse().join('');
}
function reversePlus(string) {
return Array.from(string).reverse().join('');
}
console.log(reverse(text)) // ��了哭笑 由於😂是\ud83d\ude02反轉後是\ude02\ud83d,不是一個合法的代理對(高低字節範圍不一樣)
console.log(reversePlus(text)) // 😂了哭笑
複製代碼
模板字面量的填補的ES5的一些特性
(1)多行字符串中反撇號中的全部空白符都屬於字符串的一部分
let message = `a
b`;
console.log(message.length) //15
複製代碼
(2)標籤模板:模板字符串能夠緊跟在一個函數名後面,該函數將被調用來處理這個模板字符串。這被稱爲「標籤模板」功能(tagged template)。
標籤模板其實不是模板,而是函數調用的一種特殊形式。「標籤」指的就是函數,緊跟在後面的模板字符串就是它的參數。
let a = 5;
let b = 10;
function tag(s, v1, v2) {
console.log(s[0]);
console.log(s[1]);
console.log(s[2]);
console.log(v1);
console.log(v2);
return "OK";
}
// 標籤模板調用
tag`Hello ${ a + b } world ${ a * b }`;
// 等同於
tag(['Hello ', ' world ', ''], 15, 50);
//打印
// "Hello "
// " world "
// ""
// 15
// 50
// "OK"
複製代碼
「標籤模板」的一個重要應用,就是過濾 HTML 字符串,防止用戶輸入惡意內容。標籤模板的另外一個應用,就是多語言轉換(國際化處理)。
談談Unicode編碼——其中有「大尾」和「小尾」的來源描述小人國呦