JS基础-字符-Unicode编码(下)

这一篇阐述前端与编码相关的内容

一、HTML中的meta标签

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
</head>
<body></body>
</html>

这个标签对于前端来说太熟悉了,也都知道这个<meta charset="UTF-8">表示让浏览器使用UTF-8解码文档。但真总是这样吗?

1.Content-Type

其实浏览器获取页面的本身也是发送请求,如果是请求服务器大概率都会返回Content-Type这个属性,里面就包含了编码类型,那么浏览器会根据这个编码进行解码。并且忽视文档本身的<meta charset="xxx">,因为 在浏览器解码操作中,Content-Type优先级大于<meta charset="xxx">

加载我的简书首页的请求

2.<meta charset="xxx">

如果服务器没有返回Content-Type这个属性,那浏览器只能找 <meta charset="xxx">,但是如果浏览器是根据 <meta charset="utf-8">来解析文本的话,那么"utf-8"本身也被编码了啊,浏览器是如何获取"utf-8" 的呢?是不是很奇怪。

这样的,因为大部分的主流编码都兼容了ASCII,并且我们的HTML文件前几行内容都是字母、符号、数字,所以浏览器可以直接吧body标签之前的部分按照ASCII解码,直到解码到<meta charset="xxx">时,得到xxx,可以确定编码方式了,然后再从文档开始部分,(一般为<!DOCTYPE html>)进行整个文档的解码。

3.没有Content-Type,也没有<meta charset="xxx">

如果服务端既没有返回Content-Type,文档内部也没有标明<meta charset="xxx">,这时候浏览器就真的是瞎猜了,他会根据一系类的算法进行匹配,以找出编码方式,因为每个编码都有自己的编码特色吗,但是你这个过程难免出错,尤其是你的文档并不是国际通用规范的编码时候。所有,我们还是老老实实把 <meta charset="UTF-8">定义出来吧,而且最好排在head标签中第一个位置。以便浏览器在没有Content-Type情况下尽快检索出编码方式。就像这样

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

二、script标签中的charset属性

由于现代浏览器纠错功能太强,所以,只要你的JS文件是使用UTF-8编码的,并且浏览器解析文档也是UTF-8的,无论charset属性怎么设置,都能正常解析。

MDN推荐我们:最好就是不去定义这个属性,因为他会继承HTML文档的编码格式。
W3school告诉我们:默认的字符编码是 ISO-8859-1。

这个问题不重要,不要过分纠结。

三、javascript的编码到底是 UCS-2 还是 UTF-16

这个问题问的在准确一点,应该是
javascript的字符串的存储操作,存到内存的编码到底是 UCS-2 还是 UTF-16

可以说,在js中,字符集编码是针对字符串这种数据结构的。

javascript到底是 UCS-2 还是 UTF-16 这个问题,我查阅很多资料说法不一,不过我个人认为最靠谱是:

在老版浏览器中,只支持UCS-2。当UTF-16发布出来后,根据浏览器对JS解释器的实现不同,可能使用 UCS-2,也有可能使用UTF-16。
现代浏览器(支持ES6的)全部都是UTF-16。
可以在ECMAScript的历史版本文档中,找到蛛丝马迹。

ECMAScript

四、javascript的解释器的存储到内存的编码为UTF-16,我们保存的javascript文件为UTF-8,不会造成乱码吗?

不会,这个过程是,浏览器拿到服务器的HTML文件,这个文件是一堆字符串,浏览器把这些字符串解析成DOM节点,当解析到script标签时,把script的文件内容移交给JS编译器单元,对于编译器来说,这些文件内容也是一堆字符串而已,不然也就不会有词法和语法分析了,词法和语法分析的任务就是把这些javascript文件内的字符编译成解释器能执行的代码,然后在解释器执行代码时,数据才以UTF-16编码格式进行存储,或者被取出操作取出数据。

总之,这个过程对于开发者是不可见的,所以我们一般不会在意。

我们能感知的部分就是字符串的相关操作,比如length属性,2字节编码的返回1,4字节编码的返回2。
但这其实并不符合逻辑,明明显示的是一个字符,长度怎么还分成1和2呢?这说不通吗,所以很多初学者对于这个现象很是费解。不单是length属性,其他字符相关的方法也存在问题

// Unicode编码为 16进制:1f60d;10进制:128525
let str = "😍"
console.log(str.length) // 2
console.log(str.charCodeAt()) // 55357
console.log(String.fromCharCode(128525)) // ""
for(let i = 0;i<str.length;i++){
  console.log(str.charAt(i)) //��
}
console.log("😍".split('')) // ["�", "�"]
一个都不对

不过ES6已经为我们提供了替代方法了

