learn-es6

ecmascript6

let const

1.for 循环中let可以为循环变量因子创建独立的作用域,在for循环中进行事件绑定很方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
before
var a = [];
for (var i = 0; i < 10; i++) {
(function (i) {
a[i] = function () {
console.log(i)
}
})(i);
}
console.log(a.length)
a[6](); // 6
1
2
3
4
5
6
7
8
9
after
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6

2.不存在变量提升的现象(变量提升发生在var function关键上)

3.暂时性死区现象
es6明确规定,如果一个块级作用域内存在let/const命令,那么这个区块就成了封闭作用域,凡在声明之前就使用这些变量,就会报错

1
2
3
4
5
var temp = 'sd';
if (true) {
temp = 's'; //referenceError
let temp;
}

4.let const不允许重复声明变量

let不允许在同个块级作用域内重复声明

5.const声明一个只读的常量,一旦声明,常量的值就不能改变。

对于引用类型的数据,const保证引用的地址不变,而这个地址储存的数据可以改变。

例如:

1
2
3
4
const foo = [];
foo.push(1);
foo // [1]

如果想对整个引用类型进行保持不变,应该使用Object.freeze

1
const foo = Object.freeze([]);

变量的解构赋值

  1. 数组的解构赋值
1
2
let [v1, v2, ..., vN ] = array;
let [a, b] = [1, 2]

定义变量时可以指定默认值

1
2
3
4
5
let [a = 2, b = 4] = [4]; // a= 4, b=4
let [a = 2, b = 4] = [4, undefined]; // a= 4, b=4
let [a = 2, b = 4] = [4, null]; // a= 4, b=null
// 右边的数组内的值只有是非undefined才能对默认值进行覆盖。

指定默认值时可以取已经定义好的变量

1
2
3
4
5
let [x] = [1];
let [y = x] = []
console.log(x, y) // 1, 1

2.对象的解构赋值

1
2
3
4
5
let foo;
({foo} = {foo: 1}); // 成功
let baz;
({bar: baz} = {bar: 1}); // 成功

解构赋值的变量都是重新定义一个新变量,所以需要在赋值前后加括号,让编译器认为这是一个块级区块。

如果要将一个已经声明过的变量用于解构赋值,需要额外处理

1
2
var x;
({x} = {x: 1});

如果不加括号js引擎会将{x}当做一个代码块从而引发错误。

3.字符串的解构

字符串会被转换成一个类似数组的对象,有一个length属性。

1
2
3
4
5
6
7
8
9
10
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello';
len // 5

4.函数参数的解构

1
2
3
4
5
function move({x = 0, y = 0} = {}) {
return [x, y];
}
console.log(move());

字符串的拓展

1.includes() startsWith() endsWith(‘x’, 5)

返回布尔值,这三个方法都支持第二个参数,表示开始搜索的位置。

endsWith(‘x’, n)这个方法的n表示在n个字符范围内。

数组的拓展

1.Array.from()

将两类对象(类似数组的对象)和可遍历的对象转换成真正的数组。

  1. 选择器选择后的nodeList对象
  2. arguments
  3. set

2.Array.of() 穿件数组

参数传递一个元素 就生成含几个元素的数组。

3.Array.includes() 是否包含某值

函数的拓展

1
2
3
4
5
6
7
8
9
// 写法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}

写法一将参数默认为{}对象,对传进来的属性进行设置默认值。

写法二将参数默认为{x: 0, y:0},但对传进来的属性并没有设置默认值。

区别在与如果函数调用时传进来的参数是{z:0},对于方法一由于对属性设置了默认值,所以return [0, 0]

而对于方法二,return [undefined, undefined]

2.rest参数

rest参数(形式为…变量名) 代理arguments 将一系列参数转为数组

1
2
3
4
5
6
7
8
9
10
11
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10

3.拓展运算符

将数组转变为参数序列,主要用在函数调用上。

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

1
2
3
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]

对于没有部署Iterator接口的类似数组的对象,无法将其转为真正的数组。

1
2
3
4
5
6
7
8
9
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];

4.箭头函数

可以直接返回一个变量 => a

可以返回多条语句 => { xxxx }

可以返回一个对象 => ({a: 3})

函数体内的this对象就是定义时所在的对象,不是调用函数时动态的对象。

对象的拓展

1.属性的简洁表示法

es6允许在变量中,只写属性名,不写属性值。

对象的方法也适合简写

1
2
3
4
{
method () {},
method: function (){}
}

在es6中用字面量定义对象时方法名和属性名都可以用表达式表示。

1
2
3
4
5
6
7
let obj = {
['h'+'ello']() {
return 'hi';
}
};
obj.hello() // hi

但是属性名表达式不能与属性名简洁模式一起用。

1
2
3
4
5
6
7
8
9
10
// 报错
var foo = 'bar';
var bar = 'abc';
var baz = { [foo] };
// 正确
var foo = 'bar';
var baz = {
[foo]: 'abc'
};

Symbol

一种机制,保证一个对象里的每个属性的名字都是独一无二的。

Symbol是es6中一种新的原始数据类型。

1
var a = Symbol('test')

参数只是描述性的字符串,相同的参数返回的Symbol也是不同的。

由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性

