美文网首页
JS基础-字符-Unicode编码(下)

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

作者: 火锅伯南克 | 来源:发表于2020-07-17 12:51 被阅读0次

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

    一、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

    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编码相关的东西就告一段落,但这不是结束,而仅仅是开始。

    相关文章

      网友评论

          本文标题:JS基础-字符-Unicode编码(下)

          本文链接:https://www.haomeiwen.com/subject/pwugcktx.html