30分钟快速上手ES6(下)
Iterators + For..Of 迭代器 + For..of 循环
1)for循环的疑问
起初我们如何遍历数组中的元素呢?20年前JavaScript刚萌生时,你可能这样实现数组遍历:
var myArray = ['2','3','aaa']; for (var index = 0; index < myArray.length; index++) { console.log(myArray[index]);
}
自ES5正式发布后,你可以使用内建的forEach方法来遍历数组:
myArray.forEach(function (value) { console.log(value);
});
使用forEach的这段代码看起来更加简洁,但这种方法也有一个小缺陷:你不能使用break语句中断循环,也不能使用return语句返回到外层函数。
是不是呢?你们可以进行相关的验证,同时呢它在浏览器兼容与支持方面并不理想,所以我们会想起for循环来。
当然,如果只用for循环的语法来遍历数组元素也很不错,就如上面的第一个例子。
那么,你一定想尝试一下for-in循环:
for (var index in myArray) { // 千万别这样做 console.log(myArray[index]);
}
这绝对是一个糟糕的选择,为什么呢?
- 在这段代码中,赋给index的值不是实际的数字,而是字符串“0”、“1”、“2”,而你呢?此时很可能在无意之间需要进行相关计算,但这是这不是数值的计算了,而是字符串算数计算,例如:“2” + 1 == “21”,这给编码过程带来极大的不便。
- 作用于数组的for-in循环体除了遍历数组元素外,还会遍历自定义属性。举个例子,如果你的数组中有一个可枚举属性myArray.name,循环将额外执行一次,遍历到名为“name”的索引。就连数组原型链上的属性都能被访问到。
- 最让人震惊的是,在某些情况下,这段代码可能按照随机顺序遍历数组元素。
- 简而言之,for-in是为普通对象设计的,你可以遍历得到字符串类型的键,因此不适用于数组遍历。
其实啰里啰唆说了这么多你就记住一句话:for-in是用于遍历对象的,普通for循环和forEach是遍历普通数组的。
如果有天你想要遍历对象又要得到它的键值,这里举例说明:
var obj = { 'name': '言墨儿', 'age': 21 }; console.log(Object.keys(obj)) // 返回的是一个数组 // 不推荐for-in for (var val in Object.keys(obj)) { console.log(val); // 获得键值key-字符串 console.log(Object.keys(obj)[val]); // name、age-(获得键值key对应的数据) } // 推荐for循环 for (var i = 0; i < Object.keys(obj).length; i++) { console.log(i); // 获得键值key-数字 console.log(Object.keys(obj)[i]); // name、age-(获得键值key对应的数据) }
(2)强大的for-of循环
为了解决这些问题所以新的循环方式在ES6中出现了,当然ES6不会破坏你已经写好的JS代码。目前,成千上万的Web网站依赖for-in循环,其中一些网站甚至将其用于数组遍历。而如果想通过修正for-in循环增加数组遍历支持会让这一切变得更加混乱,因此,标准委员会在ES6中增加了一种新的循环语法来解决目前的问题。
就像这样:
// for-of循环支持数组遍历和大多数类数组对象 var myArray = ['2','3','aaa'] for (var value of myArray) { console.log(value); // 2,3,aaa }
是的,与之前的内建方法相比,这种循环方式看起来是否有些眼熟?那好,我们将要探究一下for-of循环的外表下隐藏着哪些强大的功能。现在,你只需记住:
- 这是最简洁、最直接的遍历数组元素的语法
- 这个方法避开了for-in循环的所有缺陷
- 与forEach()不同的是,它可以正确响应break、continue和return语句
表面看来for-in循环用来遍历对象属性,for-of循环用来遍历数据—例如数组中的值。但是,for-of不仅如此!
for-of循环也可以遍历其它的集合,for-of循环不仅支持数组,还支持大多数类数组对象,例如DOM NodeList对象。同时,for-of循环也支持字符串遍历,它将字符串视为一系列的Unicode字符来进行遍历:
for (var chr of "yanmoer") { console.log (chr); // y,a,n,m,o,e,r }
它同样支持Map和Set对象遍历。
很多人也许没听说过Map和Set对象。他们是ES6中新增的类型。不知道也没有关系,我将在后面简单讲解这两个新的类型,如果你还有兴趣,你可以点击我存放的连接。
如果你曾在其它语言中使用过Map和Set对象,你会发现ES6中的并无太大出入。
其实讲简单点就是Set对象可以自动排除重复项:
// 基于单词数组创建一个set对象 var words = ['yanmoer','qinni','yanmoer'] var uniqueWords = new Set(words); // 如果成为Set对象,输出结果会去掉重复项 console.log(uniqueWords); // Set {"yanmoer", "qinni"} // 生成Set对象后,你可以轻松遍历它所包含的内容: for (var word of uniqueWords) { console.log(word); // yanmoer,qinni }
Set对象还有一些方法,这里不一一列举了,详情请见官网api:Set对象
Map对象则稍有不同:内含的数据由键值对组成,所以你需要使用解构(destructuring)来将键值对拆解为两个独立的变量,例子说明:
var phoneBookMap = [['name','yanmo'],['age','22']]; // Map对象就是这样的 for (var [key, value] of phoneBookMap) { // [key, value]就是解构 console.log(key + " : " + value); //'name' : 'yanmo','age' : '22' }
其实Map对象就是简单的键/值映射。其中键和值可以是任意值(对象或者原始值)。关于Map对象的方法,也是详情请见官网api:Map对象
解构也是ES6的新特性,简单说来解构赋值就是允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量,我将在后续的文章中进行讲解。看来我应该记录这些优秀的问题,未来再进行相关新内容的一一剖析。
现在,你只需记住:未来的JS可以使用一些新型的集合类,甚至会有更多的类型陆续诞生,而for-of就是为遍历所有这些集合特别设计的循环语句。
for-of循环不支持普通对象,你应当用用for-in循环(这也是它的本职工作)或内建的Object.keys()方法:
// 向控制台输出对象的可枚举属性 var someObject = { classA: 'textColor', classB: 'textSize', isA: false } for (var key of Object.keys(someObject)) { console.log(key + ": " + someObject[key]); // classA: 'textColor',classB: 'textSize',isA: false }
Promises,Generators
// 从语言层面上支持Promise let promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } }); promise.then(successFn, failFn); function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator(); // 指向内部状态的指针对象 hw.next() // 调用next方法,使得指针移向下一个状态 // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
Proxies 代理
代理可以创造一个具备宿主对象全部可用行为的对象。可用于拦截、对象虚拟化、日志/分析等。
Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写
var MyArray = function () { return new Proxy(Array.apply(null, arguments), { get: function (target, name) { if (name in target || (+name >= 0 && +name < 4294967295)) { return target[name]; } else { throw "非法索引"; } }, set: function (target, name, val) { if (name in target || (+name >= 0 && +name < 4294967295)) { target[name] = val; } else { throw "非法索引"; } } }) } var arr = MyArray(1,2,3); //新建一个自定义数组 arr; //[1, 2, 3] arr.push(4); //数组方法也能正常使用 arr; //[1, 2, 3, 4] arr[999999999999] //抛出异常,"非法索引" arr["foo"] = 1 //抛出异常,"非法索引"
Math + Number + String + Object APIs 扩展
新加入了许多库,包括核心数学库,进行数组转换的协助函数,字符串 helper,以及用来进行拷贝的Object.assign。
Number.EPSILON Number.isInteger(Infinity) // false Number.isNaN("NaN") // false Math.acosh(3) // 1.762747174039086 Math.hypot(3, 4) // 5 Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2 "abcde".includes("cd") // true "abc".repeat(3) // "abcabcabc" Array.from(document.querySelectorAll('*')) // Returns a real Array 返回一个真正的Array Array.of(1, 2, 3) // Similar to new Array(...), but without special one-arg behavior 与Array(...)类似,但只有一个参数时,并不会有特殊行为。 [0, 0, 0].fill(7, 1) // [0,7,7] [1, 2, 3].find(x => x == 3) // 3 [1, 2, 3].findIndex(x => x == 2) // 1 [1, 2, 3, 4, 5].copyWithin(3, 0) // [1, 2, 3, 1, 2] ["a", "b", "c"].entries() // iterator [0, "a"], [1,"b"], [2,"c"] ["a", "b", "c"].keys() // iterator 0, 1, 2 ["a", "b", "c"].values() // iterator "a", "b", "c" Object.assign(Point, { origin: new Point(0,0) })
Tail Calls 尾调用
function factorial(n, acc = 1) { 'use strict'; if (n <= 1) return acc; return factorial(n - 1, n * acc); } // Stack overflow in most implementations today, // but safe on arbitrary inputs in ES6 // 栈溢出存在于现在绝大多数的实现中, // 但是在 ES6 中,针对任意的输入都很安全 factorial(100000)
ES6 的一部分特性看起来很像 Go