[Effective JavaScript 笔记] 第7条:视字符串为16位的代码单元序列

Unicode编码,基础:它为世界上所有的文字系统的每个字符单位分配一个唯一的整数,该整数介于0~1114111之间,在Unicode术语中称为代码点(code point)。

和其它字符编码几乎没有任何不同(例如ASCII)。

不同在于ASCII将每个索引映射为唯一的二进制表示,但Unicode允许多个不同二进制编码的代码点。

不同的编码在要求存储的字符串数量和操作速度之间进行权衡。

目前最流行的Unicode编码方式有:UTF-8,UTF-16,UTF-32

Unicode根据历史的数据,错误的估算了代码点的容易范围。

最初,认为只需要216代码点,所以产生了UCS-2,其为16位编码的原始标准。一个代码点可以容纳16位的数字,简单的方法是将代码点与其编码元素一对一地映射起来,这称为一个代码单元(code unit)。

UCS-2是由独立的16位的代码单元组成,每个代码单元对应一个单独的Unicode代码点。这种编码方法的主要好处在于索引字符串是一种代码小的,固定时间的操作。获取某个字符串的第n个代码点只是简单简单地选取数组的第n个16位元素。

下面示例:

这个字符串,里面的每个字符由最初的16位范围中的代码点组成。对于Unicode字符串,代码点和编码元素能完全匹配

JS字符串采用这16位编码每一个元素。如果还保持20世纪90年代初的做法,JS字符串的每个元素还是对应一个单独的代码点。

Unicode从216扩展到了220个代码点。新增加后的范围被组织为17个大小为216代码点的子范围。

第一个子范围,称为基本多文种平面,包含最初的216个代码点。余下的16个范围称为辅助平面

一旦代码点的范围扩展了,UCS-2就变得过时了。它需要通过扩展来表示这些附加的代码点。基替代者UTF-16和它类似。

UTF-16采用代理对表示附加的代码点。一对16位的代码单元共同编码一个等于或大于216的代码点。(有点乱,是这样吧。一个代理对等于两个16位的代码单元。一个代码单元是代码点与其编码元素一对一地映射。)

举个例子:

高音谱号“”的代码点为U+1D11E(代码点数119070的Unicode的惯用16进制写法)

其由UTF-16格式的代码单元0xd834和0xddle共同表示。可以通过合并这两个代码单元选择的位来对代码点进行解码。(这种编码保证了这些代理对绝不会与有效的BMP代码点混淆,甚至从字符串中间的某个位置进行搜索,也可以随时识别一个代理对。)

由于UTF-16的每个代码点编码需要一个或两个16位的代码单元,因此UTF-16是一种可变长度的编码

  • 长度为n的字符串在内存中的大小变化基于字符串特定的代码点。

  • 查找字符串中的第n个代码点不再是一个固定时间的操作。
  • 搜索需要从字符串的开始进行。

当Unicode扩大规模时,JS已经采用了16位的字符串元素。字符串属性和方法都是基于代码单元层级,而不是代码点层级。

所以每当字符串包含辅助平面中的代码点时,js将每个代码点表示为两个元素而不是一个(一对UTF-16代理对的代码点)

一个js字符串的元素是一个16位的代码单元。

提取字符串的某个字符得到的是代码单元,而不是代码点。

正则表达式也工作于代码单元层级。其单字符模式(“.”)匹配一个单一的代码单元。

JS内置的字符串数据类型工作于代码单元层级,但这并不能阻止一些API意识到代码点和代理对。一些标准的ECMAScript库正确地处理了代理对。

URI操作函数:sendcodeURI,decodeURI,encodeURIComponent和decodeURIComponent。

提示

  • js字符串是由16位的代码单元组成,而不是由Unicode代码点组成。

  • js使用两个代码单元表示216及其以上的Unicode代码点。这两个代码单元被称为代理对。
  • 代理对甩开了字符串元素计数,length,charAt,charCodeAt方法及正则表达式模式受到了影响。
  • 使用第三方的库编写可识别代码点的字符串操作。
  • 每当使用一个含有字符串操作的库时,都需要查阅该库文档,看好像处理代码点的整个范围。

后记

这节看得我很蒙B,整不太懂,我平时的工作环境中,也没有遇到过这方面的BUG。