let str = "😍"
console.log(Array.from(str).length) // 1
console.log(str.codePointAt()) // 128525
console.log(String.fromCodePoint(128525)) // "😍"
for (let s of str ){
  console.log(s) //"😍"
}

不过ES6并没有定义与charAt()方法对应的针对辅助平面的方法,好在有替补方案

let str = "😍"
for (let i = 0; i < Array.from(str).length; i++) {
  console.log(fixedCharAt(str, i))  //"😍"
}
function fixedCharAt(str, idx) {
  var ret = '';
  str += '';
  var end = str.length;

  var surrogatePairs = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
  while ((surrogatePairs.exec(str)) != null) {
    var li = surrogatePairs.lastIndex;
    if (li - 2 < idx) {
      idx++;
    } else {
      break;
    }
  }

  if (idx >= end || idx < 0) {
    return '';
  }

  ret += str.charAt(idx);

  if (/[\uD800-\uDBFF]/.test(ret) && /[\uDC00-\uDFFF]/.test(str.charAt(idx + 1))) {
    // Go one further, since one of the "characters" is part of a surrogate pair
    ret += str.charAt(idx + 1);
  }
  return ret;
}

或者使用

npm i string.prototype.at

js为什么不用UTF-8编码?

我个人认为,UTF-8作为大势所趋,不是想不想的问题,而是必须要改,今天我把话放这,以后再来挖坟。
但是现在还不能,原因以下几点:

其一,历史包袱,这是最主要的原因。js从UCS-2到UTF-16的过渡比较平滑,比如之间按照UCS-2编码的老网站,依然正常运行,或者js代码会使用length去判断字符串的长度,使用UTF-16之后,这些代码也不需要改动,返回值和之前一样,因为已经说过UTF-16是UCS-2的超级,UTF-16和UCS-2的关系就像是ES6和ES5的关系一样,在ES6中使用ES5完全没问题。所以浏览器的解释器使用UTF-16替换UCS-2的影响不是很大。
改成UTF-8呢?再拿字符串length这个属性为例,UTF-16编码可以直接2字节返回1,4字节返回2,你用了UTF-8如果还要这样返回,js解释器该怎么修改才合适?ES6的关于字符串的修改其实就是在下一步大棋,那就是为JavaScript改为UTF-8编码做准备,因为好多属性都是根据字符本身返回长度,而不是字节数,这就少了很多顾虑。就看ES6什么时候能普及。

其二,UTF-16更适合数据存储,因为相比UTF-8, 它有利于CPU的操作,有利于计算,如排序和索引,但是在一个文件基本都是ASCII码表内容时,UTF-8比UTF-16更节省空间。但是相比这点空间,还是CPU的操作、计算、排序和索引重要一些。

其三,就是浏览器的生产商乐不乐意改,虽然我不太了解js解释器的内部架构是啥样的,但是我敢肯定,修改编码方式对于浏览器本身来说绝对是伤筋动骨的操作,ECMA委员会成员一大部分都来自这些浏览器厂商,让他们束手就擒应该不是短期能办到的事。不过这就是小问题了。

解决以上问题,JavaScript编码应该就能改革了。

五、前端页面有时候乱码是怎么回事?

修改一下当前文件的编码方式(VSCode编辑器)



乱码.png

之所以乱码,是因为我设置当前文件的编码为GBK,但是浏览器根据


的值,使用utf-8进行了解码,所以就是乱码。
总之乱码就是你编码用的A,你解码却用了B,那么A和B的交集部分能正常显示,交集之外的就是乱码。找了一张乱码对照表,但是现在由于UTF-8的普及,很少会有乱码的出现了。


锟斤拷乱码出现原因解析

源于 GBK 字符集和 Unicode 字符集之间的转换问题。Unicode 和老编码体系的转化过程中,肯定有一些字,用 Unicode 是没法表示的,Unicode 官方用了一个占位符来表示这些文字,这就是:U+FFFD REPLACEMENT CHARACTER。那么 U+FFFD 的 UTF-8 编码出来,恰好是 \xef\xbf\xbd。如果这个 \xef\xbf\xbd,重复多次,例如 \xef\xbf\xbd\xef\xbf\xbd,然后放到 GBK/CP936/GB2312/GB18030 的环境中显示的话,一个汉字 2 个字节,最终的结果就是:锟斤拷——锟(0xEFBF),斤(0xBDEF),拷(0xBFBD)。
出自https://moechu.cn/2020/03/16/405.html

