这一篇阐述前端与编码相关的内容
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> </head> <body></body> </html>
这个标签对于前端来说太熟悉了,也都知道这个表示让浏览器使用UTF-8解码文档。但真总是这样吗?
其实浏览器获取页面的本身也是发送请求,如果是请求服务器大概率都会返回Content-Type这个属性,里面就包含了编码类型,那么浏览器会根据这个编码进行解码。并且忽视文档本身的,因为 在浏览器解码操作中,Content-Type 优先级大于<meta charset="xxx">
<meta charset="xxx">
如果服务器没有返回Content-Type这个属性,那浏览器只能找 <meta charset="xxx">
,但是如果浏览器是根据 <meta charset="utf-8">
来解析文本的话,那么ta本身也被编码了啊,浏览器是如何获取"utf-8" 的呢?是不是很奇怪。
这样的,因为大部分的主流编码都兼容了ASCII,并且我们的HTML文件前几行内容都是字母、符号、数字,所以浏览器可以直接吧body标签之前的部分按照ASCII解码,直到解码到时,得到xxx,可以确定编码方式了,然后再从文档开始部分,(一般为)进行整个文档的解码。
<meta charset="xxx">
如果服务端既没有返回Content-Type,文档内部也没有标明<meta charset="xxx">
,这时候浏览器就真的是瞎猜了,他会根据一系类的算法进行匹配,以找出编码方式,因为每个编码都有自己的编码特色吗,但是你这个过程难免出错,尤其是你的文档并不是国际通用规范的编码时候。所有,我们还是老老实实把 <meta charset="UTF-8">
定义出来吧,而且最好排在head标签中第一个位置。以便浏览器在没有Content-Type情况下尽快检索出编码方式。就像这样
html<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head>
由于现代浏览器纠错功能太强,所以,只要你的JS文件是使用UTF-8编码的,并且浏览器解析文档也是UTF-8的,无论charset属性怎么设置,都能正常解析。
MDN推荐我们:最好就是不去定义这个属性,因为他会继承HTML文档的编码格式。
W3school告诉我们:默认的字符编码是 ISO-8859-1。
这个问题不重要,不要过分纠结。
这个问题问的在准确一点,应该是
javascript的字符串的存储操作,存到内存的编码到底是 UCS-2 还是 UTF-16
可以说,在js中,字符集编码是针对字符串这种数据结构的。
javascript到底是 UCS-2 还是
UTF-16 这个问题,我查阅很多资料说法不一,不过我个人认为最靠谱是:
在老版浏览器中,只支持UCS-2。当UTF-16发布出来后,根据浏览器对JS解释器的实现不同,可能使用 UCS-2,也有可能使用UTF-16。
现代浏览器(支持ES6的)全部都是UTF-16。
可以在ECMAScript的历史版本文档中,找到蛛丝马迹。
存储到内存的编码为UTF-16,我们保存的javascript文件为UTF-8,不会造成乱码吗?
不会,这个过程是,浏览器拿到服务器的HTML文件,这个文件是一堆字符串,浏览器把这些字符串解析成DOM节点,当解析到script标签时,把script的文件内容移交给JS编译器单元,对于编译器来说,这些文件内容也是一堆字符串而已,不然也就不会有词法和语法分析了,词法和语法分析的任务就是把这些javascript文件内的字符编译成解释器能执行的代码,然后在解释器执行
代码时,数据才以UTF-16编码格式进行存储,或者被取出操作取出数据。
总之,这个过程对于开发者是不可见的,所以我们一般不会在意。
我们能感知的部分就是字符串的相关操作,比如length属性,2字节编码的返回1,4字节编码的返回2。
明明显示的是一个字符,长度怎么还分成1和2呢?这说不通吗,所以很多初学者对于这个现象很是费解。不单是length属性,其他字符相关的方法也存在问题
js// 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已经为我们提供了替代方法了
jslet 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()方法对应的针对辅助平面的方法,好在有替补方案
jslet 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; }
或者使用
bashnpm i string.prototype.at
我个人认为,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-8, 它有利于CPU的操作,有利于计算,如排序和索引,但是在一个文件基本都是ASCII
码表内容时,UTF-8比UTF-16更节省空间。但是相比这点空间,还是CPU的操作、计算、排序和索引重要一些。
虽然我不太了解js解释器的内部架构是啥样的,但是我敢肯定,修改编码方式对于浏览器本身来说绝对
是伤筋动骨的操作,ECMA委员会成员一大部分都来自这些浏览器厂商,让他们束手就擒应该不是短期能办到的事。
修改一下当前文件的编码方式(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<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属性上定义多个字体,以防止计算机匹配不到。
font-family => 计算机本地安装的字体文件 => 具体显示的字符样式,这其中的映射关系就很明显了。
我们使用的提提图标一般都会使用@font-face
那么下载字体图标文件,比如阿里妈妈字体图标,我们可以获得如下示例代码。
css@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; }
html<!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)。
私人使用区字符的分配,可不由字面意义上的私人决定;一些组织已经发布了一些分配计划。但根据其定义,私人使用区相同的代码点可被分配为不同的字符,因此用户可能因安装了某种字体,看到其显示为一种形态,但使用了其他字体的用户可能看到完全不同的字符。
绿色为私人区域,还有16,17平面也是私人区域,可在Unicode编码(中)查看
我下载的的码点不在这个区域,是因为我要演示效果,把编码手动修改了。
这回我再改回来。。
html<div> <span class="icon iconfont"></span> <span class="icon iconfont"></span> <span class="icon iconfont"></span> </div>
我改回来之后的效果
好了,这样就不会影响到谁了,搜索引擎不会抓取没有意义的字符的。