相關文章:
一步到位 Base64 編碼
Base64 之 JavaScript 實現
上面的相關文章中有一篇Base64 之 JavaScript 實現,本篇與之雖有異曲同工之妙,但它們是兩種完全不同的實現方式。本篇在講解上更優,因為對基礎知識講的更細致,分析更透徹。
btoa 和 atob
btoa 方法
btoa
是 Binary To ASCII 的簡寫,意思就是把二進制數據編碼轉換成Base64編碼的ASCII字符串。且btoa(str)
方法是瀏覽器中的一個全局(頂級)方法。
atob 方法
與 btoa
相反 atob
是 ASCII To Binary 的簡寫,意思是把Base64格式的ASCII字符串進行Base64解碼,得到原數據。atob
正是 btoa
方法的逆過程,并且它也是瀏覽器中的一個全局(頂級)方法。
萬事大吉?
使用瀏覽器下原生的 JavaScript 方法(btoa
和atob
)已經完全可以做到對字符串和二進制數據進行Base64的編碼與解碼;看到這里是不是覺得這一篇文章可以收尾了?其實這才剛剛開始!原理是因為給 btoa
傳遞一個中文字符串作為參數時,會出現如下代碼段所示的錯誤。
> btoa("我是仵士杰");
< VM197:1 Uncaught DOMException: Failed to execute 'btoa' on 'Window':
The string to be encoded contains characters outside of the Latin1 range.(…)
為什么會報錯呢?
The string to be encoded contains characters outside of the Latin1 range.
被編碼的字符串包含Latin1范圍以外的字符。
Latin1 是什么東西?
ISO/IEC8859-1,又稱Latin-1或“西歐語言”,是國際標準化組織內ISO/IEC 8859的第一個8位字符集。以ASCII為基礎,在空置的0xA0-0xFF的范圍內,加入96個字母及符號,藉以供使用變音符號的拉丁字母語言使用。
看明白了吧,其實btoa
只能轉換占一個字節寬度的字符,就是Latin1字符集(它是ASCII的超集)。而中文漢字是被編碼成占兩個或以上個字節的。所以btoa
方法無法對中文進行操作,于是就報了上面看到的錯誤。
曲線救國第一步
曲線救國的第一步我們先來介紹一下encodeURI、encodeURIComponent、decodeURI 和 decodeURIComponent這四個方法。它們是對字符串進行URI編碼和解碼的,也稱之為轉義。下面分別對他們進行介紹。
encodeURI 方法
下面是引用了w3cshool網站上對 encodeURI
方法的介紹
該方法不會對 ASCII 字母和數字進行編碼,也不會對這些 ASCII 標點符號進行編碼: - _ . ! ~ * ' ( ) 。
該方法的目的是對 URI 進行完整的編碼,因此對以下在 URI 中具有特殊含義的 ASCII 標點符號,encodeURI() 函數是不會進行轉義的:;/?:@&=+$,#
除上述介紹中明確說明不會被轉義的字符外,URI中所有其他字符(如中文字符等)都會被轉義。請看下面的代碼段:
> encodeURI("http://www.ibestcode.com/?name=仵士杰");
< "http://www.ibestcode.com/?name=%E4%BB%B5%E5%A3%AB%E6%9D%B0"
encodeURIComponent 方法
還是引用w3cshool網站上對上的介紹
該方法不會對 ASCII 字母和數字進行編碼,也不會對這些 ASCII 標點符號進行編碼: - _ . ! ~ * ' ( ) 。
其他字符(比如 :;/?:@&=+$,# 這些用于分隔 URI 組件的標點符號),都是由一個或多個十六進制的轉義序列替換的。
除上述介紹中明確說明不會被轉義的字符外,URI中所有其他字符(如中文字符和ASCII的“;/?:@&=+$,# ”等)都會被轉義。請看下面的代碼段:
> encodeURIComponent("http://www.ibestcode.com/?name=仵士杰");
< "http%3A%2F%2Fwww.ibestcode.com%2F%3Fname%3D%E4%BB%B5%E5%A3%AB%E6%9D%B0"
通過比較我們會發現 encodeURIComponent 與 encodeURI 相比,多轉換了在URI中具有特殊含義的字符:“;/?:@&=+$,#”。
decodeURI 和 decodeURIComponent
通過名字我們就能知道 decodeURI
是 encodeURI
的逆過程,而 decodeURIComponent
是 encodeURIComponent
的逆過程,在這里就不多做介紹了,請看下面的代碼段;
> decodeURI("http://www.ibestcode.com/?name=%E4%BB%B5%E5%A3%AB%E6%9D%B0");
< "http://www.ibestcode.com/?name=仵士杰"
> decodeURI("http%3A%2F%2Fwww.ibestcode.com%2F%3Fname%3D%E4%BB%B5%E5%A3%AB%E6%9D%B0");
< "http%3A%2F%2Fwww.ibestcode.com%2F%3Fname%3D仵士杰"
> decodeURIComponent("http%3A%2F%2Fwww.ibestcode.com%2F%3Fname%3D%E4%BB%B5%E5%A3%AB%E6%9D%B0");
< "http://www.ibestcode.com/?name=仵士杰"
> decodeURIComponent("http://www.ibestcode.com/?name=%E4%BB%B5%E5%A3%AB%E6%9D%B0");
< "http://www.ibestcode.com/?name=仵士杰"
轉義規則
從上面幾段代碼中大家應該已經發現,字符串“仵士杰”轉義之后對應的是“%E4%BB%B5%E5%A3%AB%E6%9D%B0”;字符“://”轉義后對應的是“%3A%2F%2F”;其實轉義的規則是把字符的utf-8編碼以十六進制顯示,并在每個字節(8bits=2個十六進制位)前加字符‘%’。有興趣的同學可以親自去驗證一下。
小結一下
btoa
方法的參數中不能有中文,而encodeURI
和 encodeURIComponent
都可以對中文進行轉義,并且轉義后的所有字符都是ASCII碼字符。如此用encodeURI
或 encodeURIComponent
轉義后的字符串再用btoa
函數操作是不是就可以了呢?當然這樣做是不會報錯了,但最終得到的字符串適用性不強,因為這不是單純的Base64編碼,而是先進行encodeURI轉義后再進行Base64編碼的結果。如果要得到原來的字符串,還是需要先進行Base64解碼,再進行decodeURI解碼兩步操作才行的。
柳暗花明 escape 和 unescape
escape 方法
同樣引用W3CSchool上面的介紹
該方法不會對 ASCII 字母和數字進行編碼,也不會對下面這些 ASCII 標點符號進行編碼: * @ - _ + . / 。其他所有的字符都會被轉義序列替換。
escape
方法接受一個字符串(是字符串,不是二進制串),他會根據字符編碼占用的字節數不同而使用不同的方式顯示編碼。看下面的例子:
> escape(":%?")
< "%3A%25%3F"
> escape("仵士杰")
< "%u4EF5%u58EB%u6770"
看出什么門道沒?沒看明白也沒關系,下面我來解釋一下:
其實 escape
的眼里所有字符都是Unicode編碼的, 如果遇到的字符Unicode編碼只占一個字節(其實就是Latin1字符集部分,[在這里說明一下,Unicode 字符集是 Latin1字符集的超集]),就以 "%"+Unicode編碼值的十六進制表示來編碼。如“:”被編碼成“%3A”。如果遇到的字符Unicode編碼占兩個字節,就以"%u"+Unicode編碼的十六進制表示來編碼。如“仵”被編碼成“%u4EF5”,有興趣的同學可以親自去查Unicode編碼表來驗證。關于占三四個字節的情況在此就不在討論了,有興趣的同學可以自己去深挖一下,挖出寶貝記得要跟我分享啊,哈哈……。
unescape 方法
unescape
方法執行的操作正是 escape
方法的逆過程,他會把所有“%XX”(XX是兩位十六進制值)轉換到Unicode中一字節能表示的部分(其實就是Latin1字符集中的字符)。unescape 函數是一個頂級 JavaScript 函數,并不與任何對象關聯。
氣滿放大招
綜合以上所述,我們可以清楚的知道,encodeURI 和 encodeURIComponent 會把漢字等轉換成UTF-8編碼后對每個字節進行轉義得到類似"%XX"(XX是兩位十六進制值)的串。而unescape 可以把所有 "%XX"(XX是兩位十六進制值)的串,解碼到Latin1字符集上。btoa 方法正好能夠操作Latin1字符集上的字符轉換成Base64編碼。于是乎以下代碼段產生了:
function utf8ToBase64(str){
return btoa(unescape(encodeURIComponent(str)));
}
而解碼過程是編輯過程的逆過程,于是得到如下代碼
function base64ToUtf8(str){
return decodeURIComponent(escape(atob(str)));
}
如果要像之前發的文章Base64 之 JavaScript 實現寫的那樣,封裝成一個Base64對象,就可以得到如下代碼了:
!function(W){
W.Base64 = {
utf8ToBase64:function (str){
return btoa(unescape(encodeURIComponent(str)));
},
base64ToUtf8: function(str){
return decodeURIComponent(escape(atob(str)));
}
}
}(window);
//下面是測試結果
> Base64.utf8ToBase64("仵士杰")
< "5Lu15aOr5p2w"
> Base64.base64ToUtf8("5Lu15aOr5p2w")
< "仵士杰"
打完收功!