es6 学习笔记(1)

原文地址:http://es6.ruanyifeng.com/#README

const的数组或者对象,只能保证内存地址不变,不能保证内容不变
de >const a = [];
a.push(‘Hello’); // 可执行
a.length = 0; // 可执行
a = [‘Dave’]; // 报错de>

数组方式赋值
de >let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = [“foo”, “bar”, “baz”];
third // “baz”

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, …tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, …z] = [‘a’];
x // “a”
y // undefined
z // []de>

解构
de >function* fibs() {
var a = 0;
var b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}

var [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5
de>
上面代码中,de >fibsde>是一个Generator函数,原生具有Iterator接口。解构赋值会依次从这个接口获取值。

解构赋值允许指定默认值。

de >var [foo = true] = [];
foo // true

[x, y = ‘b’] = [‘a’]; // x=’a’, y=’b’
[x, y = ‘b’] = [‘a’, undefined]; // x=’a’, y=’b’de>

默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

de >let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // ReferenceErrorde>

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

de >var { bar, foo } = { foo: “aaa”, bar: “bbb” };
foo // “aaa”
bar // “bbb”

var { baz } = { foo: “aaa”, bar: “bbb” };
baz // undefinedde>
果变量名与属性名不一致,必须写成下面这样。

de >var { foo: baz } = { foo: ‘aaa’, bar: ‘bbb’ };
baz // “aaa”

let obj = { first: ‘hello’, last: ‘world’ };
let { first: f, last: l } = obj;
f // ‘hello’
l // ‘world’de>

和数组一样,解构也可以用于嵌套结构的对象。

de >var obj = {
p: [
‘Hello’,
{ y: ‘World’ }
]
};

var { p: [x, { y }] } = obj;
x // “Hello”
y // “World”
de>
注意,这时de >pde>是模式,不是变量,因此不会被赋值。

对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。

de >let { log, sin, cos } = Math;de>

类似数组的对象都有一个de >lengthde>属性,因此还可以对这个属性解构赋值。
de >let {length : len} = ‘hello’;
len // 5de>

函数参数的解构也可以使用默认值。

de >function move({x = 0, y = 0} = {}) {
return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]de>

函数参数的默认值

de >jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// … more config
}) {
// … do stuff
};de>

遍历Map结构

任何部署了Iterator接口的对象,都可以用de >for…ofde>循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。

de >var map = new Map();
map.set(‘first’, ‘hello’);
map.set(‘second’, ‘world’);

for (let [key, value] of map) {
console.log(key + “ is “ + value);
}
// first is hello
// second is worldde>

ES6为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被de >for…ofde>循环遍历。

de >for (let codePoint of ‘foo’) {
console.log(codePoint)
}
// “f”
// “o”
// “o”de>

includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
de >var s = ‘Hello world!’;

s.startsWith(‘Hello’) // true
s.endsWith(‘!’) // true
s.includes(‘o’) // true
de>
这三个方法都支持第二个参数,表示开始搜索的位置。

de >var s = ‘Hello world!’;

s.startsWith(‘world’, 6) // true
s.endsWith(‘Hello’, 5) // true
s.includes(‘Hello’, 6) // falsede>

ES7推出了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。de >padStartde>用于头部补全,de >padEndde>用于尾部补全。

de >’x’.padStart(5, ‘ab’) // ‘ababx’
‘x’.padStart(4, ‘ab’) // ‘abax’

‘x’.padEnd(5, ‘ab’) // ‘xabab’
‘x’.padEnd(4, ‘ab’) // ‘xaba’de>

de >Number.isFinite()de>用来检查一个数值是否为有限的(finite)。

de >Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite(‘foo’); // false
Number.isFinite(‘15’); // false
Number.isFinite(true); // falsede>

ES6引入了de >Number.MAX_SAFE_INTEGERde>和de >Number.MIN_SAFE_INTEGERde>这两个常量,用来表示这个范围的上下限。

de >Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
// true
Number.MAX_SAFE_INTEGER === 9007199254740991
// true

Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER
// true
Number.MIN_SAFE_INTEGER === -9007199254740991
// truede>

de >Number.isSafeInteger()de>则是用来判断一个整数是否落在这个范围之内。

de >Number.isSafeInteger(‘a’) // false
Number.isSafeInteger(null) // false
Number.isSafeInteger(NaN) // false
Number.isSafeInteger(Infinity) // false
Number.isSafeInteger(-Infinity) // false

Number.isSafeInteger(3) // true
Number.isSafeInteger(1.2) // false
Number.isSafeInteger(9007199254740990) // true
Number.isSafeInteger(9007199254740992) // false

Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1) // false
Number.isSafeInteger(Number.MIN_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1) // falsede>

de >Math.truncde>方法用于去除一个数的小数部分,返回整数部分。

de >Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc(-4.9) // -4
Math.trunc(-0.1234) // -0de>

de >Array.ofde>方法用于将一组值,转换为数组。

de >Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1de>

数组实例的de >copyWithinde>方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
de >// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]

// -2相当于3号位,-1相当于4号位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]

// 将3号位复制到0号位
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}

// 将2号位到数组结束,复制到0号位
var i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// Int32Array [3, 4, 5, 4, 5]

// 对于没有部署TypedArray的copyWithin方法的平台
// 需要采用下面的写法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]de>

数组实例的de >findde>方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为de >truede>的成员,然后返回该成员。如果没有符合条件的成员,则返回de >undefinedde>。

de >[1, 4, -5, 10].find((n) => n < 0)
// -5de>
de >fillde>方法使用给定值,填充一个数组。

de >[‘a’, ‘b’, ‘c’].fill(7)
// [7, 7, 7]

new Array(3).fill(7)
// [7, 7, 7]de>

