js基石之字符ASCII,GBK,Unicode,utf-32,utf-16,utf-8,encodeuri,encodeuricomponent,base64

js基石之数据类型一:类型分类&区别
js基石之数据类型二:类型判断
js基石之数据类型三:类型转换
js基石之Number:本质
js基石之Number:应用(数字运算,数字&字符串转换,不同进制表示&相互转换)
js基石之字符: ASCII,GBK,Unicode,utf-32,utf-16,utf-8,encodeuri,encodeuricomponent,base64

先有问题再有答案

  1. 字符是什么?
  2. 字符'a'在计算机中是如何存在的?
  3. ASCII GBK Unicode 是什么 ?
  4. utf-32 utf-16 utf-8 是做什么的 ?
  5. '𠮷a'.length === 3 // true 为什么
  6. 如何可以使'𠮷a'的长度为2 ?
  7. "👨‍👩‍👧".length === 1 或者 "👨‍👩‍👧".length === 8 ?
  8. encodeuri,encodeuricomponent,base64 有什么用?

字符char

问题1,2 done.

在计算机中,字符是用来表示文本信息的基本单位。因为在计算机这个二进制的世界里 只有0和1,所以每个字符,无论是字母、数字、标点符号还是其他符号,最后都会使用一个数字来标识,这个数字也就是码点,再将码点转换为二进制数字,存储为一个或多个字节。

这个将字符转换为对应二进制数字的过程即为编码, 计算机底层的二进制码转换成屏幕上有意义的字符(如"hello world"),这个过程就称为解码.

js使用UTF-16的编码方式表示一个字符。也就是说一般情况下一个字符占两个字节

字符集

问题3 done

一个字符对应一个数字编号(码点),收录多个字符与码点的集合被称为字符集。字符集在计算机的发展过程中出现了很多。

ASCII: 重要意义

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)字符集是最早的计算机字符编码标准之一,于1963年制定。

在很长一段历史中,计算机仅仅应用在欧洲的一些发达国家,因此在他们的程序中只存在他们所理解的拉丁字母(如a、b、c、d等)和阿拉伯数字,他们在编码解码时也只需要考虑这一种情况,就是如何将这些字符转换成计算机所能理解的二进制数。

每个ASCII编码对应一个特定的字符。例如,大写字母A的ASCII编码是65,小写字母a的ASCII编码是97,数字1的ASCII编码是49。非打印字符,像回车,换行,制表符等,也有对应的ASCII编码。例如,换行符的ASCII编码是10。

ASCII在设计时,也考虑了方便计算机处理的问题。比如大写英文字母和其对应的小写英文字母之间的ASCII值,相差32,所以想要将小写字母转化为大写,或者反之,则只需进行简单的位运算即可。

ASCII 字符集支持 128 种字符,仅使用 7 个 bit 位,也就是一个字节的后 7 位就可以将它们全部表示出来,而最前面的一位统一规定为 0 即可(如 0110 0001 表示 a)。

后来,为了能够表示更多的欧洲国家的常用字符如法语中带符号的字符 é,又制定了 ASCII 额外扩展的版本 EASCII,这里就可以使用一个完整子节的 8 个 bit位表示共 256 个字符,其中就又包括了一些衍生的拉丁字母。

GBK:中国自己的字符集 适合中文环境

当计算机传入中国后,我们的汉字同样需要编码数字化,因为ASCII最多能表示256个字符,对用中国的汉字而言实在是太少了,于是我们就搞了自己的字符集。即GBK字符集

GBK 字符集收录了 2 万多个汉字,以及所有的 ASCII 字符,最重要的是所有 ASCII 字符的代码点在 GBK 字符集和 ASCII 字符集里都是一样的。 GBK 编码采用 2 个字节来表示一个汉字,但为了避免浪费空间,ASCII 字符只会占 1 个字节

解码时如何知道某个字节是在代表一个 ASCII 字符,还是需要再组合一个字节来代表一个汉字呢?GBK 编码采用了一种很巧妙的方式,即 2 字节的字符对应的二进制第一位永远是 1,这样解码的时候,如果遇到 1 开头的字节,就连读 2 个字节按一个字符进行解码,如果遇到 0 开头的字节,这知道这个是 ASCII 字符了,不需要再继续读一个字节放在一起解了。由于这种机制,GBK 实际上无法完全利用 2 的 16 次方 (65536) 大小的空间,因为 2 字节字符的第一位的 1 是固定的,可修改的 bit 只有 15 个,但 3 万多的空间,也足够放下大部分汉字了。

其他字符集

所以问题来了,计算机在中国需要一套编码,在韩国呐?日本呐?印度呐?世界上各国都有不同的编码方式,同一个二进制数字可以被解码成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为了解决这个问题,最终的集大成者 Unicode 字符集出现了。

Unicode:万国码

