公交车上荫蒂添的好舒服的电影-公用玩物(np双xing总受)-公用小荡货芊芊-公与妇仑乱hd-攻把受做哭边走边肉楼梯play-古装一级淫片a免费播放口

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

[譯]JavaScript中Base64編碼字符串的細節(jié)

freeflydom
2023年11月27日 16:4 本文熱度 1146

本文作者為 360 奇舞團前端開發(fā)工程師


本文為翻譯


原文標題:The nuances of base64 encoding strings in Javascript

原文作者:Matt Joseph

原文鏈接:https://web.dev/articles/base64-encoding  


Base64編碼和解碼是一種常見的將二進制內(nèi)容轉(zhuǎn)換為適合Web的文本的形式。它通常用于data URLs,比如內(nèi)嵌圖片。


當你在Javascript中對字符串應(yīng)用base64編碼和解碼時會發(fā)生什么?這篇文章探討了這些細節(jié)和需要避免的常見陷阱。


btoa() 和 atob() 函數(shù)

Javascript中進行base64編碼和解碼的核心函數(shù)是btoa()和atob()。btoa()用于將字符串轉(zhuǎn)換為base64編碼的字符串,而atob()則用于解碼。


下面是一個快速示例:


// 一個非常簡單的字符串,僅包含低于128的代碼點。

const asciiString = 'hello';

 

// 這將會成功,它將打印:

// 編碼后的字符串: [aGVsbG8=]

const asciiStringEncoded = btoa(asciiString);

console.log(`Encoded string: [${asciiStringEncoded}]`);

 

// 這也將會成功,它將打印:

// 解碼后的字符串: [hello]

const asciiStringDecoded = atob(asciiStringEncoded);

console.log(`Decoded string: [${asciiStringDecoded}]`);

不幸的是,正如MDN文檔所指出的,這只適用于包含ASCII字符的字符串,即可以用單個字節(jié)表示的字符。換句話說,這對于Unicode來說不起作用。


要理解發(fā)生了什么,請嘗試以下代碼:


// 示例字符串表示了小、中、大代碼點的組合。

// 這個示例字符串是有效的UTF-16。

// 'hello' 的代碼點都低于128。

// '⛳' 是一個16位代碼單元。

// '❤️' 是兩個16位代碼單元,U+2764 和 U+FE0F(一個心形和一個變體)。

// '🧀' 是一個32位代碼點(U+1F9C0),也可以表示為兩個16位代碼單元的替代對 '\ud83e\uddc0'。

const validUTF16String = 'hello⛳❤️🧀';

 

// 這將不會成功。它將打印:

// DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.

try {

  const validUTF16StringEncoded = btoa(validUTF16String);

  console.log(`Encoded string: [${validUTF16StringEncoded}]`);

} catch (error) {

  console.log(error);

}

字符串中的任何一個表情符號都會導致錯誤。為什么Unicode會引起這個問題?


為了理解,讓我們先退后一步,深入了解計算機科學和Javascript中的字符串。


Unicode和Javascript中的字符串

Unicode是當前的全球字符編碼標準,它是將數(shù)字分配給特定字符的實踐,以便在計算機系統(tǒng)中使用。有關(guān)Unicode的更深入了解,請訪問W3C的文章。


h - 104

ñ - 241

❤ - 2764

❤️ - 2764 帶有一個隱藏的修改編號65039

⛳ - 9971

🧀 - 129472


表示每個字符的數(shù)字被稱為“代碼點”。您可以將“代碼點”視為每個字符的地址。在紅心表情符號中,實際上有兩個代碼點:一個用于心形,另一個用于“變化”顏色并使其始終為紅色。


深入了解變體選擇器的概念。


Unicode有兩種常見的方法將這些代碼點轉(zhuǎn)換為計算機可以一致解釋的字節(jié)序列:UTF-8和UTF-16。


一個過于簡化的視角是:


在UTF-8中,一個代碼點可以使用一到四個字節(jié)(每個字節(jié)8位)。


在UTF-16中,一個代碼點始終是兩個字節(jié)(16位)。


重要的是,Javascript處理字符串時使用的是UTF-16。這破壞了像btoa()這樣的函數(shù),這些函數(shù)實際上是基于這樣一個假設(shè):字符串中的每個字符映射到一個單字節(jié)。MDN上明確說明了這一點:


The btoa() method creates a Base64-encoded ASCII string from a binary string (i.e., a string in which each character in the string is treated as a byte of binary data).


現(xiàn)在您知道Javascript中的字符通常需要不止一個字節(jié),下一部分將演示如何處理這種情況下的base64編碼和解碼。


btoa()和atob()與Unicode

正如您現(xiàn)在所知,拋出的錯誤是由于我們的字符串包含位于單個字節(jié)之外的UTF-16字符。


幸運的是,MDN關(guān)于base64的文章包含了一些有用的示例代碼來解決這個“Unicode問題”。您可以修改這些代碼以適應(yīng)前面的示例:


// 來自https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem。

function base64ToBytes(base64) {

  const binString = atob(base64);

  return Uint8Array.from(binString, (m) => m.codePointAt(0));

}

 

// 來自https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem。

function bytesToBase64(bytes) {

  const binString = String.fromCodePoint(...bytes);

  return btoa(binString);

}

 

// 示例字符串表示了小、中、大代碼點的組合。

// 這個示例字符串是有效的UTF-16。

// 'hello' 的代碼點都低于128。

// '⛳' 是一個16位代碼單元。

// '❤️' 是兩個16位代碼單元,U+2764 和 U+FE0F(一個心形和一個變體)。

// '🧀' 是一個32位代碼點(U+1F9C0),也可以表示為兩個16位代碼單元的替代對 '\ud83e\uddc0'。

const validUTF16String = 'hello⛳❤️🧀';

 

// 這將會成功。它將打印:

// 編碼后的字符串: [aGVsbG/im7PinaTvuI/wn6eA]

const validUTF16StringEncoded = bytesToBase64(new TextEncoder().encode(validUTF16String));

console.log(`Encoded string: [${validUTF16StringEncoded}]`);

 

// 這將會成功。它將打印:

// 解碼后的字符串: [hello⛳❤️🧀]

const validUTF16StringDecoded = new TextDecoder().decode(base64ToBytes(validUTF16StringEncoded));

console.log(`Decoded string: [${validUTF16StringDecoded}]`);The following steps explain what this code does to encode the string:

使用TextEncoder接口將UTF-16編碼的Javascript字符串轉(zhuǎn)換為UTF-8編碼的字節(jié)流,可通過TextEncoder.encode()實現(xiàn)。


這將返回一個Uint8Array,這是Javascript中較少使用的數(shù)據(jù)類型,是TypedArray的子類。


將這個Uint8Array提供給bytesToBase64()函數(shù),該函數(shù)使用String.fromCodePoint()將Uint8Array中的每個字節(jié)作為代碼點處理,并從中創(chuàng)建一個字符串,其結(jié)果為一個可以全部用單個字節(jié)表示的代碼點的字符串。


使用btoa()對該字符串進行base64編碼。


解碼過程與此相同,但順序相反。


這有效的原因是,Uint8Array和字符串之間的步驟保證了雖然Javascript中的字符串是以UTF-16的兩字節(jié)編碼表示的,但每兩個字節(jié)代表的代碼點始終小于128。


這段代碼在大多數(shù)情況下都工作良好,但在其他情況下會悄悄地失敗。


靜默失敗的案例

使用相同的代碼,但使用不同的字符串:


// 來自https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem。

function base64ToBytes(base64) {

  const binString = atob(base64);

  return Uint8Array.from(binString, (m) => m.codePointAt(0));

}

 

//  來自https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem。

function bytesToBase64(bytes) {

  const binString = String.fromCodePoint(...bytes);

  return btoa(binString);

}

 

// 示例字符串表示了小、中、大代碼點的組合。

// 這個示例字符串是無效的UTF-16。

// 'hello' 的代碼點都低于128。

// '⛳' 是一個16位代碼單元。

// '❤️' 是兩個16位代碼單元,U+2764 和 U+FE0F(一個心形和一個變體)。

// '🧀' 是一個32位代碼點(U+1F9C0),也可以表示為兩個16位代碼單元的替代對 '\ud83e\uddc0'。

// '\uDE75' 是代理對中的一半。

