10、Unicode
渐进增强支持完整的Unicode,包括在字符串中实现新的Unicode迭代,以及在新的正则中使用u模式的Unicode 码位,新API可以在21位码位级别处理字符串。增加支持用JavaScript构建全局应用程序。
1. // 与 ES5.1 一样 2. "".length == 2 3. // 新的正则表达式行为,选择加入“u”模式 4. "".match(/./u)[0].length == 2 5. // 新的处理形式 6. "\u{20BB7}"==""=="\uD842\uDFB7" 7. // 新的字符串操作 8. "".codePointAt(0) == 0x20BB7 9. // 使用 for-of 迭代码位 10. for(var c of "") { 11. console.log(c); 12. }
在Unicode中,码位的总范围为0x0到0x10FFFF,共1,114,112个码位。2048个用于编码代理(UTF-16),66个非字符码位(例如BOM),137,468个预留给私人使用,最终剩余974,530用于普通字符分配。
码位的最大值为0x10FFFF,对应二进制有21位,我们将2^16个值分为一组,则Unicode总共可以分为17份,每一份称之为平面(Plane),每一个平面有65,536(2^16)个码位。
ES6 新的写法,"\u{20bb7}",在新的Unicode码,在javascript中,采用UTF-16的编码方式,最新Unicode标准(v15.0)中一个字符占用的最大的二进制为21位。所以就会出现“u20bb”这种形式,首先我们先计算后4位,0bb7 转换为二进制为 101110110111,分成2部分计算
UTF-16 bit 分布:
标准值 |
UTF-16 |
xxxxxxxxxxxxxxxx |
xxxxxxxxxxxxxxxx |
000uuuuuxxxxxxxxxxxxxxxx |
110110wwwwxxxxxx 110111xxxxxxxxxx |
注:wwww = uuuuu - 1
0x20bb7 - 0x10000 = 1 0000 1011 1011 0111
110111拼接后面10位 = 110111111011011 转换成16进制 ==> DFB7
前面只剩下7位1000010,不够10位,前面补3个0,变成0001 0000 10,再把110110拼接在这个的前面组成 1101 1000 0100 0010 转换成16进制 ==> D842
11、模块
组件定义模块是语言级的支持。 编码方式源于流行的JavaScript模块加载器(AMD,CommonJS)。由主机定义的默认加载程序定义的运行时行为。 隐式异步模型-在请求的模块可用和处理之前,不执行代码。
静态的 import 语句用于导入由另一个模块导出的绑定。
在浏览器中,import 语句只能在声明了 type="module" 的 script 的标签中使用。
1. // lib/math.js 2. export function sum(x, y) { 3. return x + y; 4. } 5. export var pi = 3.141593; // 通过export 进行导出
1. // app.js 2. import * as math from "lib/math"; 3. console.log("2π = " + math.sum(math.pi, math.pi));
1. // other 2. import {sum, pi} from "lib/math"; 3. console.log("2π = " + sum(pi, pi));
一些附加功能包括export default和export *:
1. // lib/mathplusplus.js 2. export * from "lib/math"; 3. export var e = 2.71828182846; 4. export default function(x) { 5. return Math.log(x); 6. }
1. // app.js 2. import ln, {pi, e} from "lib/mathplusplus"; 3. console.log("2π = " + ln(e)*pi*2);
动态 import
标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入。下面的是你可能会需要动态导入的场景。
- 当静态导入的模块很明显的降低了代码的加载速度且被使用的可能性很低,或者并不需要马上使用它。
- 当静态导入的模块很明显的占用了大量系统内存且被使用的可能性很低。
- 当被导入的模块,在加载时并不存在,需要异步获取。
- 当导入模块的说明符,需要动态构建。(静态导入只能使用静态说明符)
- 当被导入的模块有副作用(这里说的副作用,可以理解为模块中会直接运行的代码),这些副作用只有在触发了某些条件才被需要时。(原则上来说,模块不能有副作用,但是很多时候,你无法控制你所依赖的模块的内容)
请不要滥用动态导入(只有在必要情况下采用)。静态框架能更好的初始化依赖,而且更有利于静态分析工具和 tree shaking 发挥作用。
1. import('./mathlib.js') 2. .then(res=> 3. console.log(res) 4. ); 5. let math_module = await import('./mathlib.js');
export(导出)
export 语句用于从模块中导出实时绑定的函数、对象或原始值,以便其他程序可以通过 import 语句使用它们。被导出的绑定值依然可以在本地进行修改。在使用 import 进行导入时,这些绑定值只能被导入模块所读取,但在 export 导出模块中对这些绑定值进行修改,所修改的值也会实时地更新。
1. // 导出单个特性 2. export let name1, name2, …, nameN; // 同样还有 var, const 3. export let name1 = …, name2 = …, …, nameN; // 同样还有 var, const 4. export function FunctionName(){...} 5. export class ClassName {...} 6. // 导出列表 7. export { name1, name2, …, nameN }; 8. // 重命名导出 9. export { variable1 as name1, variable2 as name2, …, nameN }; 10. // 解构导出并重命名 11. export const { name1, name2: bar } = o; 12. // 默认导出 13. export default expression; 14. export default function (…) { … } // 同样还有 class, function* 15. export default function name1(…) { … } // 同样还有 class, function* 16. export { name1 as default, … }; 17. // 导出模块合集 18. export * from …; // does not set the default export 19. export * as name1 from …; // Draft ECMAScript® 2O21 20. export { name1, name2, …, nameN } from …; 21. export { import1 as name1, import2 as name2, …, nameN } from …; 22. export { default } from …;
12、模块加载器(提案)
类似实现的包,可参考(npm i es-module-loader)
模块加载器支持:
- 动态加载
- 状态隔离
- 全局命名空间隔离
- 编译钩子
- 嵌套虚拟化
可以配置默认的模块加载器,并且可以构造新的加载器,以在隔离或受约束的上下文中评估和加载代码。
1. // 动态加载 – ‘System’ 是默认的加载器 2. System.import('lib/math').then(function(m) { 3. console.log("2π = " + m.sum(m.pi, m.pi)); 4. }); 5. // 创建可执行的沙箱 – new Loaders 6. var loader = new loader({ 7. global: fixup(window) // replace ‘console.log’ 8. }); 9. loader.eval("console.log('hello world!');"); 10. // 直接操作模块缓存 11. System.get('jquery'); 12. System.set('jquery', Module({$: $})); //警告:未确定...
13、map、set、weakmap、weaakset
Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值。
1. const map1 = new Map(); 2. map1.set('a', 1); 3. map1.set('b', 2); 4. map1.set('c', 3); 5. console.log(map1.get('a')); // 输出: 1 6. map1.set('a', 97); 7. console.log(map1.get('a')); // 输出:97 8. console.log(map1.size); // 输出:3 9. map1.delete('b'); 10. console.log(map1.size); // 输出:2
Object和Map的比较:
Object 和 Map 类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值。因此(并且也没有其他内建的替代方式了)过去我们一直都把对象当成 Map 使用。
Map |
Object |
|
附带的键 |
Map 默认情况不包含任何键。只包含显式插入的键。 |
一个 Object 有一个原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。 |
键的类型 |
一个 Map 的键可以是任意值,包括函数、对象或任意基本类型。 |
一个 Object 的键必须是一个 String 或是 Symbol。 |
键的顺序 |
Map 中的键是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值 |
虽然 Object 的键目前是有序的,但并不总是这样,而且这个顺序是复杂的。因此,最好不要依赖属性的顺序。 |
统计个数 |
Map 的键值对个数可以轻易地通过 size 属性获取。 |
Object 的键值对个数只能手动计算。 |
迭代 |
Map 是 可迭代的 的,所以可以直接被迭代。 |
Object 没有实现 迭代协议,所以使用 JavaSctipt 的 for...of 表达式并不能直接迭代对象 |
性能 |
在频繁增删键值对的场景下表现更好。在频繁增删键值对的场景下表现更好。 |
在频繁添加和删除键值对的场景下未作出优化。 |
序列化和解析 |
没有元素的序列化和解析的支持。 JSON.stringify和JSON.parse都支持第二个参数,replacer和reviver,可以定义自己的规则实现序列化。 |
原生的由 Object 到 JSON 的序列化支持,使用 JSON.stringify()。 原生的由 JSON 到 Object 的解析支持,使用 JSON.parse()。 |
Set
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set对象是值的集合,你可以按照插入的顺序迭代它的元素。Set 中的元素只会出现一次,即 Set 中的元素是唯一的。
因为 Set 中的值总是唯一的,所以需要判断两个值是否相等。在 ECMAScript 规范的早期版本中,这不是基于和===操作符中使用的算法相同的算法。具体来说,对于 Set,+0(+0 严格相等于 -0)和 -0 是不同的值。然而,在 ECMAScript 2015 规范中这点已被更改。有关详细信息,请参阅浏览器兼容性表中的“Key equality for -0 and 0”。
另外,NaN 和 undefined 都可以被存储在 Set 中,NaN 之间被视为相同的值(NaN 被认为是相同的,尽管 NaN !== NaN)。
1. let mySet = new Set(); 2. 3. mySet.add(1); // Set [ 1 ] 4. mySet.has(1); // true 5. mySet.has(3); // false 6. mySet.size; // 5 7. mySet.delete(1); // true,从 set 中移除 1
WeakMap
WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。
在 JavaScript 里,map API 可以 通过使其四个 API 方法共用两个数组(一个存放键,一个存放值)来实现。给这种 map 设置值时会同时将键和值添加到这两个数组的末尾。从而使得键和值的索引在两个数组中相对应。当从该 map 取值的时候,需要遍历所有的键,然后使用索引从存储值的数组中检索出相应的值。
但这样的实现会有两个很大的缺点:
- 首先赋值和搜索操作都是 O(n) 的时间复杂度(n 是键值对的个数),因为这两个操作都需要遍历全部整个数组来进行匹配。
- 另外一个缺点是可能会导致内存泄漏,因为数组会一直引用着每个键和值。这种引用使得垃圾回收算法不能回收处理他们,即使没有其他任何引用存在了。
相比之下,原生的 WeakMap 持有的是每个键对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行。原生 WeakMap 的结构是特殊且有效的,其用于映射的 key _只有_在其没有被回收时才是有效的。
正由于这样的弱引用,WeakMap 的 key 是不可枚举的(没有方法能给出所有的 key)。如果 key 是可枚举的话,其列表将会受垃圾回收机制的影响,从而得到不确定的结果。因此,如果你想要这种类型对象的 key 值的列表,你应该使用 Map。
1. const wm1 = new WeakMap(), 2. wm2 = new WeakMap(), 3. wm3 = new WeakMap(); 4. const o1 = {}, 5. o2 = function() {}, 6. o3 = window; 7. wm1.set(o1, 37); 8. wm1.set(o2, 'azerty'); 9. wm2.set(o1, o2); // value 可以是任意值,包括一个对象或一个函数 10. wm2.set(o3, undefined); 11. wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个 WeakMap 对象 12. wm1.get(o2); // "azerty" 13. wm2.get(o2); // undefined,wm2 中没有 o2 这个键 14. wm2.get(o3); // undefined,值就是 undefined 15. wm1.has(o2); // true 16. wm2.has(o2); // false 17. wm2.has(o3); // true (即使值是 undefined) 18. wm3.set(o1, 37); 19. wm3.get(o1); // 37 20. wm1.has(o1); // true 21. wm1.delete(o1); 22. wm1.has(o1); // false 23. for (let i of wm1) { 24. console.log(i) 25. }; 26. // caught TypeError: wm1 is not iterable
WeakSet
WeakSet 对象允许你将弱保持对象存储在一个集合中。
WeakSet 对象是一些对象值的集合。且其与 Set 类似,WeakSet 中的每个对象值都只能出现一次。在 WeakSet 的集合中,所有对象都是唯一的。
它和 Set 对象的主要区别有:
- WeakSet 只能是对象的集合,而不能像 Set 那样,可以是任何类型的任意值。
- WeakSet 持弱引用:集合中对象的引用为弱引用。如果没有其他的对 WeakSet 中对象的引用,那么这些对象会被当成垃圾回收掉。
这也意味着 WeakSet 中没有存储当前对象的列表。正因为这样,WeakSet 是不可枚举的。
使用示例:
1. const ws = new WeakSet(); 2. const foo = {}; 3. const bar = {}; 4. ws.add(foo); 5. ws.add(bar); 6. ws.has(foo); // true 7. ws.has(bar); // true 8. ws.delete(foo); // 从 set 中删除 foo 对象 9. ws.has(foo); // false,foo 对象已经被删除了 10. ws.has(bar); // true,bar 依然存在
用例:检测循环引用
1. // 对 传入的 subject 对象 内部存储的所有内容执行回调 2. function execRecursively(fn, subject, _refs = new WeakSet()) { 3. // 避免无限递归 4. if (_refs.has(subject)) { 5. return; 6. } 7. fn(subject); 8. if (typeof subject === "object") { 9. _refs.add(subject); 10. for (const key in subject) { 11. execRecursively(fn, subject[key], _refs); 12. } 13. } 14. } 15. const foo = { 16. foo: "Foo", 17. bar: { 18. bar: "Bar", 19. }, 20. }; 21. foo.bar.baz = foo; // 循环引用! 22. execRecursively((obj) => console.log(obj), foo);
对象的数量或它们的遍历顺序无关紧要,因此,WeakSet 比 Set 更适合(和执行)跟踪对象引用,尤其是在涉及大量对象时。
14、Proxy
Proxy 允许创建具有可用于宿主对象的全部行为范围的对象。 可用于拦截、对象虚拟化、日志记录/分析等。
1. const handler = { 2. get: function(obj, prop) { 3. return prop in obj ? obj[prop] : 37; 4. } 5. }; 6. let target = {} 7. const p = new Proxy(target, handler); 8. p.a = 1; 9. p.b = undefined; 10. 11. console.log(p.a, p.b); // 1, undefined 12. console.log(target.a, target.b); // 1, undefined 13. console.log('c' in p, p.c); // false, 37
这里为了创建一个完整的 traps 列表示例,我们将尝试代理化一个非原生对象,这特别适用于这类操作
1. var handler = 2. { 3. get:..., 4. set:..., 5. has:..., 6. deleteProperty:..., 7. apply:..., 8. construct:..., 9. getOwnPropertyDescriptor:..., 10. defineProperty:..., 11. getPrototypeOf:..., 12. setPrototypeOf:..., 13. enumerate:..., 14. ownKeys:..., 15. preventExtensions:..., 16. isExtensible:... 17. }