浅谈一下前端字符编码

背景

众所周知,计算机只能识别二进制,它是由逻辑电路组成,逻辑电路通常只有两个状态,开关的接通与断开,这两种状态正好可以用二进制数的0和1表示。但是现实中存在着其他的字符:数字、字母、中文、特殊符号等。因此就需要将这些字符转化成计算器可以识别的二进制编码。而我们在开发过程中,也常常会遇到各种各样的编码,例如ACSII、utf-8、base64等编码,接下来让我们来看一下这些常见编码。
编码方式

ASCII

我们知道在计算机存储数据时要使用二进制进行表示。而最初计算机只在美国使用,因此人们要考虑如何使用二进制来表达 52 个英文字母(包括大小写)、阿拉伯数字(0-9)以及常用的符号(如! @ # $ 等)。
于是便有从电报码发展而来的 ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)(发音 /ˈæski/)编码。它定义了英文字符和二进制的对应关系,一直沿用至今。

标准的ASCII字符总计有128个字符(2^7),字节的最高位一般设置为0。按照字符是否可见,可分为33个不可见字符,95个可见字符。

● 不可见字符:0-31 和 127 (0x00-0x1F 和 0x7F) 为不可见字符,也是控制字符,共 33 个。用于进行终端的换行、响铃、删除等动作。
浅谈一下前端字符编码_第1张图片

● 可见字符:32-126 (0x20-0x7E) 为可见字符,共 95 个,存储了空格、0-9 十个阿拉伯数字、52 个大小写英文字母,以及标点、运算符号等。
浅谈一下前端字符编码_第2张图片

虽然现代英语使用 128 个字符就足够了,但表示其他语言就远远不够了。因此当 ASCII 进入欧洲后,又被扩展为了 EASCII(Extended ASCII),将 7 bit 扩展为 8 bit,从128为扩展成256位,并且前 127 个编码含义和ASCII 保持一致。
编码
我们要知道一个字符对应的二进制,可以先找到它对应的十进制,然后再转化为二进制。
例如‘d’字符,它对应的十进制是100,转二进制的口诀是:除2倒取余法”,即将十进制整数除以2,得到一个商和一个余数;再将商除以2,又得到一个商和一个余数;以此类推,直到商等于零为止。
计算如下,倒取余数可以得到:110 0100
浅谈一下前端字符编码_第3张图片

解码
可以看到以下的编码,使用 ASCII 码进行映射时,下面的二进制编码可以翻译成“Hello world”。

01001000 01100101 01101100 01101100 01101111 00100000 01110111 
01101111 01110010 01101100 01100100

我们列举一下第一个二进制的0100100的转化,对应十进制的转法是:把二进制数按权展开、相加即得十进制数。

2^6+2^3=64+8=72

可以算出它对应的十进制是72,从表格上对应到的字符就是H。

ASCII的主要缺点是它只能表示256个不同的字符,因为它只有8位。这意味着ASCII无法编码世界上许多其他语言中的字符。如果想要在计算机上使用中文、俄语、日语等语言,就需要另一种不同的字符编码标准。Unicode进一步扩展为UTF-8、UTF-16、UTF-32等编码方案,以便能够编码各种类型的字符。因此,ASCII和Unicode之间的主要区别在于所使用的位数进行编码。接下来我们来看一下Unicode的概念和使用方式。

Unicode

统一码(Unicode),也叫万国码、单一码它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、垮平台进行文本转换、处理的要求,是国际组织制定的,用于收纳世界上所有文字和符号的字符集方案。前128个字符同ASCII一样,进行扩充后,使用数字0-0x10FFFF来映射这些字符。

● 码点
Unicode 规定了每个字符的数字编号,这个编号被称为 码点(code point)。码点以 U+hex 的形式表示,U+是代表Unicode的前缀,而 hex 是一个16进制数。取值范围是从 U+0000 到 U+10FFFF。每个码点对应一个字符,绝大部分的常见字符在最前面的 65536 (2^16)个字符,范围是 U+0000到U+FFFF。

● 字符平面:目前的Unicode分成了17个编组,也称平面,每个平面有65536个码点。
○ 基本平面:U+0000 - U+FFFF,多数常见字符都在该区间,其他平面则为辅助平面。
○ 辅助平面:U+10000 到 U+10FFFF,如我们在网上常见 Emoji 表情。

