[关闭]
@FunC 2016-11-19T16:37:11.000000Z 字数 6689 阅读 2397

ES6笔记

前端 JavaScript


let和const

let
块级作用域
不存在变量提升
暂时性死区(TDZ):进入let 声明所在区块时,在用let声明前都无法使用、更改该变量(即使在区块外已用var定义)
不允许在相同作用域内重复声明同一变量(另一声明用var也不行、同时也不能重复声明函数参数)
ES6中块级作用域中用function声明函数与let类似,仅在块级作用域中有效。(但因为会带来严重的兼容性问题,目前只在ES6实现的浏览器做出如上表现)

const
不存在变量提升
一旦声明不能更改
对于复合类型,指向的是地址,无法保证该地址的数据不变

顶层对象的属性

let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。

顶层对象

ES5中顶层对象在各种实现里不统一(浏览器、Web Worker、Node)
尚未实现的提案:引入global作为顶层对象


变量的解构赋值(与Iterator接口有关)

  1. var [a, b, c] = [1, 2, 3];
  2. //相当于
  3. //var a = 1;
  4. //var b = 2;
  5. //var c = 3;

左边数量少于右边:不完全解构(但也是成功的解构)
左边数量大于右边:多出的为undefined

默认值

允许设置默认值
在数组成员严格等于(===undefined时生效
见下面例子:

  1. var [x = 1] = [undefined];
  2. x // 1
  3. var [x = 1] = [null];
  4. x // null

配置默认值的逻辑如下:

  1. let x;
  2. if ([1][0] === undefined) {
  3. x = f();
  4. } else {
  5. x = [1][0];
  6. }

可见,如果默认值是表达式,那么将在右侧严格等于undefined时才为默认值
这点与一般默认值的认知有所差别,请注意

对对象的解构赋值

根据属性名称匹配(所以可以无序排列)
例子:

  1. var { foo, bar } = { foo: "aaa", bar: "bbb" };
  2. foo // "aaa"
  3. bar // "bbb"

实质为:

  1. var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

数值和布尔值的解构赋值

结构赋值的规则:等号右边不是对象的话先转为对象。
undefined null不能转化为对象,对其进行解构赋值时会报错。

函数参数的解构赋值(可用于为函数设定默认值)

见下面代码:

  1. function move({x = 0, y = 0} = {}) {
  2. return [x, y];
  3. }
  4. move({x: 3, y: 8}); // [3, 8]
  5. move({x: 3}); // [3, 0]
  6. move({}); // [0, 0]
  7. move(); // [0, 0]

这是为变量x,y设定默认值

注意与下面写法的区别:

  1. function move({x, y} = { x: 0, y: 0 }) {
  2. return [x, y];
  3. }
  4. move({x: 3, y: 8}); // [3, 8]
  5. move({x: 3}); // [3, undefined]
  6. move({}); // [undefined, undefined]
  7. move(); // [0, 0]

这是为函数参数设定默认值(似乎直接给变量写默认值为更优选择,见下文函数的扩展)

关于圆括号的使用

大概原则就是能不加圆括号就不加圆括号,不然容易导致解析有歧义而报错。
具体见阮一峰的ES6入门

变量的解构赋值的用途

  1. 交换变量的值
  1. [x, y] = [y, x];
  1. 从函数返回多个值
  1. // 参数是一组有次序的值
  2. function f([x, y, z]) { ... }
  3. f([1, 2, 3]);
  4. // 参数是一组无次序的值
  5. function f({x, y, z}) { ... }
  6. f({z: 3, y: 2, x: 1});
  1. 函数参数的定义(可实现无序传入参数)
  2. 提取JSON数据
  3. 设置函数参数的默认值
  4. 遍历Map结构
  5. 输入模块的指定方法(Node.js,ES6使用import导入模块)
  1. const { SourceMapConsumer, SourceNode } = require("source-map");

字符串的扩展(先看完ES5的字符串再过来,太多不懂了)

1.字符的Unicode表示法

ES5中只支持\u0000 ~ \uFFFF 之间的字符,超出范围时要用双字节的形式表达
ES6支持四字节的UTF-16编码,用{}\u后的码点括起来即可

未完待续......


函数的扩展

一、函数的默认值

1.参数默认值:直接在参数后写默认值

  1. function Point(x = 0, y = 0) {
  2. this.x = x;
  3. this.y = y;
  4. }
  5. var p = new Point();
  6. p // { x: 0, y: 0 }

2.配合解构赋值默认值使用(不建议单独使用解构赋值默认值,原因见下文)

此处容易混淆,大致的区分方法为:不在{}内部的逗号分隔开的是参数,参数进行了赋值就是参数默认值;花括号内部的赋值为解构赋值默认值。当某参数只设置了解构赋值默认值时,即该参数无默认值,所以缺少该参数时会报错。

两种写法的区分

  1. // 写法一:个人更推荐
  2. function m1({x = 0, y = 0} = {}) {
  3. return [x, y];
  4. }
  5. // 写法二
  6. //该函数参数为一个对象,且未设置解构赋值默认值,所以一旦有参数传入但无x,y属性时,x,y便是undefined,失去默认值的意义
  7. function m2({x, y} = { x: 0, y: 0 }) {
  8. return [x, y];
  9. }

参数默认值的位置

需要注意的是,定义了默认值的参数应该是函数的尾参数。因为如果是非尾参数的话,在传入参数时,中间值无法省略(只能通过显式设定undefined来忽略)

进一步理解函数的length属性

先看如下代码

  1. (function (a) {}).length // 1
  2. (function (a = 5) {}).length // 0
  3. (function (a, b, c = 5) {}).length // 2
  4. //rest参数也不会计入length属性
  5. (function(...args) {}).length // 0

为什么会这样呢?那是因为length属性的含义是:该函数预期传入的参数个数。

注意:如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

  1. (function (a = 0, b, c) {}).length // 0
  2. (function (a, b = 1, c) {}).length // 1

作用域

若参数的默认值是一个变量,那么该变量的作用域规则与其他变量作用域规则一致。即先函数作用域、后全局作用域。
见下面代码

  1. var x = 1;
  2. //下面x为第一个参数,先在函数作用域中生成
  3. function f(x, y = x) {
  4. console.log(y);
  5. }
  6. f(2) // 2

若参数的默认值是一个函数,那么情况将复杂得多。该函数的作用域是其声明时所在的作用域。
例如下面这个稍微复杂一点的例子:

  1. var x = 1;
  2. function foo(x, y = function() { x = 2; }) {
  3. var x = 3;//若去掉var则最后结果为2,即全部的x都是一样的
  4. y();
  5. console.log(x);
  6. }
  7. foo() // 3

y函数内部的x是作为形参的x,在foo内部声明了x之后x就是一个变量了,切断了与原来形参的联系(参考以对象作参数,然后重新声明一个与参数名相同的对象)

函数参数默认值的应用

设置函数默认值除了可以省略某参数以外,还能制定某一参数不可省略,若省略则报错。
实现方式如下:

  1. function throwIfMissing() {
  2. throw new Error('Missing parameter');
  3. }
  4. function foo(mustBeProvided = throwIfMissing()) {
  5. return mustBeProvided;
  6. }
  7. foo()
  8. // Error: Missing parameter

值得注意的是:参数mustBeProvided的默认值等于throwIfMissing函数的运行结果(即函数名之后有一对圆括号),这揭示了参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与python语言不一样。

二、rest参数

ES6引入rest参数(形式为:...变量名),用于获取函数的多余参数。rest参数搭配的变量是一个数组(故能使用所有数组特有的方法),该变量将多余的参数放入数组中。
以改写数组push方法为例:

  1. function push(array, ...items) {
  2. items.forEach(function(item) {
  3. array.push(item);
  4. console.log(item);
  5. });
  6. }
  7. var a = [];
  8. push(a, 1, 2, 3)

三、扩展运算符

扩展运算符(spread)是三个点...。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
该运算符主要用于函数调用:

  1. function add(x, y) {
  2. return x + y;
  3. }
  4. var numbers = [4, 38];
  5. add(...numbers) // 42

扩展运算符的应用

1.合并数组

  1. // ES5
  2. [1, 2].concat(more)
  3. // ES6
  4. [1, 2, ...more]
  5. var arr1 = ['a', 'b'];
  6. var arr2 = ['c'];
  7. var arr3 = ['d', 'e'];
  8. // ES5的合并数组
  9. arr1.concat(arr2, arr3);
  10. // [ 'a', 'b', 'c', 'd', 'e' ]
  11. // ES6的合并数组
  12. [...arr1, ...arr2, ...arr3]
  13. // [ 'a', 'b', 'c', 'd', 'e' ]

2.与解构赋值结合
用于生成数组:

  1. const [first, ...rest] = [1, 2, 3, 4, 5];
  2. first // 1
  3. rest // [2, 3, 4, 5]
  4. const [first, ...rest] = [];
  5. first // undefined
  6. rest // []:
  7. const [first, ...rest] = ["foo"];
  8. first // "foo"
  9. rest // []

如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

3.函数的返回值
JavaScript的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。

4.扩展运算符还可以将字符串转为真正的数组
重点是能正确识别32位Unicode字符

  1. //三个字符分别为x、\uD83D\uDE80、y
  2. 'x\uD83D\uDE80y'.length // 4
  3. [...'x\uD83D\uDE80y'].length // 3

凡是涉及到操作32位Unicode字符的函数,都有这个问题。因此,最好都用扩展运算符改写:

  1. function length(str) {
  2. return [...str].length;
  3. }

5.实现了Iterator接口的对象
任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。

6.Map和Set结构,Generator函数

四、严格模式

五、name属性

六、箭头函数

ES6中允许使用“箭头”=>定义函数

  1. //ES5
  2. var f = function(v) {
  3. return v;
  4. };
  5. //ES6
  6. var f = v => v;

当不需要参数或需要多个参数时,使用圆括号括起来。
代码块多于一条语句时,用大括号括起来(因此,如果像返回一个对象,要在对象外再加一个括号)

可与解构解析结合使用

  1. const full = ({ first, last }) => first + ' ' + last;
  2. // 等同于
  3. function full(person) {
  4. return person.first + ' ' + person.last;
  5. }

箭头函数使用时的注意事项:

  1. 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  2. 不可当作构造函数(即不能使用new命令,否则报错)
  3. 不可使用arguement对象,该对象在函数体内不存在(为什么?arguement对象是通过function命令生成的吗?)。可使用Rest参数代替。
  4. 不可使用yield命令,所以箭头函数不能用作Generator函数

this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数

除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:argumentssupernew.target

七、绑定this

八、尾调用优化

Set与Map数据结构

一、Set

类似于数组的对象,但成员唯一。
Set本身是一个构造函数,用来生成Set数据结构
Set函数可以接受一个数组(或类似数组的对象)进行初始化。

  1. var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
  2. items.size // 5

Set可用于去除数组重复成员:

  1. // 去除数组的重复成员,下文还会提供另一种方法
  2. [...new Set(array)]

值得注意的是:像Set中加入值时,不会发生类型转换。所以5和"5"是不同的值。Set内部判断两个值是否不同的算法类似于精确相等运算符===,区别在于该算法判断两个NaN是相等的。另外,两个对象总是不相等的

Set的实例属性和方法

属性:
Set.prototype.constructor:构造函数,默认就是Set函数。
Set.prototype.size:返回Set实例的成员总数。

方法:


Array.from

  1. function dedupe(array) {
  2. return Array.from(new Set(array));
  3. }
  4. dedupe([1, 1, 2, 3]) // [1, 2, 3]


值得注意的是:Set的遍历顺序就是插入顺序(对比对象的for...in遍历,是无序的)。这个特性有时非常有用,比如使用Set保存一个回调函数列表,调用时就能保证按照添加顺序调用。

forEach()

  1. let set = new Set([1, 2, 3]);
  2. set.forEach((value, key) => console.log(value * 2) )
  3. // 2
  4. // 4
  5. // 6

上述例子说明forEach方法的参数就是一个处理函数。该函数的参数依次为键值键名集合本身(上例中省略了)。另外,forEach方法还可以有第二个参数,表示绑定的this对象。

遍历的应用

扩展运算符(...)内部使用for...of循环,所以也可以用于Set结构。
故可用于去除数组的重复成员。

另外,数组的mapfilter方法也可以用于Set:

  1. let set = new Set([1, 2, 3]);
  2. set = new Set([...set].map(x => x * 2));
  3. // 返回Set结构:{2, 4, 6}
  4. let set = new Set([1, 2, 3, 4, 5]);
  5. set = new Set([...set].filter(x => (x % 2) == 0));
  6. // 返回Set结构:{2, 4}

因此使用Set可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。

  1. let a = new Set([1, 2, 3]);
  2. let b = new Set([4, 3, 2]);
  3. // 并集
  4. let union = new Set([...a, ...b]);
  5. // Set {1, 2, 3, 4}
  6. // 交集
  7. let intersect = new Set([...a].filter(x => b.has(x)));
  8. // set {2, 3}
  9. // 差集
  10. let difference = new Set([...a].filter(x => !b.has(x)));
  11. // Set {1}

如果想在遍历中同步更新原来的Set结构,目前没有直接的方法。变通的方法如下:

  1. // 方法一
  2. let set = new Set([1, 2, 3]);
  3. set = new Set([...set].map(val => val * 2));
  4. // set的值是2, 4, 6
  5. // 方法二
  6. let set = new Set([1, 2, 3]);
  7. set = new Set(Array.from(set, val => val * 2));
  8. // set的值是2, 4, 6

二、WeakSet

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注