页面编码是utf-8或gbk的,是否可以不用考虑上面说的?

现在只是知道在内存的存储过程中,每种情况对应的存储方式。

再想进一步的,可以自己再去网上找资料看了。

进一步阅读

网上找了几个写这方面的文章,有兴趣自己去读吧。

  1. 简单几句话总结Unicode,UTF-8和UTF-16

  2. Unicode(UTF-8, UTF-16)令人混淆的概念

  3. 为什么 UTF-8 编码比 UTF-16 编码应用更广泛?
  4. UTF-8 GBK UTF8 GB2312 之间的区别和关系

时间: 2024-11-06 07:20:37

[Effective JavaScript 笔记] 第7条:视字符串为16位的代码单元序列的相关文章

[Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式传递给eval函数以达到同样的功能.程序员面临一个选择:应该将代码表示为函数还是字符串?毫无疑问,应该将代码表示为函数.字符串表示代码不够灵活的一个重要原因是:它们不是闭包. 闭包回顾 看下面这个图 js的函数值包含了比调用它们时执行所需要的代码还要多的信息.而且js函数值还在内部存储它们可能会引用

[Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+1}" 反射获取函数源代码的功能很强大,使用函数对象的toString方法有严重的局限性.toString方法的局限性ECMAScript标准对函数对象的toString方法的返回结果(即该字符串)并没有任何要求.这意味着不同的js引擎将产生不同的字符串,甚至产生的字符串与该函数并不相关. 如果函数

[Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //"number" typeof "s";//"string" typeof null;//"object":ECMAScript把null描述为独特的类型,但返回值却是对象类型,有点困惑. 可以使用Object.prototype.t

[Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合

对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置不同的属性与顺序无关,都会以大致相同的效率产生相同的结果.也就是说访问属性a和访问属性b,没有哪个访问更快之说.ES标准并未规定属性存储的任何特定顺序,甚至于枚举对象也未涉及.for...in循环会挑选一定的顺序来枚举对象的属性,标准允许js引擎自由选择一个顺序,它们的选择会微妙地改变程序行为.如要

[Effective JavaScript 笔记]第60条:支持方法链

无状态的API的部分能力是将复杂操作分解为更小的操作的灵活性.一个很好的例子是字符串的replace方法.由于结果本身也是字符串,可以对前一个replace操作重复执行替换.这种模式的一个常见用例是在将字符串插入到HTML前替换字符串的特殊字符字母. function escapeBasicHTML(str){ return str.replace(/&/g,"&") .replace(/< /g,"<") .replace(/>/

[Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑

构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.txt',function(file){ console.log('file:'+file); }); 基于promise的API不接收回调函数作为参数.相反,它返回一个promise对象,该对象通过其自身的then方法接收回调函数. var p=downloadP('file.txt'); p.th

[Effective JavaScript 笔记]第51条:在类数组对象上复用通用的数组方法

前面有几条都讲过关于Array.prototype的标准方法.这些标准方法被设计成其他对象可复用的方法,即使这些对象并没有继承Array. arguments对象 在22条中提到的函数arguments对象.它是一个类数组对象,并不是一个标准的数组,所以无法使用数组原型中的方法,因此无法使用arguments.forEach这样的形式来遍历每一个参数.这里我们必须使用call方法来对使用forEach方法. function highlight(){ [].forEach.call(argume

[Effective JavaScript 笔记]第15条:当心局部块函数声明笨拙的作用域

嵌套函数声明.没有标准的方法在局部块里声明函数,但可以在另一个函数的顶部嵌套函数声明. function f(){return "global"} function test(x){ var result=[]; function f(){return "local";}//block-local if(x){ result.push(f()); } result.push(f()); return result; } test(true);//["loc

[Effective JavaScript 笔记]第58条:区分数组对象和类数组对象

示例 设想有两个不同类的API.第一个是位向量:有序的位集合 var bits=new BitVector(); bits.enable(4); bits.enable([1,3,8,17]); bits.bitAt(4);//1 bits.bitAt(8);//1 bits.bitAt(9);//0 enable方法被重载了,可以传入一个索引或索引的数组.第二个类的API是字符串集合:无序的字符串集合 var set=new StringSet(); set.add('Hamlet'); se