Unicode通常为两个字节,对于英文字符的一个字节即可表示,高位字节补0,这样对比ASCII编码存储空间就会翻倍,在存储和传输上就十分不划算。这就会使得Unicode编码一时间很难推广。于是,为了较好的解决 Unicode 的编码问题, UTF-8 和 UTF-16、UTF-32 应运而生(UTF-8是8位的单字节码元,UTF-16是16位的双字节码元,UTF-32是32位的四字节码元)。UTF是Unicode TransferFormat的缩写。
Unicode和ASCII的区别如下:

UTF-8

UTF-8是一种可变长度字符编码,其第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部分修改后,便可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。

计算机在读取 UTF-8 中以 0 开头的内容时,就知道只需要读取一个字节并显示 Unicode 中 0-127 范围内的正确字符即可。如果遇到两个 1,就需要读取 2 个字节,范围为128-2047,3 个 1 在一起表示需要读取三个字节。

十六进制 二进制 范围
0000 0000 - 0000 007F 0xxxxxxx 0-127
0000 0080 - 0000 07FF 110xxxxx 10xxxxxx 128-2047
0000 0800 - 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx 2048-65535
0001 0000 - 0010 FFFF 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-2098151

应用

1. URL编码
在前端常接触的网页中,URL链接编码也是非常常见的。因为URL 只能包含标准的 ASCII 字符,所以必须对其他特殊字符进行编码。
JavaScript提供了四个URL的编码/解码方法,可以用于将非ASCII码的字符,如中文字符、特殊字符、表情字符等,进行UTF-8的编解码操作:
● 编码:encodeURI() 和 encodeURIComponent()
● 解码:decodeURI() 和 decodeURIComponent()

转换方式为:先转为UTF-8的字节码,然后前面加个 % 进行拼接得到编码结果。

encodeURI(' 12 33')--->'%2012%2033'
decodeURI('%2012%2033')--->' 12 33'

注意encodeURL有11个字符不能进行编码,只能使用encodeURLComponent进行编码

encodeURI与encodeURIComponent区别