它成功实现了每个数字代表唯一的至少在某种语言中使用的符号,目前,有一百多万个(从 0x0000000x10FFFF),这么一来即使全世界所有语言的字符都放进来也还绰绰有余,甚至放了很多 emoji 😂 表情字符。

既然可以表示这么多字符,那岂不是每个字符也需要更大的空间,不然岂不是会重复?确实如此,需要 4 个字节来表示。但 Unicode 提供了多种字符编码方式,有些编码方式可以帮我们节省空间。

关于unicode我们需要知道的点有

  1. 收录了世界上的所有语言文字表示的字符,而且目前每年依然可以扩展
  2. 这么多的字符 最多使用32位可以完全覆盖 还有很多很多剩余空间....
  3. 依然兼容ASCII,即 0~127 意义依然不变

编码方式

问题4 done

一般一个字符集对应的编码方式是固定的,但是unicode并不是这样 它支持多种编码方式例如utf-32, utf-16, utf-8. utf原Unicode Transformation Format;

utf-32: 固定长度的编码方式。

每四个字节即32位代表一个字符,这意味着每个字符都使用相同数量的字节来表示。这使得字符的定位和计数变得非常简单,因为你只需要跳过固定数量的字节就可以找到下一个字符。这是 UTF-32 的主要优点。

UTF-32 的主要缺点是它的空间效率较低。由于每个字符都使用 4 个字节,所以对于那些可以使用更少字节表示的字符(例如 ASCII 字符),UTF-32 会浪费大量的空间。此外,由于每个字符都使用 4 个字节,所以 UTF-32 编码的文本在网络传输时可能会比其他编码方式更慢。

utf-16: js使用的编码方式

变长的编码方式, 使用 16 位(即 2 个字节)作为基本单位进行编码.

采用 UTF-16 编码的一个 Unicode 字符可能是 2 字节大小,也可能是 4 字节大小。具体来说,Unicode 中的前 65,536 个字符(也被称为基本多语言平面,包括大多数常用字符)可以使用一个 16 位单元来表示。超出这个范围的字符则需要使用两个 16 位单元(也被称为代理对)来表示。

utf-8: 网络中最常用的方式没有之一

即使是 UTF-16,每个字符最少也依然需要 2 个字节表示,这依然存在空间浪费。于是终极版的 UTF-8 编码它来了.

utf-8变长的编码方式,使用一个字节作为基本单位编码.

需要注意的是:

  1. 一个字节分配给ASCII 所以一个字节是 0xxxxxxx的格式
  2. 多个字节编码规则 开头有几个1表示有几个字节:
    二个字节110xxxxx 10xxxxxx
    三个字节1110xxxx 10xxxxxx 10xxxxxx
    四个字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

对js的影响

获取码点

unicode字符集中每个字符对应的数字编号即为码点,在js中可以通过codePointAt 和 charCodeAt 方法获取对应的码点。

charCodeAt :

这个方法返回字符串中指定位置字符的 UTF-16 码位。如果该字符是基本多语言平面(BMP)的字符(即码位小于或等于 U+FFFF 的字符),那么这个方法返回的就是该字符的 Unicode 码位。但是,如果该字符是辅助平面的字符(即码位大于 U+FFFF 的字符),那么这个方法只会返回该字符的第一个 16 位单元(也就是代理对的高代理项)。

arduino 复制代码
"好".charCodeAt() // 22909 

codePointAt

这个方法返回字符串中指定位置字符的完整 Unicode 码位。无论该字符是基本多语言平面的字符还是辅助平面的字符,这个方法都能正确返回其 Unicode 码位。

perl 复制代码
let str = '𠜎'; // 这个字符的 Unicode 码位是 U+2070E console.log(str.charCodeAt(0)); // 输出 55360,这是 '𠜎' 的高代理项 console.log(str.codePointAt(0)); // 输出 132878,这是 '𠜎' 的正确 Unicode 码位

码元&码点。

问题5 done

因为js使用utf-16的方式来表示一个字符,所以在字符的编码小于65,536的情况下 字符的长度即为1,当字符的编码大于这个长度后 utf-16使用两个16位 所以一个字符的长度会变为2.

arduino 复制代码
'好'.codePointAt() // 码点 22909 < 65536 
'好'.length === 1 // true

'𠮷'.codePointAt() // 码点 134071 > 65536 
'𠮷'.length === 2 // true

在js中2字节的基本单位也被称为码元,表现为字符串的长度为1,js中大部分方法都是使用码元的方式计算长度的。

perl 复制代码
"😄".split(""); //  ['\uD83D', '\uDE04'] 一个字符占据2个码元 因为split按照码元分拆 所以打印了unicode编码

想要正确的访问字符或者打印字符串的长度可以使用码点的方式,迭代器支持按照码点访问问题6 done

