这一篇阐述前端与编码相关的内容
####一、HTML中的meta标签
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body></body>
</html>
这个标签对于前端来说太熟悉了,也都知道这个表示让浏览器使用UTF-8解码文档。但真总是这样吗? ######1.Content-Type 其实浏览器获取页面的本身也是发送请求,如果是请求服务器大概率都会返回Content-Type这个属性,里面就包含了编码类型,那么浏览器会根据这个编码进行解码。并且忽视文档本身的,因为 在浏览器解码操作中,Content-Type优先级大于
######2. 如果服务器没有返回Content-Type这个属性,那浏览器只能找 ,但是如果浏览器是根据 来解析文本的话,那么"utf-8"本身也被编码了啊,浏览器是如何获取"utf-8" 的呢?是不是很奇怪。
这样的,因为大部分的主流编码都兼容了ASCII,并且我们的HTML文件前几行内容都是字母、符号、数字,所以浏览器可以直接吧body标签之前的部分按照ASCII解码,直到解码到时,得到xxx,可以确定编码方式了,然后再从文档开始部分,(一般为)进行整个文档的解码。
######3.没有Content-Type,也没有 如果服务端既没有返回Content-Type,文档内部也没有标明,这时候浏览器就真的是瞎猜了,他会根据一系类的算法进行匹配,以找出编码方式,因为每个编码都有自己的编码特色吗,但是你这个过程难免出错,尤其是你的文档并不是国际通用规范的编码时候。所有,我们还是老老实实把 定义出来吧,而且最好排在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的历史版本文档中,找到蛛丝马迹。
####四、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编辑器) 之所以乱码,是因为我设置当前文件的编码为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这个属性,并且没有声明,只要你是用包含汉字的编码格式(例如: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进制 - A</h2>
<h2>HTML16进制 - A</h2>
<h3>JavaScript - <span id="js"></span></h3>
<script>
document.getElementById('js').innerHTML = '\u0041';
</script>
</body>
#####这里其实重要的要说我们常用的字体图标 先来认识下常用的font-family属性
页面中的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">A</span>
<span class="icon iconfont">B</span>
<span class="icon iconfont">C</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)。
私人使用区字符的分配,可不由字面意义上的私人决定;一些组织已经发布了一些分配计划。但根据其定义,私人使用区相同的代码点可被分配为不同的字符,因此用户可能因安装了某种字体,看到其显示为一种形态,但使用了其他字体的用户可能看到完全不同的字符。
我下载的的码点不在这个区域,是因为我要演示效果,把编码手动修改了。 这回我再改回来。。
<div>
<span class="icon iconfont"></span>
<span class="icon iconfont"></span>
<span class="icon iconfont"></span>
</div>
好了,这样就不会影响到谁了,搜索引擎不会抓取没有意义的字符的。
###至此,Unicode编码相关的东西就告一段落,但这不是结束,而仅仅是开始。