de >Array.prototype.includesde>方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的de >includesde>方法类似。该方法属于ES7,但Babel转码器已经支持。

de >[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, NaN].includes(NaN); // true
de>
该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。

de >[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // truede>

参数默认值可以与解构赋值的默认值,结合起来使用。

de >function foo({x, y = 5}) {
console.log(x, y);
}

foo({}) // undefined, 5
foo({x: 1}) // 1, 5
foo({x: 1, y: 2}) // 1, 2
foo() // TypeError: Cannot read property ‘x’ of undefinedde>

下面是另一个对象的解构赋值默认值的例子。

de >function fetch(url, { body = ‘’, method = ‘GET’, headers = {} }) {
console.log(method);
}

fetch(‘http://example.com', {})
// “GET”

fetch(‘http://example.com')
// 报错
de>
上面代码中,如果函数de >fetchde>的第二个参数是一个对象,就可以为它的三个属性设置默认值。

上面的写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。

de >function fetch(url, { method = ‘GET’ } = {}) {
console.log(method);
}

fetch(‘http://example.com')
// “GET”de>

再请问下面两种写法有什么差别?

de >// 写法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}

// 写法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
de>
上面两种写法都对函数的参数设定了默认值,区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。

de >// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]

// x和y都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]

// x有值,y无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]

// x和y都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]

m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]de>

指定了默认值以后,函数的de >lengthde>属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,de >lengthde>属性将失真。

de >(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
de>
上面代码中,de >lengthde>属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。比如,上面最后一个函数,定义了3个参数,其中有一个参数de >cde>指定了默认值,因此de >lengthde>属性等于de >3de>减去de >1de>,最后得到de >2de>。

这是因为de >lengthde>属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest参数也不会计入de >lengthde>属性。

de >(function(…args) {}).length // 0
de>
如果设置了默认值的参数不是尾参数,那么de >lengthde>属性也不再计入后面的参数了。

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

如果参数的默认值是一个函数,该函数的作用域是其声明时所在的作用域。请看下面的例子。

de >let foo = ‘outer’;

function bar(func = x => foo) {
let foo = ‘inner’;
console.log(func()); // outer
}

bar();
de>
上面代码中,函数de >barde>的参数de >funcde>的默认值是一个匿名函数,返回值为变量de >foode>。这个匿名函数声明时,de >barde>函数的作用域还没有形成,所以匿名函数里面的de >foode>指向外层作用域的de >foode>,输出de >outerde>。

下面是一个更复杂的例子。

de >var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;
y();
console.log(x);
}

foo() // 3
de>
上面代码中,函数de >foode>的参数de >yde>的默认值是一个匿名函数。函数de >foode>调用时,它的参数de >xde>的值为de >undefinedde>,所以de >yde>函数内部的de >xde>一开始是de >undefinedde>,后来被重新赋值de >2de>。但是,函数de >foode>内部重新声明了一个de >xde>,值为de >3de>,这两个de >xde>是不一样的,互相不产生影响,因此最后输出de >3de>。

如果将de >var x = 3de>的de >varde>去除,两个de >xde>就是一样的,最后输出的就是de >2de>。

de >var x = 1;
function foo(x, y = function() { x = 2; }) {
x = 3;
y();
console.log(x);
}

foo() // 2de>

ES6引入rest参数(形式为“…变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

de >function add(…values) {
let sum = 0;

for (var val of values) {
sum += val;
}

return sum;
}

add(2, 5, 3) // 10de>

可变参数调用
de >console.log(…[1, 2, 3])
// 1 2 3

console.log(1, …[2, 3, 4], 5)
// 1 2 3 4 5

[…document.querySelectorAll(‘div’)]
// [

,
,
]de>

Generator函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。

de >var go = function*(){
yield 1;
yield 2;
yield 3;
};

[…go()] // [1, 2, 3]de>

ES6允许使用“箭头”(de >=>de>)定义函数。

de >var f = v => v;
de>
上面的箭头函数等同于:

de >var f = function(v) {
return v;
};
de>
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

de >var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};de>

如果是非尾递归的fibonacci 递归方法

de >function Fibonacci (n) {
if ( n <= 1 ) {return 1};

return Fibonacci(n - 1) + Fibonacci(n - 2);
}

Fibonacci(10); // 89
// Fibonacci(100)
// Fibonacci(500)
// 堆栈溢出了
de>
如果我们使用尾递归优化过的fibonacci 递归算法

de >function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};

return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity
de>
由此可见,“尾调用优化”对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有ECMAScript的实现,都必须部署“尾调用优化”。这就是说,在ES6中,只要使用尾递归,就不会发生栈溢出,相对节省内存。

除了属性简写,方法也可以简写。

de >var birth = ‘2000/01/01’;

var Person = {

name: ‘张三’,

//等同于birth: birth
birth,

// 等同于hello: function ()…
hello() { console.log(‘我的名字是’, this.name); }

};de>

这种写法用于函数的返回值,将会非常方便。

de >function getPoint() {
var x = 1;
var y = 10;
return {x, y};
}

getPoint()
// {x:1, y:10}de>

属性的赋值器(setter)和取值器(getter),事实上也是采用这种写法。

de >var cart = {
_wheels: 4,

get wheels () {
return this._wheels;
},

set wheels (value) {
if (value < this._wheels) {
throw new Error(‘数值太小了!’);
}
this._wheels = value;
}
}de>

ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。

de >let propKey = ‘foo’;

let obj = {
[propKey]: true,
[‘a’ + ‘bc’]: 123
};de>

(4)合并多个对象

将多个对象合并到某个对象。

de >const merge =
(target, …sources) => Object.assign(target, …sources);
de>
如果希望合并后返回一个新对象,可以改写上面函数,对一个空对象合并。

de >const merge =
(…sources) => Object.assign({}, …sources);de>