ini 复制代码
[..."😄"] // ['😄']
[..."😄"].length === 1 // true
[...'𠮷'].length === 1 // true

字素簇(grapheme cluster)& 零宽度连接符(ZWJ)

问题7 done

Unicode 除了使用单个码点表示 Emoji,还允许多个码点组合表示一个 Emoji。

其中的一种方式是"零宽度连接符"(ZERO WIDTH JOINER,缩写 ZWJ)U+200D。举例来说,下面是三个 Emoji 的码点。

  • U+1F468:男人
  • U+1F469:女人
  • U+1F467:女孩

上面三个码点使用U+200D连接起来,U+1F468 U+200D U+1F469 U+200D U+1F467,就会显示为一个 Emoji 👨‍👩‍👧,表示他们组成的家庭。如果用户的系统不支持这种方法,就还是显示为三个独立的 Emoji 👨👩👧。

javascript 复制代码
// 将多个unicode字符通过ZWJ连接一起
function joinWithZWJ(items) {
    // 将每个元素转换为字符
    let chars = items.map(item => {
        if (typeof item === 'string') {
            return item;
        } else if (typeof item === 'number') {
            return String.fromCodePoint(item);
        } else {
            throw new Error('Invalid input');
        }
    });

    // 使用零宽度连接符将字符连接起来
    let result = chars.join('\u200D');

    return result;
}

const family = joinWithZWJ(["👨","👩","👧"]) // family = "👨‍👩‍👧"

"👨‍👩‍👧".length === 8 // true length按照码元计算长度
[..."👨‍👩‍👧"].map(item=> `${item}`.codePointAt()) //  [128104, 8205, 128105, 8205, 128103]
// 8  =  3个码点 * 2 + 2(ZWJ)

// 正则判断输入的字符是否为通过zwj连接的
/\u{200D}/u.test('a👨‍👩‍👧') // true

前端常用的编码方式

问题8 done

encodeuri: 编码uri

  1. 特殊字符:URL 中有一些字符有特殊的含义,如问号(?)用于开始查询字符串,井号(#)用于开始片段标识符,斜杠(/)用于分隔路径的各个部分。如果你需要在 URL 的数据部分使用这些字符,你需要对它们进行编码,否则它们可能会被误解为它们的特殊含义。
  2. 非 ASCII 字符URL 必须是 ASCII 字符集的一个子集,这意味着所有的非 ASCII 字符 在 URL 中使用时都需要进行编码。
  3. 空格:URL 中不能直接包含空格,否则可能会导致错误。你需要使用 encodeURI 来将空格编码为 %20。
  4. 安全性:对 URL 进行编码可以防止注入攻击,因为它可以确保数据被正确地解析,而不是被误解为代码。
javascript 复制代码
encodeURI('https://example.com/search?q=你好/世界')
// 'https://example.com/search?q=%E4%BD%A0%E5%A5%BD/%E4%B8%96%E7%95%8C'

encodeuri的编码方式为% + urf-8的数字

encodeuricomponent: 编码uri参数

uri中特殊含义的字符也会被编码 所以不能用encodeuricomponent编码完整的uri

javascript 复制代码
`https://example.com/search?q=${encodeURIComponent('你好/世界')}`
// 'https://example.com/search?q=%E4%BD%A0%E5%A5%BD%2F%E4%B8%96%E7%95%8C'

base64: 编码ascii字符

btoa:将ascii字符串或二进制数据转换成一个base64编码过的字符串,该方法不能直接作用于Unicode字符串。 atob:将已经被base64编码过的数据进行解码。

csharp 复制代码
btoa('aa') // 'YWE='
btoa('世界') // 直接编码中文 报差错
VM520:1 Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.
    at <anonymous>:1:1

utf8字符串base64编码与解码:

scss 复制代码
function uft8ToBase64(utf8) {
return btoa(encodeURIComponent(utf8));
}
function base64ToUtf8(base64) {
return decodeURIComponent(atob(base64));
}
var base64 = uft8ToBase64("hello {世界} ");
base64ToUtf8(base64); // 'hello {世界} '

参考

  1. ruanyifeng_base64
  2. ruanyifeng_emoji
  3. full-emoji-list
  4. 一次性搞懂字符集和字符编码(ASCII、GBK、Unicode、UTF-32、UTF-16、UTF-8)
  5. 深入理解字符编码(ASCII、Unicode、UTF-8、UTF-16、UTF-32)
相关推荐
程序员爱技术5 分钟前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
并不会1 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
悦涵仙子1 小时前
CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
javascript·css·sass
衣乌安、1 小时前
【CSS】居中样式
前端·css·css3
兔老大的胡萝卜1 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
低代码布道师1 小时前
CSS的三个重点
前端·css
耶啵奶膘3 小时前
uniapp-是否删除
linux·前端·uni-app
王哈哈^_^4 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie5 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic5 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js