1
2
3
4
5
6
7
8
9
10
var mySymbol = Symbol();
// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
var a = {
[mySymbol]: 'Hello!'
};

Symbol()创建出来的实例作为对象的属性时在遍历时无法被识别,需要特殊的方法可以找到这个属性

Iterator(遍历器)和for of 循环

Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口就可以完成遍历操作。这个遍历器主要供for ... of消费使用。凡是部署了Symbol.iterator属性的数据结构,就会返回一个遍历器对象。

es6规定,一个数据结构只要具有symbol.iterator属性,就可以认为是可遍历的。

在es6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构

or…of循环可以使用的范围包括数组、Set和Map结构、某些类似数组的对象(比如arguments对象、DOM NodeList对象)、后文的Generator对象,以及字符串。

es6的数组 set map都部署了以下三个方法,调用后返回遍历器对象。

  • entries() 返回一个遍历器对象,用来遍历[键名,键值]组成的数组。
  • keys() 用来遍历所有的键名
  • values() 用来遍历所有的键值。
1
2
3
4
let arr = ['a', 'b', 'c'];
for (let [x,y] of arr.entries()) {
console.log(y); // a b c
}

对象没有部署iterator接口,可以采取以下方式进行hack

1
2
3
for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}

另外for of 不同用于forEach方法,它可以与break、continue和return配合使用。

Generator

generator函数是es6提供的一种异步编程解决方案,语法行为和传统函数完全不同。

generator函数是一个状态机,封装了多个内部状态,执行generator函数会返回一个遍历器对象,该对象可以依次遍历generator函数内部的每一个状态。

1
2
3
4
5
6
7
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();

hw不是generator函数的返回值,而是一个遍历器对象,这个对象有有next方法,可以遍历generator内部的状态。

1
2
3
4
5
6
7
8
9
10
11
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

由于generator函数返回的遍历器对象只有调用next方法才会遍历下一个内部状态,所以提供了一个可以暂停执行的函数。yield语句就是暂停标志。yield在表达式中需要加括号包住

yield语句本身没有返回值,总是返回undefined。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

next方法的参数表示上一个yield语句的返回值,所以第一次使用next方法是不能带有参数。

调用generator函数生成的遍历器对象可以直接使用for..of遍历方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}

第一个遍历器对象i抛出的错误被generator函数内部的catch捕获,第二次i又抛出错误但事后generator已经处理过了就会省略,然后就变成函数体外的catch捕获到错误。

如果generator函数内部没有部署try catch代码块,那么遍历器对象抛出的错误有=由外部的try catch代码块捕获。

2.Generator.prototype.return()

generator函数返回的遍历器对象还有一个return方法,可以返回给定的值,并且终结遍历generator函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers()
g.next() // { done: false, value: 1 }
g.next() // { done: false, value: 2 }
g.return(7) // { done: false, value: 4 }
g.next() // { done: false, value: 5 }
g.next() // { done: true, value: 7 }

3.yield* 语句

如果要嵌套generator函数 需要在引用generator函数的前方定义yield*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield foo();
yield 'y';
}
var gen = bar();
var a = gen.next();
var b = gen.next().value;
for(let bb of b) {
console.log(bb) // a b
}

yield不加*号引用generator函数时,返回值是一个遍历器对象,可以使用for of进行遍历。

如果加了*号 返回的就是计算后的值,见:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
var gen = bar();
for(let b of gen) {
console.log(b) // x a b y
}

实际上,任何数据结构只要有Iterator接口,就可以被yield*遍历。

1
2
3
4
5
6
7
let read = (function* () {
yield 'hello';
yield* 'hello';
})();
read.next().value // "hello"
read.next().value // "h"

4.generator应用

(一)异步操作的同步化表达

1
2
3
4
5
6
7
8
9
10
11
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()
// 卸载UI
loader.next()

一下ajax请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}
var it = main();
it.next();

Module

require依赖是运行时加载,只有在运行时才能得到这个对象。

1
2
3
4
5
6
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;

比如此时项目文件内已经有了整个fs的对象,再去引用fs对象中的三个方法。这样做非常浪费资源。

而es6模块不是对象,而是通过export命令显示指定输出的代码。

1
2
// ES6模块
import { stat, exists, readFile } from 'fs';

上面代码的实质是从fs模块加载3个方法,其他的方法不加载。这种加载称为「编译时加载」,即es6可以在编译时就完成了模块的加载。

2.export命令

export命令用于规定模块的对外接口,import用来输入其他模块提供的功能。

1
2
3
4
5
6
7
8
9
10
// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};

export会将变量与模块之间的变量一一绑定,所以当模块内的变量发生变化,export出来的变量也会实时变化。

3.import

1
import {a, b} from 'module'
1
2
import 'lodash'
// 上述代码执行lodash模块,但是不输入任何值

4.模块的整体加载

*号指定一个对象,所有输出值都加载在这个对象上面。

1
2
3
4
5
6
7
8
9
10
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
1
2
3
4
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

5.export default命令

export default是输出一个默认值,这样在import时就不需要知道名字直接重命名进行引用,import的方式有些改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 输出
export default function crc32() {
// ...
}
// 输入
import whatever from 'crc32';
// 输出
export function crc32() {
// ...
};
// 输入
import {crc32} from 'crc32';