其他:
如果你的html文件中包含汉字,服务器没有返回Content-Type这个属性,并且没有声明<meta charset="xxx">,只要你是用包含汉字的编码格式(例如:utf-8,utf-16,utf-32,GBK,GB2312,UCS-2....)进行保存,现代浏览器都能正常解码,因为它们本身带有编码纠错功能,但不推荐这么做!!!
但是如果你使用不包含汉字的编码格式(例:ASCII,ISO-8859-1...)进行保存,浏览器的纠错功能也回天乏术了。

六、HTML,CSS,JS使用Unicode编码的方式

<head>
  <meta charset="UTF-8">
  <style>
    .css:after {
      content: "\0041";
    }
  </style>
</head>
<body>
  <h1 class="css">CSS- </h1>
  <!-- HTML十进制: &#; -->
  <!-- HTML十六进制: &#x; -->
  <h2>HTML10进制 - &#65;</h2>
  <h2>HTML16进制 - &#x41;</h2>
  <h3>JavaScript - <span id="js"></span></h3>
  <script>
    document.getElementById('js').innerHTML = '\u0041';
  </script>
</body>
这里其实重要的要说我们常用的字体图标

先来认识下常用的font-family属性


下载到本的新字体,TTF格式
页面显示效果

页面中的font-family属性,浏览器会选择列表中第一个该计算机上有安装的字体,或者是通过 @font-face 指定的可以直接下载的字体。
所以一般我们都会在font-family属性上定义多个字体,以防止计算机匹配不到。

通过工具解析字体文件内容,查看字体自负与Unicode编码的联系。
看看字体的编码
然后再通过解析三种字体对比查看。
font-family => 计算机本地安装的字体文件 => 具体显示的字符样式,这其中的映射关系就很明显了。

我们使用的提提图标一般都会使用@font-face
那么下载字体图标文件,比如阿里妈妈字体图标,我们可以获得如下示例代码。

@font-face {font-family: "iconfont";
  src: url('iconfont.eot?t=1594826325393'); /* IE9 */
  src: url('iconfont.eot?t=1594826325393#iefix') format('embedded-opentype'), /* IE6-IE8 */
  url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAA.......AA==') format('woff2'),
  url('iconfont.woff?t=1594826325393') format('woff'),
  url('iconfont.ttf?t=1594826325393') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
  url('iconfont.svg?t=1594826325393#iconfont') format('svg'); /* iOS 4.1- */
}

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <title>IconFont Demo</title>
  <link rel="stylesheet" href="iconfont.css">
  <style>
    .icon{font-size: 40px !important;}
  </style>
</head>
<body>
  <div>
    <span class="icon iconfont">&#x41;</span>
    <span class="icon iconfont">&#x42;</span>
    <span class="icon iconfont">&#x43;</span>
  </div>
  <script>
  </script>
</body>
</html>

这是我随便找的三个字体图标,虽然在页面上显示了图形,但控制台的document文档中依然是ABC,这是因为浏览器解析document文档时可不会等你的字体文件加载完,它也不需要,无论是本地的字体文件,还是网络的字体文件。他会以自己的方式根据Unicode编码表显示字符,这个显示的字符都是固定的一种字体的,因为不会按照你的dom的font-family去解析字体,这也是为什么控制台document中的字体永远都是那样的原因。

但是这样就有一个问题,搜索引擎可是会查看这个document文档内容的,让搜索引擎抓取到这些不需要的字符没有必要。所以一般你在阿里妈妈字体库里添加的图标,它们的码点默认都是再Unicode的第一个基础平面私人区域,这块区域码点为E000-F8FF。

在Unicode中,私人使用区(简称:私用区;英语:Private Use Areas,简称:PUA)指其解释未在Unicode标准中指定,而是由合作用户之间的私人协议决定其用途的一系列码位。目前定义了三个私人使用区:一个在基本多语言平面(U+E000-U+F8FF)中,另外两个几乎包含了整个第15和第16平面(分别为U+F0000-U+FFFFD,U+100000-U+10FFFD)。

私人使用区字符的分配,可不由字面意义上的私人决定;一些组织已经发布了一些分配计划。但根据其定义,私人使用区相同的代码点可被分配为不同的字符,因此用户可能因安装了某种字体,看到其显示为一种形态,但使用了其他字体的用户可能看到完全不同的字符。


绿色为私人区域,还有16,17平面也是私人区域,可在Unicode编码(中)查看

我下载的的码点不在这个区域,是因为我要演示效果,把编码手动修改了。
这回我再改回来。。


<div>
    <span class="icon iconfont">&#xe69c;</span>
    <span class="icon iconfont">&#xe69d;</span>
    <span class="icon iconfont">&#xe69e;</span>
  </div>
我改回来之后的效果

好了,这样就不会影响到谁了,搜索引擎不会抓取没有意义的字符的。

至此,Unicode编码相关的东西就告一段落,但这不是结束,而仅仅是开始。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341