const partiallyInvalidUTF16String = 'hello⛳❤️🧀\uDE75';

 

// 這將會成功。它將打印:

// 編碼后的字符串: [aGVsbG/im7PinaTvuI/wn6eA77+9]

const partiallyInvalidUTF16StringEncoded = bytesToBase64(new TextEncoder().encode(partiallyInvalidUTF16String));

console.log(`Encoded string: [${partiallyInvalidUTF16StringEncoded}]`);

 

// 這也將會成功。它將打印:

// 解碼后的字符串: [hello⛳❤️🧀�]

const partiallyInvalidUTF16StringDecoded = new TextDecoder().decode(base64ToBytes(partiallyInvalidUTF16StringEncoded));

console.log(`Decoded string: [${partiallyInvalidUTF16StringDecoded}]`);

如果您查看解碼后的最后一個字符(�)的十六進制值,您會發(fā)現(xiàn)它是\uFFFD而不是原來的\uDE75。它沒有失敗或拋出錯誤,但輸入和輸出數(shù)據(jù)已經(jīng)悄悄地改變了。為什么會這樣?


Javascript API中的字符串變化

如前所述,Javascript將字符串處理為UTF-16。但是UTF-16字符串有一個獨特的屬性。


以奶酪表情為例。這個表情(🧀)的Unicode代碼點是129472。不幸的是,16位數(shù)的最大值是65535!那么UTF-16是如何表示這個更高的數(shù)字的呢?


UTF-16有一個稱為代理對的概念。您可以這樣想:


對中的第一個數(shù)字指定要搜索的“書籍”。這被稱為 "surrogate"。


對中的第二個數(shù)字是“書籍”中的條目。


您可以想象,有時僅擁有代表書籍的數(shù)字而沒有實際書籍中的條目可能是有問題的。在UTF-16中,這被稱為 lone surrogate。


這在Javascript中尤其具有挑戰(zhàn)性,因為一些API盡管存在單獨代理也能工作,而其他API則會失敗。


在前面的例子中,您在從base64解碼回來時使用了TextDecoder。特別是,TextDecoder的默認設(shè)置指定了以下內(nèi)容:


它默認為false,這意味著解碼器用替代字符替換格式錯誤的數(shù)據(jù)。


您之前觀察到的那個�字符,用十六進制表示為\uFFFD,就是那個替代字符。在UTF-16中,帶有單獨代理的字符串被視為“格式錯誤的”或“不規(guī)范的”。


有各種Web標準(示例1, 2, 3, 4)準確指定了格式錯誤的字符串何時影響API行為,但值得注意的是TextDecoder是這些API之一。在進行文本處理之前確保字符串格式規(guī)范是一個好習慣


檢查格式良好的字符串

最近版本的瀏覽器現(xiàn)在具有用于此目的的函數(shù):isWellFormed().


瀏覽器支持: isWellFormed().


您可以通過使用encodeURIComponent()來實現(xiàn)類似的結(jié)果,如果字符串包含單獨代理,則會拋出URIError錯誤。


以下函數(shù)在可用時使用isWellFormed(),如果不可用則使用encodeURIComponent()。類似的代碼可用于創(chuàng)建isWellFormed()的polyfill。


// 由于舊版瀏覽器不支持isWellFormed(),可以快速創(chuàng)建polyfill。

// encodeURIComponent()對于單獨代理會拋出錯誤,這本質(zhì)上是相同的。

function isWellFormed(str) {

  if (typeof(str.isWellFormed)!="undefined") {

    // 使用更新的isWellFormed()功能。

    return str.isWellFormed();

  } else {

    // 使用較老的encodeURIComponent()。

    try {

      encodeURIComponent(str);

      return true;

    } catch (error) {

      return false;

    }

  }

}

將所有內(nèi)容整合在一起

現(xiàn)在您已經(jīng)知道如何處理Unicode和單獨代理,您可以將所有內(nèi)容整合在一起,創(chuàng)建能夠處理所有情況并且不會進行靜默文本替換的代碼。


// 來自https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem.

function base64ToBytes(base64) {

  const binString = atob(base64);

  return Uint8Array.from(binString, (m) => m.codePointAt(0));

}

 