○ encodeURI
encodeURI通常用于转码整个 URL,不会对URL 元字符以及语义字符进行转码,URL元字符:

  1. URL 元字符:分号(;),逗号(,),斜杠(/),问号(?),冒号(:),at(@),&,等号(=),加号(+),美元符号($),井号(#)
  2. 语义字符:a-z,A-Z,0-9,连词号(-),下划线(_),点(.),感叹号(!),波浪线(~),星号(*),单引号('),圆括号(())

○ encodeURIComponent
encodeURIComponent()通常只用于转码URL组成部分,如URL中?后的一串;会转码除了语义字符之外的所有字符,即元字符也会被转码

2. 指定编码
如果没有显式指定编码方式,浏览器假定任何程序的源代码都是用本地字符集编写的,这会因国家/地区而异,可能会出现意料之外的情况。因此,给 JavaScript 文档设置字符集非常重要,可以使用以下三种方式进行设定

○ 获取文件时,可以在Content-type指定

Content-Type: application/javascript; charset=utf-8

○ 在script标签设置charset

<script src="./app.js" charset="utf-8">

○ 嵌入head中


<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
</head>

在前端开发中,Javascript程序是使用Unicode字符集,Javascript源码文本通常是基于UTF-8编码。
但js代码中的字符串类型是UTF-16编码的,正如 ECMAScript 标准所说,JavaScript 字符串都是 UTF-16 序列。这也是解释了api接口返回字符串在前端出现乱码,因为多数服务都使用utf-8编码,前后编码方式不一致。

3. ‘锟斤拷’乱码问题
由于 Unicode 字符集在不断更新中,因此会出现 A 系统发送的字符,在 B 系统中无法识别的情况。于是 Unicode 规定对于无法识别的字符,一律使用 �(0xFFFD )字符来代替。
将FFFD转utf-8时,我们可以先将其转十进制等于65534,发现其在三个字节内的,因此先将其转成二进制。
从低位开始分成六位一组得到三组: 1111 111111 111110。后两组前面补10,第一组补1110。最后得到utf-8编码: 11101111 10111111 10111110。

//十六进制 -》十进制
FFFD -> 15*16^3+15*16^2+15*16+14=65534  -1111 111111 111110
                                        11101111 10111111 10111110
                                        	EF        BF       BD

然后再将其转成十六得到EF BF BD,即 0xFFFD 在 UTF-8 编码下为 0xEF 0xBF 0xBD,当多 � 出现时,就会产生连续的 0xEF 0xBF 0xBD 0xEF 0xBF 0xBD。
如果这些字符又被使用了 GB 编码的程序中打开,就会按照 GB 双字节编码将其解析。这样刚好就对应了 「0xEFBF 锟」 ,「0xBDEF 斤」,「0xBFBD 拷」 这几个字。
浅谈一下前端字符编码_第4张图片

base64

Base64 也称为 Base64 内容传输编码。Base64 是将二进制数据编码为 ASCII 文本。Base64 一个字节只能表示 64 种情况,且编码格式每个字节的前两位都只能是 0,使用剩下的 6 位表示内容。再加上大多数字符集中存在的一个填充字符=。所以它是一种仅使用可打印字符表示二进制数据的方法。Base64 常用于在通常处理文本数据的场景,表示、传输、存储一些二进制数据,包括MIME的电子邮件及XML的一些复杂数据、以及图片地址。

这种编码格式无法充分利用存储资源,效能较低。那为什么还会成为网络中的普遍用法呢?
----其实 Base64 最早是应用在邮件传输协议中的。当时邮件传输协议只支持 ASCII 字符传递,使用 ASCII 码来表示所有的英文字符和数字还有一些符号。这里有一个问题,如果邮件中只传输英文数字等,那么 ASCII 可以直接支持。但是如果要在文件中传输图片、视频等资源的话,这些资源转成 ASCII 的时候会出现非英文数字的情况。而且邮件中还存在很多控制字符,这些控制字符又会成为不可见字符。非英文字符和控制字符在传输过程中很容易产生错误,影响邮件的正确传输。为此才有了诞生了一个新的编码规则,把二进制以 3 个字节为一组,再把每组的 3 个字节(24 位)转换成 4 个 6 位,每 6 位根据查以下映射表对应一个 ASCII 符号。不够6位使用000000 字节值在末尾补足,使其字节数能够被 3 整除,补位用 = 表示,每2个额外的0由1个 = 字符表示,并在解码时自动去除这就是 Base64。
浅谈一下前端字符编码_第5张图片

编码
例如我们要编译hello,首先将其转成ASCII码01001000 01100101 01101100 01101100 01101111
Hello–> 01001000 01100101 01101100 01101100 01101111
然后将其从前往后,三个字节为一组,后面两个字节也自成一组。每一组按照六位为一组,不够六位补0。
算出每组对应的十进制,然后到表格中找出对应的符号,对应的转化如下,由于最后的一组补充了两个0,因此需要补充1个填充字符=

010010 000110 010101 101100           011011 000110 111100
18       6      21      44              27    6      60
S        G       V       s               b     G     8=

即编码后hello-》SGVsbG8=

解码
了解了编码,我们来看一下解码,还是用上面的例如对于编码后的字符SGVsbk8==。
四个字符为一组,并且删除每一组尾部的=。将每一个字符对应的十进制找到,然后再转化为二进制。从高到低每8位为一组,可得到:01001000 01100101 01101100 01101100 01101111对应ASCII表格对应的字符即可得到Hello

S        G         V        s                        b          k        8
18       6         21       44                       27         10       60
010010   000110   010101   101100                    011011    000110   111100
01001000  01100101  01101100                         01101100 01101111

应用

● javascript对应的base64编解码方法
在JavaScript 中,可以使用 btoa(binary to ASCII)和 atob(ASCII to binary)方法来做 Base64 的编码和解码。
例如对‘Hello’做 Base64 的编码与解码:
浅谈一下前端字符编码_第6张图片
对于中文的base64编解码,由于ASCII 无法表示中文,因此要先做 UTF-8 编码,然后再做Base64 编码;解码方式为先做 Base64 解码,再做UTF-8 解码:

const encodedData = btoa(encodeURI('你好')); //  "JUU0JUJEJUEwJUU1JUE1JUJE"
const decodedData = decodeURI(atob(encodedData)); // "你好"

浅谈一下前端字符编码_第7张图片

● base64图片地址
通常在图片比较多的情况为了减少http请求,图片地址我们会用base64编码。
前端拿到这个data字符串后,先拼接一下前缀:data:图片类型 ; 编码类型, data字符串数据
data:image/png;base64,iVBORw0KGgoAA…

有两种方式显示图片

  1. css方式-背景图片
img {
    background-image: url(data:image/png;base64,iVBORw0KG......);
}    
  1. img标签方式
<img width="900" height="450" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGMAAAAqCAYAAA...."/>

参考资料

● 聊聊前端字符编码:ASCII、Unicode、Base64、UTF-8、UTF-16、UTF-32-51CTO.COM
● Base64 编码知识,一文打尽!
● 关于编码的那些事——前端应该了解的字符编码_winty~~的博客-CSDN博客
● 前端开发中需要搞懂的字符编码知识_前端的字符和字节_jh035的博客-CSDN博客

你可能感兴趣的:(前端,开发语言,javascript,字符编码)