前言
提到這個 %20
,想必大家都見過,熟悉一點編碼的人,還會知道這玩意就是空格轉換而來! 那麼我們一起破解, 如何編碼而來?
我們今天繼續學習前端編碼知識, 其他編碼文章:
之後再補上
- UTF-16 編碼
- UTF-8 編碼
前端所需要的基本編碼知識體系就基本形成。
更多前端基礎進階知識,可以
- 關注專欄 前端基礎進階,
- 關注公衆號
成長的程序世界
, - 進交流羣
dirge-cloud
Unicode基礎知識
Unicode 只是一個字符集, 其爲每個字符提供了一個編號,我們稱之爲碼點。
Unicode 可以使用的編碼有三種,分別是:
UFT-8:一種 變長的編碼方案
,使用 1~6 個字節來存儲。
UTF-16:對於碼點小於0xFFFF
(65535)的字符,兩個字節存儲,反之採用 4個字節來存儲。
UFT-32:一種 固定長度的編碼方案
,不管字符編號大小,始終使用 4 個字節來存儲。
所以UTF-8個UTF-16都屬於變長編碼方案,而UTF-32屬於固定長度編碼方案。
固定長度編碼方案優點當然是簡單啊,缺點嘛,費空間, 這就是爲嘛還要有UTF-16和UTF-8。
我們網絡傳輸常用 UTF-8, 而javascript運行時的字符編碼是 UTF-16.
%20
怎麼來的
我們看看,我們怎麼樣可以得到這個%20
:
escape(" ") "%20"
encodeURI(" ") "%20"
encodeURIComponent(" ") "%20"
其是字符的16進制格式值
, 是百分號編碼,之後會細說。
怎麼獲得這個編碼,寫一個簡單的方法你就懂了
function to16Format(ch){
return '%' + ch.codePointAt(0).toString(16)
}
to16Format(" ") // "%20"
雖然3個方法都能獲得同樣的值,
很少有人告訴你 esacpe是基於UTF-16,而另外兩個是基於 UTF-8, 看個例子:
0-0xFF
碼點範圍編碼結果是一致的,
0xFF
以上,結果就不一樣了, 原理我們後面說。
escape("") //%20
encodeURI("") //%20
escape("人") // "%u4EBA"
encodeURI("人") // "%E4%BA%BA"
escape("𣑕") // %uD84D%uDC55
encodeURI("𣑕") // "%F0%A3%91%95"
小結一下:
- escape,encodeURI和encodeURIComponent 對空格編碼
" "
均能得到20%
- escape進行的是UTF-16編碼,後兩者是UTF-8編碼,只是碼點
0xFF
以下的編碼結果一致罷了
當然,不是所有的字符都會被編碼,接下來一起看哪些字符不會被編碼。
哪些字符不會被編碼
%20
, 就不得不提到我們的常用編碼的三對姊妹:
- escape (unescape)
已過時
- encodeURI (decodeURI)
- encodeURIComponent (decodeURIComponent)
我們先把A-Z a-z 0-9
單獨列出來,因爲都是不會被編碼的, 看看哪些字符不會被編碼。
系列 | 保留字符 | 編碼 |
---|---|---|
escape | @ * _ + - . / |
UTF-16 |
encodeURI | - _ . ! ~ * ' ( ) ; , / ? : @ & = + $ # |
UTF-8 |
encodeURIComponent | - _ . ! ~ * ' ( ) |
UTF-8 |
編碼之 escape
簡單來說,escape是生成新的由十六進制轉義序列替換的字符串,作用是讓它們在所有電腦上可讀。
編碼之後的效果是%XX
或者%uXXXX
這種形式。
當你需要對URL編碼時,請使用 encodeURI
或者 encodeURIComponent
。
劃重點: 基於UTF-16進行編碼
UTF-16字符編碼,對於碼點大於0xFFFF
的字符,其編碼結果是分高低位的, charCodeAt(0)
可以獲得高位, charCodeAt(1)
可以獲得低位。
escape之碼點大於0xFFFF的字符
轉義爲兩個%uXXXX
先直接看代碼結果:
var ch = String.fromCodePoint(0x23455); // "𣑕"
escape(ch) // '%uD84D%uDC55' 碼點大於 0xFFFF
unescape(escape(ch)) // "𣑕"
ch.charCodeAt(0).toString(16).toUpperCase(); // 高位
// 'D84D'
ch.charCodeAt(1).toString(16).toUpperCase(); // 低位
// 'DC55'
看着結論就知道了,和charCodeAt的邏輯處理一致。 都是返回UTF-16編碼的高低位編碼。
編碼之 encodeURI
由於 URL 只能由標準 ASCII 字符
組成,因此必須對其他特殊字符進行編碼。它們將被代表 utf-8編碼
的一系列不同字符所取代。
encodeURI 和 encodeURIComponent 用於此目的。
劃重點,encodeURI 和 encodeURIComponent 採用的是UTF-8編碼。
先看看碼點和UTF-8編碼格式,以及需要的字節數。
Unicode 碼點範圍(十六進制) | 十進制範圍 | UTF-8 編碼方式(二進制) | 字節數 |
---|---|---|---|
0000 0000 ~ 0000 007F |
0 ~ 127 |
0xxxxxxx |
1 |
0000 0080 ~ 0000 07FF |
128 ~ 2047 |
110xxxxx 10xxxxxx |
2 |
0000 0800 ~ 0000 FFFF |
2048 ~ 65535 |
1110xxxx 10xxxxxx 10xxxxxx |
3 |
0001 0000 ~ 0010 FFFF |
65536 ~ 1114111 |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
4 |
我們先看看 人
字:
- 獲取其碼點
4eba
var codePoint = "人".codePointAt(0).toString(16) // `4eba`
- 其位於
0000 0800 ~ 0000 FFFF
, 格式爲1110xxxx 10xxxxxx 10xxxxxx
, 需要三個字節 - encodeURI, 可以看到是三個
%XX
encodeURI("人") // %E4%BA%BA
這裏我們省略了具體的編碼過程, 具體的編碼結果驗證可以去 Convert UTF8 to Binary Bits - Online UTF8 Tools 驗證
最終編碼結果: 11100100 10111010 10111010
(0b11100100).toString(16).toUpperCase() // E4
(0b10111010).toString(16).toUpperCase() // BA
(0b10111010).toString(16).toUpperCase() // BA
encodeURI("人") // %E4%BA%BA => E4 BA BA
再推導一下𣑕
字
- 碼點是
0x23455
0001 0000 ~ 0010 FFFF
之間,格式爲11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
, 需四個字節- encodeURI, 其由四個
%XX
組成
encodeURI("𣑕") // "%F0%A3%91%95"
編碼之 encodeURIComponent
既然有encodeURI
爲嘛還要來一個encodeURIComponent
呢?
其用於對地址後的 參數值進行編碼, 我們通常稱呼爲queryString
。
看個例子:
var param = "http://www.yyy.com"; //param爲參數
param = encodeURIComponent(param);
var url = "http://www.xxxx.com?target=" + param;
同理下面的?
之後的部分空 鍵=啊 哈&type=x
,鍵值對均需要encodeURIComponent進行編碼。
http://wwww.xxxyyy.com/哈 哈?空 鍵=啊 哈&type=x
其實吧,現代瀏覽器,默認都會自行進行編碼,你不妨把上面的地址貼到瀏覽器:
application/x-www-form-urlencoded
對於 application/x-www-form-urlencoded
(POST) 這種數據方式, 也是需要編碼的。
其編碼規則:
- 數據被編碼成以
'&'
分隔的鍵-值對, 同時以'='
分隔鍵和值. - 非字母或數字的字符會被 percent-encoding
我們先一起看看 percent-encoding(百分號編碼)。
percent-encoding
百分比編碼(也有叫百分號編碼的) 是一種擁有8位字符編碼的編碼機制,這些編碼在URL的上下文中具有特定的含義。它有時被稱爲URL編碼。編碼由英文字母替換組成:“%” 後跟替換字符的ASCII的十六進制表示。
它廣泛地應用於主統一資源標誌符/統一資源定位符集(URI) ,其中包括 URL 和統一資源名(URN)。
它還用於準備應用 application/x-www-form-urlencoded 媒體類型的數據,這通常用於在 HTTP 請求中提交 HTML 表單數據。
URI所允許的字符分作保留與未保留。保留字符是那些具有特殊含義的字符,例如:斜線字符用於URL(或URI)不同部分的分界符;未保留字符沒有這些特殊含義。百分號編碼把保留字符表示爲特殊字符序列。
保留字符
保留字符需要編碼,其有: ':'
,'/'
,'?'
,'#'
,'['
,']'
,'@'
,'!'
,'$'
,'&'
,"'"
,'('
,')'
,'*'
,'+'
,','
,';'
,'='
,以及,'%'
本身, 以及一個空格 " "
。
percent-encoding編碼對照表請參見:percent-encoding | MDN
非保留字符
不需要被編碼,直接使用就行。
- A-Z
- a-z
- 0-9
- _ . ~
特殊的字符 " "
,
- 其在作爲URL的時候,編碼是轉爲
%20
- post提交(application/x-www-form-urlencoded)替換爲
+
那麼,我們這裏直接使用 encodeURLComponent編碼值和鍵,能行嗎?
答案是不行:
百分比編碼需要編碼特殊字符的是 20個(加上 ' '
)
: / ? # [ ] @ ! $ & ' ( ) * + , ; = %
encodeURLComponent不編碼的字符是 9 個:
- _ . ! ~ * ' ( )
所以還需要額外編碼爲: ['!', "'", '(', ')', '*']
, 怎麼計算而得,參見下面代碼:
var percentChars = [':', '/', '?', '#', '[', ']', '@', '!', '$', '&', "'", '(', ')', '*', '+', ',', ';', '=', '%', ' '];
var eURICChars = ['-', '_', '.', '!', '~', '*', "'", '(', ')'];
var notInPChars = percentChars.filter(c=> eURICChars.includes(c));
console.log("notInPChars:", notInPChars);
// notInPChars: (5) ['!', "'", '(', ')', '*']
所以,完整的編碼應該如下:
function encodeValue(val)
{
var eVal = encodeURIComponent(val);
// 單獨處理encodeURIComponent不編碼的字符
eVal = eVal.replace(/\*/g, '%2A');
eVal = eVal.replace(/!/g, '%21');
eVal = eVal.replace(/\(/g, '%28');
eVal = eVal.replace(/\)/g, '%29');
eVal = eVal.replace(/'/g, '%27');
// 特殊處理空格字符
return eVal.replace(/\%20/g,'+');
}
Content-Disposition: attachment; filename
我們後臺返回文件的時候,如果指定Content-Disposition: attachment
並設定好filename, 客戶端收到請求後是可以直接進行文件下載的。 問題就在於這個filename,其也是需要被編碼的,我們瞭解一下即可:
參考MDN:
var fileName = 'my file(2).txt';
var header = "Content-Disposition: attachment; filename*=UTF-8''"
+ encodeRFC5987ValueChars(fileName);
console.log(header);
// 輸出 "Content-Disposition: attachment; filename*=UTF-8''my%20file%282%29.txt"
function encodeRFC5987ValueChars (str) {
return encodeURIComponent(str).
// 注意,儘管 RFC3986 保留 "!",但 RFC5987 並沒有
// 所以我們並不需要過濾它
replace(/['()]/g, escape). // i.e., %27 %28 %29
replace(/\*/g, '%2A').
// 下面的並不是 RFC5987 中 URI 編碼必須的
// 所以對於 |`^ 這3個字符我們可以稍稍提高一點可讀性
replace(/%(?:7C|60|5E)/g, unescape);
}
其比 percent-encoding又還有些區別,註釋裏面寫得很清楚。 我真想說,搞那麼多協議不累嗎?
看到註冊,我們可以看到 RFC3986, RFC5987等協議,我們一起了解一下。
RFC3986 ,RFC1738 ,RFC5987
RFC3986, RFC1738
是關於URI的編碼規範,RFC5987
是關於http協議文件頭字段的規範。
-
RFC3986
2005年發佈,現行標準。文檔對URL的編解碼問題做出了詳細的建議,指出了哪些字符需要被編碼纔不會引起Url語義的轉變,以及對爲什麼這些字符需要編碼做出了相應的解釋 -
RFC 1738
94年發佈。同上。 -
RFC5987
Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters。 翻譯: 超文本傳輸協議文件頭字段參數的字符集和語言編碼, 對http傳輸頭部字符串編碼的規範。
你會發現很多代碼還會處理~
符號,雖然RFC3986文檔規定,對於波浪符號~,不需要進行Url編碼,但是還是有很多老的網關或者傳輸代理。
兼容性好的代碼,會兼容處理 RFC1738, 比如著名的qs庫的 formats.js
window.btoa 和window.atob
window.btoa可以進字符進行base64編碼, window.atob可以解碼。
window.btoa("abcd") // "YWJjZA=="
window.atob("YWJjZA==") // "abcd"
但是其職能編碼ASCII 字符串, 試試中文:
window.btoa("人")
// Uncaught DOMException: Failed to execute 'btoa' on 'Window':
// The string to be encoded contains characters outside of the Latin1 range.
怎麼解決呢?
// ucs-2 string to base64 encoded ascii
function utoa(str) {
return window.btoa(unescape(encodeURIComponent(str)));
}
// base64 encoded ascii to ucs-2 string
function atou(str) {
return decodeURIComponent(escape(window.atob(str)));
}
驗證一下, 完美。
utoa("人") //5Lq6
atou("5Lq6") //人
那麼這是什麼思路呢???
encodeURIComponent 將字符轉爲百分比utf-8字節存儲爲% XX 之後,unescape 將它們轉換爲 btoa 所要求的單個代碼點。因此,btoa (unescape (encodeURIComponent (str)))都將文本編碼爲 utf-8字節,然後將其編碼爲 Base64。
雖然,你去掉中間的unescape和escape也可以正常使用,但是必須搭配使用啦。
但是,已經不是標準的utf-8轉爲Base64了。
自己玩:
window.btoa(encodeURIComponent("我是人a"))
// JUU2JTg4JTkxJUU2JTk4JUFGJUU0JUJBJUJBYQ==
decodeURIComponent(window.atob("JUU2JTg4JTkxJUU2JTk4JUFGJUU0JUJBJUJBYQ=="))
// 我是人a
標準base解碼,已經得不到正確結果:
總結
%20
是escape或者URL編碼得到的結果,對應着空字符" "
。 也可是說是百分號編碼。- escape是把字符串轉爲十六進制轉義序列,作用是讓它們在所有電腦上可讀。 已過時,現在也沒啥用。
- encodeURI 是URL編碼,不處理參數部分
- encodeURIComponent 也是URL編碼
主要用於- url的參數部分
- post 數據類型爲application/x-www-form-urlencoded
- 附件文件名 filename
- RFC3986 ,RFC1738 是URL編碼協議
- RFC5987 是http傳輸頭部字符串編碼的規範
- window.btoa 和window.atob 默認只能處理ASCII碼字符,在encodeURIComponent和escape的配合下,可以處理任意字符。
最後提一個問題:
百分號編碼 和 escape, encodeURI, encodeURIComponent是什麼關係?
寫在最後
不忘初衷,有所得,而不爲所累,如果你覺得不錯,你的一讚一評就是我前行的最大動力。
技術交流羣請到 這裏來。
或者添加我的微信 dirge-cloud,一起學習。
引用
escape (string)
encodeURI
encodeURIComponent
escape,encodeURI,encodeURIComponent有什麼區別
percent-encoding | MDN
Percent-encoding | wikipedia
百分號編碼 | 中文 維基
Converting to Base64 in JavaScript without Deprecated 'Escape' call