// 來自https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem.

function bytesToBase64(bytes) {

  const binString = String.fromCodePoint(...bytes);

  return btoa(binString);

}

 

// 由于舊版瀏覽器不支持isWellFormed(),可以快速創(chuàng)建polyfill。

// encodeURIComponent()對于單獨代理會拋出錯誤,這本質(zhì)上是相同的。

function isWellFormed(str) {

  if (typeof(str.isWellFormed)!="undefined") {

    // Use the newer isWellFormed() feature.

    return str.isWellFormed();

  } else {

    // Use the older encodeURIComponent().

    try {

      encodeURIComponent(str);

      return true;

    } catch (error) {

      return false;

    }

  }

}

 

const validUTF16String = 'hello⛳❤️🧀';

const partiallyInvalidUTF16String = 'hello⛳❤️🧀\uDE75';

 

if (isWellFormed(validUTF16String)) {

  // 這將會成功。它將打印:

  // 編碼后的字符串: [aGVsbG/im7PinaTvuI/wn6eA]

const validUTF16StringEncoded = bytesToBase64(new TextEncoder().encode(validUTF16String));

console.log(`Encoded string: [${validUTF16StringEncoded}]`);

 

  // 這將會成功。它將打印:

  // 解碼后的字符串: [hello⛳❤️🧀]

  const validUTF16StringDecoded = new TextDecoder().decode(base64ToBytes(validUTF16StringEncoded));

  console.log(`Decoded string: [${validUTF16StringDecoded}]`);

} else {

  // 忽略

}

 

if (isWellFormed(partiallyInvalidUTF16String)) {

  // 忽略

} else {

  // 這不是一個格式良好的字符串,因此我們要處理這種情況。

  console.log(`Cannot process a string with lone surrogates: [${partiallyInvalidUTF16String}]`);

}

這段代碼可以進行許多優(yōu)化,比如將其泛化為一個polyfill,將TextDecoder的參數(shù)更改為在單獨代理處拋出而不是默默替換,以及其他。有了這些知識和代碼,您還可以明確決定如何處理格式不正確的字符串,比如拒絕數(shù)據(jù)或明確啟用數(shù)據(jù)替換,或者為以后分析而拋出錯誤。除了作為base64編碼和解碼的一個有價值的例子外,本文還提供了一個例子,說明仔細處理文本數(shù)據(jù)尤其重要,特別是當文本數(shù)據(jù)來自用戶生成或外部來源時。


- END -

————————————————

版權(quán)聲明:本文為CSDN博主「奇舞周刊」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。

原文鏈接:https://blog.csdn.net/qiwoo_weekly/article/details/134522065



該文章在 2023/11/27 16:04:38 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 高清在线制服亚洲 | 国产三级精品播放 | 国产毛片久久久久久久精品 | 国产a级作爱视频 | 91天堂一区二区三区在线 | 国产精品无码一区免费看 | 精品人妻大屁股白浆久久 | 91精品国产自产在线观看 | 国产成人啪在线观看一 | 国产成人精品久久一区二区三 | 国产av台湾精品 | 91在线无码视频 | 国产精品嫩草影院午夜 | 国产精品亚洲精品日韩电影 | 国产日韩一区二区三区在线观看 | 激情欧美成人久久综合小说 | 成人无码国产电影 | 国产精品福利在线观电影看 | av无码一区二区 | 国产午夜电影久久 | 国产精品白丝jk白袜喷水视频 | 国产精品乱码久久久久久小说 | 成人综合国产成人亚洲 | 国产精品v欧美精品v日韩苍 | 国产亚洲综合专区在线播放 | 91久久精品在这里色伊人68 | av无码高清| 东京热无码人妻一区二区av | 国产成人午夜在线观看91 | 成午夜精品一区二区三区 | 国产精品极品美女自在线观看免 | 韩国三级丰满40少妇高潮 | 国产欧美国日产在线视频 | 精品一区二区三区东京热 | 18禁超污无遮挡无码免费网站 | 精品一卡二卡三无码a | 国产交换配乱婬视频a | 国产一区二区三区亚洲 | 国产成人精品手机在线观看 | 国产aaa午夜激 | av无码免费无禁无码网站 |