预计在2014年底,ECMAScript 6将会正式发布,他的草案在13年3月份被冻结,后续提出新特性将会移至ECMASript 7中。目前还没有哪款浏览器实现了ES6的全部内容,兼容性最强的一款要数FireFox了。具体情况可以在这里查看。
关于 ECMAScript 6 草案,我在博客里头复制了一份,可以点击这里。
JavaScript的内容是越来越丰富,在ES6中还添加了模块(module)和类(class),感觉他已经失去了曾经的单纯了,不知道这些新功能的补充对开发者来说是福音还是负担。之前写过两篇关于ES6的文章,ECMAScript 6 简介 和 ECMAScript 6中的let和const关键词,本文将一一介绍ES6中的一些新特性。
本文地址:http://www.cnblogs.com/hustskyking/p/ecmascript6-overview.html,转载请注明源地址。
注意:如果想测试以下属性,请安装 0.11+ 版本的 node,并添加上 --harmony 参数。
这个内容在 ECMAScript 6中的let和const关键词 一文中已经介绍过了。简单来说就是一句话:ES6中引入了块级作用域,let的有效区间是他所在的 {}
大括号中。const 为常量,定义之后不能更改,也删除不了。
> const PI = 3.14;
> Object.getOwnPropertyDescriptor(window, PI) Object {value: 3.1415, writable: false, enumerable: true, configurable: false}
writable 和 configurable 都是 false。
写过 coffee-script 的人都知道,我们可以这样给一个数据赋值:
num[1..3] = ["hello", "i'am", "Barret Lee"]
ES6中也允许类似的多变量赋值:
var [x, y, z] = ["hello", "i'am", "Barret Lee"];
更强大的是,他还适合对象:
var { foo, bar } = { foo: "Barret", bar: "Lee" };
这种赋值方式是模式匹配的,只要左侧跟右侧对应,便可以成功赋值。感觉新手不会太适应这种写法。
先看例子:
var a1 = [1, 2, 3, 4]; var a2 = [i * 2 for (i of a1)]; a2 // [2, 4, 6, 8]
这东西只是简化了编程,没有从根本上增加功能和特性,写 coffee 的人应该比较喜欢,我看着还是有点不习惯,其实上面的写法就等价于:
var a1 = [1, 2, 3, 4]; var a2 = a1.map(function (i) { return i * 2 });
我想除非是代码长度有限制,否则这玩意儿正式出来了我也不会用它。
这一块的内容相当于是给 JS 编码打一个补丁,这个补丁用来弥补双字节 UTF-16 字符带来的问题,引入的各个函数也只是对不同场景的修复。这个扩展还是相当重要的,尤其是 ArrayBuffer 中数据类型的相关处理,涉及到很多类似 Float64Array Uint32Array 等类型化数组的处理,我在 你所不知道的JavaScript数组 曾提到过。
这个地方需要解释下 JavaScript 对字符的储存模式,JavaScript 中的字符串是以 UTF-16 为代码单元,通常我们使用的字符范围都在 Unicode 值 0x10000 以内,他们对应的 UTF-16 就是它们自身,但 Unicode 中也存在这个范围之外的字符,这时候就需要两个 UTF-16 字符来描述,比如:
alert("?".length); //2
因为字符串的 length 表示的并不是字符个数,而是 UTF-16 的单元个数。关于这方面知识的具体介绍,可以戳这里。
ES6提供了 codePointAt 函数来正确处理 4 个字节储存的字符。
var s = "?二"; s.codePointAt(0);
codePointAt 会把 s 中的两个字符都正确解析出来,相当于智能的将储存单元切换为 2 或者 4.
对应 String.fromCharCode 的能够智能解析 s 的函数是 String.fromCodePoint
我们知道可以使用 \u0000-\uFFFF 来表示一个 Unicode 字符,依然是上面的问题,如果字符超出了这个范围,比如 "\u10000" 这个字符,便不能正确的解析出来,ES6中可以这样:
#\u{10000}
用大括号括起来就可以正常解析超过 FFFF 的字符了。
/^.$/.test("?") // false /^..$/.test("?") // true /^.$/u.test("?") // true
看到上面三个测试,相信你已经知道是什么意思了。
contains(), startsWith(), endsWith(), repeat()
都是对 String 的拓展,顾名就能思意。
之前写过一篇关于 TEMPLATE 标签的文章,这里我们又看到了一个跟模板相关的东西。
// 字符串中嵌入变量 var name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?` var x = 1; var y = 2; console.log(`${ x } + ${ y } = ${ x + y}`) // "1 + 2 = 3"
配合 TEMPLATE 标签使用是比较方便的,不过 TEMPLATE 标签可以很方便的使用自定义标签,这个特性也不是很突出了。
<div id="test"> <x-from>DOM文档</x-from> <x-name>test元素</x-name> </div> <template id="temp"> 我是来自 <strong><content select="x-from"></content></strong> 中的 <strong><content select="x-name"></content></strong> 的数据。 </template> <script> var root = test.webkitCreateShadowRoot(); root.appendChild(document.importNode(temp.content)); </script>
0b111110111 === 503 // true 0o767 === 503 // true
前缀 0b 和 0o 表示二进制和八进制数值
具体细节可以去 MDN 上搜查。
在 ES5.1 中我们看到了 Object 的灵活性提高了很多,加入了 create、defineProperty、freeze 等等很多十分有用的方法,而在 ES6 中,依然继续加强:
Object.is()
比较严格相等,传入两个参数,其功能跟 ===
差不多,不同的是:+0不等于-0,NaN与本身恒等
+0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true
obj.__proto__ = otherObj;
这东西浏览器早就实现了,只是在 ES6 才被纳入标准中,还记得这东西被用来判断是否为 IE 么:
var isIE = "__proto__" in {} ? false : true;
又是一个装饰品:
var Person = { name: 'Barret Lee', //等同于birth: birth birth, // 等同于hello: function ()... hello() { console.log('My Name Is', this.name); } };
var name = "my name"; var Me = { [name]: "Barret Lee", "name": "李靖" }; Me[name] // "Barret Lee" Me["my name"] // "Barret Lee" Me["name"] // "李靖"
挺方便的,不过容易出错,比如上面,看晕了吧~
一种从类型上区分数据的工具,他是一个原始类型的值,不是对象,很适合做标识符。
Proxy 内置的一个代理工具,使用他可以在对象处理上加一层屏障:
var plain = { name : "Barret Lee" }; var proxy = new Proxy(plain, { get: function(target, property) { return property in target ? target[property] : "我擦"; } }); proxy.name // "Barret Lee" proxy.title // "我擦"
Proxy(target, handler), 这里的 handler 可以是 set get has hasOwn keys 等等方法,具体可以移步这里:http://wiki.ecmascript.org/doku.php?id=harmony:direct_proxies
function Point(x = 0, y = 0) { this.x = x; this.y = y; } var p = new Point(); // p = { x:0, y:0 }
他这个参数可以初始化相当于暴露更多的底层接口吧,提高了函数的拓展性。
function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } add(1, 2 ,3) // 6
这里的 values 需要用 val of values 来遍历。
之前写过 coffee ,所以对这个我还是比较熟悉的:
var f = (a, b) => {return a + b};
这个跟上面提到的一些内容一样,都是装饰性的,没太多用途。
简单点解释,他就是一个没有重复数值的数组。或者说他就是一个数组的 hash 表。
var items = new Set([1,2,3,4,5,5,5,5]); for (i of s) {console.log(i)} // 2 3 4 5
其遍历也是使用 val of values,他没有继承 Array 的方法,自己的几个方法是:
转化为数组的方式:
var items = new Set([1, 2, 3, 4, 5]); var array = Array.from(items);
Map 是一个“超对象”,其 key 除了可以是 String 类型之外,还可以为其他类型(如:对象)
var m = new Map(); o = {p: "Hello World"}; m.set(o, "content") console.log(m.get(o)) // "content"
他的方法和 Set 差不多:
如果说Map 是一个“超对象”,那 WeakMap 就是个 “弱超对象”,他的键值只能是除 null 以外的对象。他的存在是为了方便垃圾回收。
相比 Array,他对数据的管理更为紧凑,而且可操纵性也比较强,以后会成为一个比较通用的 JS 工具。
function idMaker(){ var index = 0; return { next: function(){ return {value: index++, done: false}; } } } var it = idMaker(); it.next().value // '0' it.next().value // '1' it.next().value // '2'
一个对象只要具备了next方法,就可以用for...of循环遍历它的值。
for (var n of it) { if (n > 5) break; console.log(n); }
Generator就是一个改装了的 Iterator 遍历器,通过 yield 来增加一个 next() 节点。
声明一个 Generator 的方法:
function * foo( input ) { var res = yield input; }
使用方式:
function * foo( input ) { var res = yield input; } var g = foo(10); g.next(); // { value: 10, done: false } g.next(); // { value: undefined, done: true }
Genrator的标识就是函数名前面有个 *
号,由于每个 yield 都会增加一个 next() 节点,当我们在一个 Generator 中添加多个 yield 的时候:
function* G() { yield 'Barret'; yield 'Lee'; } var g = G(); g.next(); // "Barret" g.next(); // "Lee" g.next(); // undefined g.next(); // Error: Generator has already finished
相比 Iterator,我更喜欢 Generator。
ES6将 Promise 纳入规范之后,很多浏览器根据 Promise/A+ 规范实现了一套 API,Promise对象是对异步操作的平坦式表达,避免了 callback hell,也就是多层嵌套的回调函数。这方面的东西可以参考我之前写的 JavaScript异步编程原理.
这里是 Promise/A+ 规范文档,感兴趣的可以阅读一下。
在Promises/A规范中,每个任务都有三种状态:默认(pending)、完成(fulfilled)、失败(rejected)。
Class,对象模板:
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '('+this.x+', '+this.y+')'; } }
一下子就有了 C++/Java 的感觉了,在类中可以使用 extends 继承:
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // same as super.constructor(x, y) this.color = color; } toString() { return this.color+' '+super(); } }
之前在这篇文章谈过模块化编程的必要性,时代在变化,需求也在变,ES6引入 Module 也算是与时俱进。ES6允许将独立的js文件作为模块,也就是说,允许一个JavaScript脚本文件调用另一个脚本文件,从而使得模块化编程成为可能。
// circle.js export function area(radius) { return Math.PI * radius * radius; } export function circumference(radius) { return 2 * Math.PI * radius; }
如果我们要引入 circle.js 的函数,可以:
// main.js import { area, circumference } from 'circle'; console.log("圆面积:" + area(4)); console.log("圆周长:" + circumference(14));
如果要引入整个 circle 的内容:
// main.js module circle from 'circle'; console.log("圆面积:" + circle.area(4)); console.log("圆周长:" + circle.circumference(14));
模块之间的继承使用这条命令:
// other.js export * from 'circle';
这个东西,很好用,可惜了,纯洁的 JS,被糟蹋成啥样了=. =
本文的书写逻辑参考阮一峰的总结,并参入了一些个人主观色彩,感谢前人栽树!