补充一下ES6的知识
- [Array]请注意,如果通过索引赋值时,索引超过了范围,同样会引起Array大小的变化:
1 2 3
| var arr = [1, 2, 3]; arr[5] = 'x'; arr; // arr变为[1, 2, 3, undefined, undefined, 'x']
|
大多数其他编程语言不允许直接改变数组的大小,越界访问索引会报错。然而,JavaScript的Array却不会有任何错误。在编写代码时,不建议直接修改Array的大小,访问索引时要确保索引不会越界。
- [String]要获取字符串某个指定位置的字符,使用类似Array的下标操作,索引号从0开始:
1 2 3 4 5 6 7
| var s = 'Hello, world!'; s[0]; // 'H' s[6]; // ' ' s[7]; // 'w' s[12]; // '!' s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined
|
需要特别注意的是,字符串是不可变的,如果对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果:
1 2 3
| var s = 'Test'; s[0] = 'X'; alert(s); // s仍然为'Test'
|
Map是一组键值对的结构,具有极快的查找速度。
举个例子,假设要根据同学的名字查找对应的成绩,如果用Array实现,需要两个Array:
1 2
| var names = ['Michael', 'Bob', 'Tracy']; var scores = [95, 75, 85];
|
给定一个名字,要查找对应的成绩,就先要在names中找到对应的位置,再从scores取出对应的成绩,Array越长,耗时越长。
如果用Map实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用JavaScript写一个Map如下:
1 2
| var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); m.get('Michael'); // 95
|
初始化Map需要一个二维数组,或者直接初始化一个空Map。Map具有以下方法:
1 2 3 4 5 6 7
| var m = new Map(); // 空Map m.set('Adam', 67); // 添加新的key-value m.set('Bob', 59); m.has('Adam'); // 是否存在key 'Adam': true m.get('Adam'); // 67 m.delete('Adam'); // 删除key 'Adam' m.get('Adam'); // undefined
|
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
1 2 3 4
| var m = new Map(); m.set('Adam', 67); m.set('Adam', 88); m.get('Adam'); // 88
|
由于map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果:
1 2 3 4 5 6 7
| function pow(x) { return x * x; } var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81] map()传入的参数是pow,即函数对象本身。
|
你可能会想,不需要map(),写一个循环,也可以计算出结果:
1 2 3 4 5 6 7 8 9
| var f = function (x) { return x * x; }; var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var result = []; for (var i=0; i<arr.length; i++) { result.push(f(arr[i])); }
|
的确可以,但是,从上面的循环代码,我们无法一眼看明白“把f(x)作用在Array的每一个元素并把结果生成一个新的Array”。
所以,map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把Array的所有数字转为字符串:
1 2
| var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
|
只需要一行代码。
Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。
要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:
1 2
| var s1 = new Set(); // 空Set var s2 = new Set([1, 2, 3]); // 含1, 2, 3
|
重复元素在Set中自动被过滤:
1 2
| var s = new Set([1, 2, 3, 3, '3']); s; // Set {1, 2, 3, "3"}
|
注意数字3和字符串’3’是不同的元素。
通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:
1 2 3 4 5 6
| >>> s.add(4) >>> s {1, 2, 3, 4} >>> s.add(4) >>> s {1, 2, 3, 4}
|
通过delete(key)方法可以删除元素:
1 2 3 4
| var s = new Set([1, 2, 3]); s; // Set {1, 2, 3} s.delete(3); s; // Set {1, 2}
|
Map和Set是ES6标准新增的数据类型,请根据浏览器的支持情况决定是否要使用。
遍历Array可以采用下标循环,遍历Map和Set就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable类型,Array、Map和Set都属于iterable类型。
具有iterable类型的集合可以通过新的for … of循环来遍历。
for … of循环是ES6引入的新的语法,用for … of循环遍历集合,用法如下:
1 2 3 4 5 6 7 8 9 10 11 12
| var a = ['A', 'B', 'C']; var s = new Set(['A', 'B', 'C']); var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); for (var x of a) { // 遍历Array alert(x); } for (var x of s) { // 遍历Set alert(x); } for (var x of m) { // 遍历Map alert(x[0] + '=' + x[1]); }
|
你可能会有疑问,for … of循环和for … in循环有何区别?
for … in循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。
当我们手动给Array对象添加了额外的属性后,for … in循环将带来意想不到的意外效果:
1 2 3 4 5
| var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x in a) { alert(x); // '0', '1', '2', 'name' }
|
for … in循环将把name包括在内,但Array的length属性却不包括在内。
for … of循环则完全修复了这些问题,它只循环集合本身的元素:
1 2 3 4 5
| var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x of a) { alert(x); 'A', 'B', 'C' }
|
这就是为什么要引入新的for … of循环。
然而,更好的方式是直接使用iterable内置的forEach方法,它接收一个函数,每次迭代就自动回调该函数。以Array为例:
1 2 3 4 5 6 7
| var a = ['A', 'B', 'C']; a.forEach(function (element, index, array) { // element: 指向当前元素的值 // index: 指向当前索引 // array: 指向Array对象本身 alert(element); });
|
注意,forEach()方法是ES5.1标准引入的,你需要测试浏览器是否支持。
Set与Array类似,但Set没有索引,因此回调函数的前两个参数都是元素本身:
1 2 3 4
| var s = new Set(['A', 'B', 'C']); s.forEach(function (element, sameElement, set) { alert(element); });
|
Map的回调函数参数依次为value、key和map本身:
1 2 3 4
| var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); m.forEach(function (value, key, map) { alert(value); });
|
如果对某些参数不感兴趣,由于JavaScript的函数调用不要求参数必须一致,因此可以忽略它们。例如,只需要获得Array的element:
1 2 3 4
| var a = ['A', 'B', 'C']; a.forEach(function (element) { alert(element); });
|
由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:
ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域:
1 2 3 4 5
| 'use strict'; const PI = 3.14; PI = 3; // 某些浏览器不报错,但是无效果! PI; // 3.14
|
- [装饰器]
p.s.这段没怎么看懂
利用apply(),我们还可以动态改变函数的行为。
JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
现在假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的parseInt():
1 2 3 4 5 6 7 8 9 10 11 12 13
| var count = 0; var oldParseInt = parseInt; // 保存原函数 window.parseInt = function () { count += 1; return oldParseInt.apply(null, arguments); // 调用原函数 }; // 测试: parseInt('10'); parseInt('20'); parseInt('30'); count; // 3
|
再看reduce的用法。Array的reduce()把一个函数作用在这个Array的[x1, x2, x3…]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是:
[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
比方说对一个Array求和,就可以用reduce实现:
1 2 3 4
| var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x + y; }); // 25
|
- [filter]
filter也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素。
和map()类似,Array的filter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
例如,在一个Array中,删掉偶数,只保留奇数,可以这么写:
1 2 3 4 5
| var arr = [1, 2, 4, 5, 6, 9, 10, 15]; var r = arr.filter(function (x) { return x % 2 !== 0; }); r; // [1, 5, 9, 15]
|
把一个Array中的空字符串删掉,可以这么写:
1 2 3 4 5
| var arr = ['A', '', 'B', null, undefined, 'C', ' ']; var r = arr.filter(function (s) { return s && s.trim(); // 注意:IE9以下的版本没有trim()方法 }); r; // ['A', 'B', 'C']
|
可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。
+[闭包]
1 2 3 4 5 6 7 8
| function lazy_sum(arr) { var sum = function () { return arr.reduce(function (x, y) { return x + y; }); } return sum; }
|
当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
1
| var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
|
调用函数f时,才真正计算求和的结果:
在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
1 2 3
| var f1 = lazy_sum([1, 2, 3, 4, 5]); var f2 = lazy_sum([1, 2, 3, 4, 5]); f1 === f2; // false
|
f1()和f2()的调用结果互不影响。