目录
阮一峰:ECMAScript 6入门
学习ES2015
class
箭头函数
ES6允许使用“箭头”(=>)定义函数。
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分;
var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回;
var sum = (num1, num2) => { return num1 + num2; };
大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错;
// 报错 let getTempItem = id => { id: id, name: "Temp" }; // 不报错 let getTempItem = id => ({ id: id, name: "Temp" });
如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了;
let fn = () => void doesNotReturn();
箭头函数可以与变量解构结合使用;
const full = ({ first, last }) => first + ' ' + last; // 等同于 function full(person) { return person.first + ' ' + person.last; }
箭头函数表达更加简洁,可以简化回调函数。
// 正常函数写法 [1,2,3].map(function (x) { return x * x; }); // 箭头函数写法 [1,2,3].map(x => x * x); // 正常函数写法 var result = values.sort(function (a, b) { return a - b; }); // 箭头函数写法 var result = values.sort((a, b) => a - b);
注意
函数体内的this对象,是定义时所在的对象,而不是使用时所在的对象;
this对象的指向是可变的,但是在箭头函数中,它是固定的:
function foo() { setTimeout(() => { console.log('id:', this.id); }, 100); } var id = 21; foo.call({ id: 42 }); // id: 42
不可以当作构造函数,即不可以使用new命令,否则会抛出一个错误;
不可以使用arguments对象,该对象在函数体内不存在,如果要用,可以用rest参数代替;
const numbers = (...nums) => nums; numbers(1, 2, 3, 4, 5) // [1,2,3,4,5] const headAndTail = (head, ...tail) => [head, tail]; headAndTail(1, 2, 3, 4, 5) // [1,[2,3,4,5]]
- 不可以使用yield命令,因此箭头函数不能用作Generator函数。
Promise
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.all()
- Promise.race()
- Promise.resolve()
- Promise.reject()
Promise.try()
Promise基本使用:
// 执行代码 function test(resolve, reject){ var timeOut = Math.random() * 2; console.log('set timeout to:', timeOut, 'seconds.'); setTimeout(function(){ if(timeOut < 1){ console.log('call resolve()...'); resolve('200 OK'); }else{ console.log('call reject()...'); reject('timeout in' + timeOut + 'seconds.'); } }, timeOut * 1000); } var p1 = new Promise(test); // 处理结果的代码 var p2 = p1.then(function(result){ console.log('成功:', result); }); var p3 = p2.catch(function(reason){ console.log('失败:', reason); }); // 也可以这样写,但尽量用上面的写法 // p1.then(function(result){ // console.log('成功:', result); // }, function(reason){ // console.log('失败:', reason); // }) // ↓↓↓ 可简化为 ↓↓↓ new Promese(test).then(function(result){ console.log('成功:', result); }).catch(function(reason){ console.log('失败:', reason); }); // new Promese(test).then(function(result){ // console.log('成功:', result); // }, function(reason){ // console.log('失败:', reason); // }) // Promise最大的好处可以把“执行代码”和“处理结果的代码”清晰的分离开来。
有若干个异步任务,需要先做任务1,1成功后再做任务2,2成功后再做任务3,任何任务失败则不再继续并执行错误处理函数:
// job1、job2、job3都是Promise对象 job1.then(job2).then(job3).catch(handleError);
Promise还可以并行执行异步任务:
var p1 = new Promise(function(resolve, reject){ setTimeout(resolve, 800, 'P1'); }) var p2 = new Promise(function(resolve, reject){ setTimeout(resolve, 600, 'P2'); }) // 同时执行p1和p2,当它们都完成后执行then: Promise.all([p1, p2]).then(function(results){ console.log(results); // 获得一个数组['P1', 'P2'] })
var p1 = new Promise(function(resolve, reject){ setTimeout(resolve, 800, 'P1'); }) var p2 = new Promise(function(resolve, reject){ setTimeout(resolve, 600, 'P2'); }) // 同时执行p1和p2, // p2执行较快,Promise的then()将获得结果'P2', // p1仍在继续执行,但执行结果将被丢弃: Promise.race([p1, p2]).then(function(result){ console.log(result); // 'P2' })
ES2018标准引入 finally 方法,不管Promise对象最后状态如何,都会执行 finally 指定的操作:
var p = new Promise.then(result=>{...})
.catch(error=>{...})
.finally(()=>{...});
// finally 本质上是 then 的特例
解构赋值
模块化
Object.assign(target, ...sources)
- 用于将所有可枚举属性的值从一个或多个源对象sources复制到目标对象target
- 它将返回目标对象target
const object1 = {
a: 1,
b: 2,
c: 3
};
const object2 = Object.assign({c: 4, d: 5}, object1);
console.log(object2.c, object2.d);
// expected output: 3 5
// 合并对象
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
var obj = Object.assign(o1, o2, o3);
console.log(obj);
// { a: 1, b: 2, c: 3 }
console.log(o1);
// { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。
如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。
var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };
var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
Object.assign()拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
function test() {
'use strict';
let obj1 = { a: 0 , b: { c: 0}};
let obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}
obj1.a = 1;
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}}
console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}}
obj2.a = 2;
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 0}}
console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 0}}
obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 3}}
console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 3}}
// Deep Clone
obj1 = { a: 0 , b: { c: 0}};
let obj3 = JSON.parse(JSON.stringify(obj1));
obj1.a = 4;
obj1.b.c = 4;
console.log(JSON.stringify(obj3)); // { a: 0, b: { c: 0}}
}
test();
- 拷贝symbol类型的属性
var o1 = { a: 1 };
var o2 = { [Symbol('foo')]: 2 };
var obj = Object.assign({}, o1, o2);
console.log(obj);
// { a : 1, [Symbol("foo")]: 2 } (cf. bug 1207182 on Firefox)
Object.getOwnPropertySymbols(obj);
// [Symbol(foo)]
- 继承属性和不可枚举属性是不能拷贝的
var obj = Object.create({foo: 1}, { // foo 是个继承属性。
bar: {
value: 2 // bar 是个不可枚举属性。
},
baz: {
value: 3,
enumerable: true // baz 是个自身可枚举属性。
}
});
var copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }
- 异常会打断后续拷贝任务
var target = Object.defineProperty({}, "foo", {
value: 1,
writable: false
}); // target 的 foo 属性是个只读属性。
Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4});
// TypeError: "foo" is read-only
// 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。
console.log(target.bar);
// 2,说明第一个源对象拷贝成功了。
console.log(target.foo2);
// 3,说明第二个源对象的第一个属性也拷贝成功了。
console.log(target.foo);
// 1,只读属性不能被覆盖,所以第二个源对象的第二个属性拷贝失败了。
console.log(target.foo3);
// undefined,异常之后 assign 方法就退出了,第三个属性是不会被拷贝到的。
console.log(target.baz);
// undefined,第三个源对象更是不会被拷贝到的。
- 原始类型会被包装为对象
var v1 = "abc";
var v2 = true;
var v3 = 10;
var v4 = Symbol("foo");
var obj = Object.assign({}, v1, null, v2, undefined, v3, v4);
// 原始类型会被包装,null 和 undefined 会被忽略。
// 注意,只有字符串的包装对象才可能有自身可枚举属性。
console.log(obj);
// { "0": "a", "1": "b", "2": "c" }
- 拷贝访问器
var obj = {
foo: 1,
get bar() {
return 2;
}
};
var copy = Object.assign({}, obj);
// { foo: 1, bar: 2 }
// copy.bar的值来自obj.bar的getter函数的返回值
console.log(copy);
// 下面这个函数会拷贝所有自有属性的属性描述符
function completeAssign(target, ...sources) {
sources.forEach(source => {
let descriptors = Object.keys(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
return descriptors;
}, {});
// Object.assign 默认也会拷贝可枚举的Symbols
Object.getOwnPropertySymbols(source).forEach(sym => {
let descriptor = Object.getOwnPropertyDescriptor(source, sym);
if (descriptor.enumerable) {
descriptors[sym] = descriptor;
}
});
Object.defineProperties(target, descriptors);
});
return target;
}
var copy = completeAssign({}, obj);
console.log(copy);
// { foo: 1, get bar() { return 2 } }
Symbol
最初JS定义了6种基本类型:
- null
- undefined
- boolean
- number
- string
- object(array和function都是属于object的子类)
前五个都是有限集,Object是一个无限集,每一个Object都是独一无二的。
ES6中,新增了一种数据类型symbol:
// 判断
typeof Symbol() === 'symbol' //true
// 特点
Symbol('key') !== Symbol('key') //true
symbol类型的对象永远不相等,即便创建它们的时候传入了相同的值。
因此,可借助此特性解决属性名的冲突问题(比如适用于多人编码的时候),这也是该数据类型存在的主要用途,意为标记。
var sym = Symbol('foo');
var obj = {[sym] : 1};
obj[sym] === 1 //true