目录
1. ES6 简介
2. let 和 const
3. 模板字符串
4. 函数默认值和剩余参数
5. 扩展运算符和箭头函数
6. 箭头函数的 this 指向问题
7. 解构赋值
8. 对象的扩展功能
9. Symbol类型
10. Map 和 Set 方法
11. 数组的扩展方法
12. Iterator(遍历器)
13. Generator 函数(生成器)
14. Promise 对象
15. async 异步操作
16. class 类的用法
17. ES6 module(模块化)
参考文献:1、ES6常用API详讲 - 掘金
2、ES6 入门教程
- ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言
- ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”
- 90%的浏览器都支持ES6,也可以通过 babel 编译器(一个 js 编译器)将 ES5 代码 转为 ES6 代码
- let 声明变量没有变量提升,var 有变量提升
- let 是一个块作用域,
let
声明的变量只在它所在的代码块有效。- let 不可以重复声明 ,var 可以重复
- const 声明常量,一旦声明就不能被修改,除此之外也有 let 的三个特性。
建议:开发中,默认情况下用 const,而只有在你知道变量值需要被修改的情况下使用 let
// 例 1
// 底层看到 var ,会自动在前面声明变量,即变量提升,而 let 不会
// 如下代码,会在前面 自动添加 var a; 而不会自动添加 let b;
let a = 10;
var b = 1;
console.log(a) // ReferenceError: a is not defined. 报错
console.log(b) // 1
// 例 2
var a = [];
// 由于变量提升,会在这里自动声明 var = i;
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
// let 是一个块作用域,let声明的变量只在它所在的代码块有效。
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i); // ReferenceError: i is not defined
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
// 例 3
// var 的重复声明
var c = 1;
var c = 3;
console.log(c); //4
//let 不可以重复声明
let d = 1;
let d = 3;
console.log(d); //报错
// 例 4
//const 声明常量,一旦声明就不能被修改
const max = 30;
max = 40;
console.log(max); //会报错
- 模板字符串:使用 tab 键的反引号 `
- 插入变量时:使用 ${ 变量名 }
const oBox = document.querySelector('.box')
let id = 1, name = '牛肉粉';
// ES6 写法
let htmlStr = `
-
${name}
`
oBox.innerHTML = htmlStr;
// ES6 以前的写法
// oBox.innerHTML = "" + name + "
"
// 例 1
// ES5 的写法
function add(a, b){
a = a || 10; //设置默认值
b = b || 20;
return a + b;
}
console.log(add()); // 30
// ES6 的写法
function add(a = 10, b = 20){ //设置默认值
return a + b;
}
console.log(add()); // 30
// 例 2.默认的值也可以是函数
function add(a, b = getVal(5)) {
return a + b;
}
function getVal(val) {
return val + 5;
}
console.log(add(10)); // 20
- 由...和一个紧跟着的具名参数组成 : ...keys
- ...keys 解决了 arguments 的问题
function checkArgs(...args){
console.log(args); // 返回一个真实的实参数组["a", "b", "c"]
console.log(arguments); // 返回一个实参伪数组
}
checkArgs('a','b','c');
剩余参数与扩展运算符的区别:
- 剩余参数:把多个独立的合并到一个数组中
- 扩展运算符:将一个数组分割,并将各个项作为分离的参数传给函数
// Math.max 获取最大值
const arr = [10, 20, 50, 30, 90, 100, 40];
// 扩展运算符更方便
console.log(Math.max(...arr)) // 100
- 使用 => 来定义 , function(){} 等价于 () => {} ,代码变得更加简洁
- 箭头函数内部没有 arguments
- 箭头函数不能使用 new 关键字来实例化对象,function 函数也是一个对象,但是箭头函数不是
let add = function(a, b){
return a + b;
}
// 上下两者写法等价
let add = (a, b) => {
return a + b;
}
var getVal = (a,b) => {
console.log(arguments);
return a + b;
}
console.log(getVal(1,2)); //arguments is not defined
// 箭头函数不能使用 new 关键字来实例化对象,function 函数也是一个对象,但是箭头函数不是
let Person = () => {
};
let p = new Person(); //会报错
- 箭头函数没有this的指向,箭头函数内部的this值只能通过查找作用域链来确定
- 如果箭头函数被一个非箭头函数所包括,那么this的值与该函数的所属对象相等,否则 则是全局的window对象
let PageHandler = {
id:123,
init:function(){
document.addEventListener('click',function(event) {
this.doSomeThings(event.type); // 这里的 this 指向 document
},false);
},
doSomeThings:function(type){
console.log(`事件类型:${type},当前id:${this.id}`);
}
}
PageHandler.init();
//解决this指向问题
let PageHandler = {
id: 123,
init: function () {
// 使用bind来改变内部函数this的指向
document.addEventListener('click', function (event) {
this.doSomeThings(event.type); // 这里的 this 指向 PageHandler
}.bind(this), false);
},
doSomeThings: function (type) {
console.log(`事件类型:${type},当前id:${this.id}`);
}
}
PageHandler.init();
let PageHandler = {
id: 123,
init: function () {
// 箭头函数没有this的指向,箭头函数内部的this值只能通过查找作用域链来确定
// 如果箭头函数被一个非箭头函数所包括,那么this的值与该函数的所属对象相等,否则 则是全局的window对象
document.addEventListener('click', (event) => {
console.log(this);
this.doSomeThings(event.type);
}, false);
},
doSomeThings: function (type) {
console.log(`事件类型:${type},当前id:${this.id}`);
}
}
PageHandler.init();
- 解构赋值是对赋值运算符的一种扩展。它通常针对数组和对象进行操作。
- 优点:代码书写简洁且易读性高
// 例 1 对对象的解构
// 完全解构
let node = {
type:'iden',
name:'foo'
}
let {type,name} = node;
console.log(type,name) //iden foo
// 不完全解构
let obj = {
a:{
name:'张三'
},
b:[],
c:'hello world'
}
//可忽略 忽略b,c属性
let {a} = obj;
//剩余运算符 使用此法将其它属性展开到一个对象中存储
let {a,...res} = obj;
console.log(a,res);
// 默认值 a 的值 20 ,b 的值 30
let {a,b = 30} = {a:20};
// 例 2 对数组的解构
let [a, b] = [1, 2, 3];
console.log(a,b)
//可嵌套
let [a, [b], c] = [1, [2], 3];
对象的方法:
1. Object.is()
- is() 和 ===
- 比较两个值是否严格相等
ES5 比较两个值是否相等,只有两个运算符:相等运算符(
==
)和严格相等运算符(===
)。它们都有缺点,前者会自动转换数据类型,后者的NaN
不等于自身,以及+0
等于-0
。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。
Object.is
就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。2. Object.assign()
Object.assign(target, obj1, obj2...)
方法用于对象的合并,将原对象(obj)的所有属性,复制到目标对象(target)。
// 例 1
const name = '张三';
const age = 19;
const person = {
name, //等同于name:name
age,
// 方法也可以简写
sayName() {
console.log(this.name);
}
}
person.sayName();
// 例 2
function getPoint() {
const x = 1;
const y = 10;
return {x, y};
}
getPoint()
// {x:1, y:10}
// 对象的方法:
// is() 和 ===
// 比较两个值是否严格相等
console.log(NaN === NaN); // false ,===有缺陷
console.log(Obejct.is(NaN, NaN)); // true
// assign()
// 对象的合并
let newObj = Object.assign({}, {a: 1}, {b: 2});
console.log(newObj); // {a: 1, b: 2}
- 它表示的是独一无二的值
- 最大的用途:用来定义对象的私有变量
- 如果用 Symbol 定义的是对象中的变量,取值时一定要用 [变量名]
- 如果用 Symbol 定义的是对象中的变量,该变量不能作为key,无法用 for 循环遍历
注意: 这个数据类型实际开发中用的不多
const name = Symbol('name');
const name2 = Symbol('name');
console.log(name === name2); // false
// 用来定义对象的私有变量
let s1 = Symbol('s1');
console.log(s1);
let obj = {};
obj[s1] = '牛肉粉'; // 等价于 obj = { [s1]: '牛肉粉' }
//如果用 Symbol 定义的是对象中的变量,取值时一定要用 [变量名]
console.log(obj[s1]);
console.log(obj.s1); // 会报错
如果用 Symbol 定义的是对象中的变量,该变量不能作为key,无法用 for 循环遍历
for(let key in obj){
console.log(key); // 没有输出
}
console.log(Object.keys(obj)); // 输出一个空数组 []
Set 集合:表示无重回复值的有序列表
let set = new Set();
// 添加元素 add()
set.add(2);
set.add('4');
set.add('4'); // 这个4会被忽略,因为集合表示无重回复值的有序列表
console.log(set); // set(2) {2, "4"}
// 也可以添加数组
set.add([1, 2, 3]);
// 删除元素 delete()
set.delete(2);
console.log(set);
// 校验某个值是否在 set 中 has()
console.log(set.has('4')); // 返回 true
// 访问集合的长度
console.log(set.size);
// set 转换成 数组
let set2 = new Set([1, 2, 3, 4]);
// 使用扩展运算符
let arr = [...set2];
console.log(arr);
Map:键值对的有序列表,键和值是任意类型
let map = new Map();
// set() 设置值
map.set('name', '张三');
map.set('age', '20');
console.log(map); // 输出 { 'name' => '张三', 'age' => '20'}
// 键和值可以是任意类型
map.set(['a', [1, 2, 3]], 'hello');
// get() 获取值
console.log(map.get('name')); //张三
// has() 校验某个值是否在 map 中
console.log(map.has('name')); //true
// delete() 删除值
map.delete('name');
// clear() 清除所有值
map.clear();
Array.from()
方法用于将伪数组转为真正的数组
// Array.from()方法用于将伪数组转为真正的数组
function add() {
// ES5 的写法,不宜阅读
var arr1 = [].slice.call(argumets); // [1, 2, 3]
console.log(arr1);
// ES6 的写法
let arr2 = Array.from(argumets); // [1, 2, 3]
console.log(arr2);
}
add(1, 2, 3);
// 方法二:使用扩展运算符, 将伪数组转为真正的数组
// querySelectorAll()方法返回的是一个类似数组的对象
let lis = document.querySelectorAll('li');
console.log([...lis]);
// Array.from()方法还可以接受第二个参数,对每个元素进行处理
let lis = document.querySelectorAll('li');
let names2 = Array.from(lis, s => s.textContent);
console.log(names2);
Array.of()
方法用于将一组值,转换为数组
// Array.of()方法用于将一组值,转换为数组
console.log(Array.of(3, 11, 20, [1, 2, 3], {id: 1}));
copyWithin(target, start = 0, end = this.length); 在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组
- target(必需):从该位置开始替换数据。如果为负值,表示倒数。
- start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
这三个参数都应该是数值,如果不是,会自动转为数值。
// 表示将从 3 号位直到数组结束的成员(8,9,10),复制到从 0 号位开始的位置,结果覆盖了原来的 1, 2, 3。
[1, 2, 3, 8, 9, 10].copyWithin(0, 3); // [8, 9, 10, 8, 9, 10];
find() 方法:找出第一个符合条件的数组成员,它的参数是一个回调函数。
findIndex() 方法:找出第一个符合条件的数组成员的索引
let num = [1, 4, -5, 10, -4].find((n) => n < 0);
console.log(num); // -5
let numIdex = [1, 4, -5, 10, -4].findIndex((n) => n < 0);
console.log(numIdex); // 2
ES6 提供三个新的方法——
entries()
,keys()
和values()
——用于遍历数组。它们都返回一个遍历器对象,可以用for...of
循环进行遍历,唯一的区别是:
keys()
是对键名的遍历、values()
是对键值的遍历,entries()
是对键值对的遍历
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
// 如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']
includes() 返回一个布尔值,表示某个数组是否包含给定的值
注意:以前的
indexOf
方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,返回值是1或-1,所以要去比较是否不等于-1
,表达起来不够直观
console.log([1, 2, 3].includes(2)); // true
console.log([1, 2, 3].includes(4)); // false
- 遍历器(Iterator)它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员),快捷的访问数据。
- Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令
for...of
循环,Iterator 接口主要供for...of
消费。- Iterator 的遍历过程是这样的。
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- 第一次调用指针对象的
next
方法,可以将指针指向数据结构的第一个成员。- 第二次调用指针对象的
next
方法,指针就指向数据结构的第二个成员。- 不断调用指针对象的
next
方法,直到它指向数据结构的结束位置。- 每一次调用
next
方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value
和done
两个属性的对象。其中,value
属性是当前成员的值,done
属性是一个布尔值,表示遍历是否结束。
- 原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
let items = ['one', 'two', 'three'];
//创建一个遍历器
let ite = items[Symbol.iterator]();
console.log(ite.next()) // { value: 'one', done: false }
console.log(ite.next()) // { value: 'two', done: false }
console.log(ite.next()) // { value: 'three', done: false }
console.log(ite.next()) // { value: undefined, done: true }
- 上一章说过,
Symbol.iterator
方法,等于该对象的遍历器生成函数,调用该函数会返回一个遍历器对象。而 Generator 函数也是遍历器生成函数,可以把 Generator 函数赋值给对象的Symbol.iterator
属性,从而使得该对象具有 Iterator 接口,给不具备 Iterator 接口的数据结构(不自带Symbol.iterator
方法)提供遍历操作。- 形式上,Generator 函数是一个普通函数,但是有两个特征(区别)。一是,
function
关键字与函数名之间有一个星号 * ;二是,函数体内部使用yield
表达式- Generator 函数是分段执行的,只有调用
next
方法才会遍历下一个内部状态。yield
是暂停执行标志,next()是恢复执行。当 next() 传入参数时,该参数就会被当作上一个 yield 表达式的返回值
// 生成器函数:function关键字与函数名之间有一个星号 * ;函数体内部使用yield表达式
function* add() {
console.log('start');
// 这里的 x 不是 yield '2' 的返回值,它是下一个 next()调用恢复执行时传入的参数值
let x = yield '2'; // yield 暂停执行标志
console.log('one:' + x);
let y = yield '3'; // yield 暂停执行标志
console.log('two:' + y);
return x + y;
}
// 返回一个遍历器对象
let fn = add()
// next() 恢复执行标志
console.log(fn.next()); // start {value: '2', done: false}
// 当 next() 传入参数时,该参数就会被当作上一个 yield 表达式的返回值 ,即 x = 20, y = 30
console.log(fn.next(20)); // one:20 {value: '3', done: false}
console.log(fn.next(30)); // two:30 {value: '50', done: true}
- 使用场景:为不具备 Iterator 接口的对象提供了遍历操作
- 利用
for...of
循环,可以写出遍历任意对象(object)的方法。原生的 JavaScript 对象没有遍历接口(不具备Symbol.iterator
方法),无法使用for...of
循环,通过 Generator 函数为它加上这个接口,即将 Generator 函数加到对象的Symbol.iterator
属性上面,就可以用了。
// Generator 生成器函数
function* objectEntries(obj){
// 获取对象的所有 key 保存到数组 [name, age]
const propKeys = Object.keys(obj);
for(const propkey of propKeys){
yield [propkey, obj[propkey]]
}
}
const obj = {
name: '牛肉粉',
age: 18
}
// 把 Generator 生成器函数赋值给对象的Symbol.iterator属性, 为该对象加上遍历器接口
obj[Symbol.iterator] = objectEntries;
console.log(obj);
// objectEntries(obj) 等价于 obj[Symbol.iterator](obj)
for(let [key, value] of objectEntries(obj)){
console.log(`${key}: $value`);
}
Generator 函数在ajax请求的异步应用,让异步代码同步化
function* main(){
let res = yield request('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976');
console.log(res);
console.log('数据加载完成,可以继续操作');
}
const ite = main();
ite.next();
function request(url){
$.ajax({
url,
method: 'get',
success(res){
ite.next(res);
}
})
}
Generator 函数在加载页面的异步应用
// 1.加载 Loading...页面
// 2.数据加载完成...(异步操作)
// 3.Loading 关闭掉
function loadUI(){
console.log('加载 Loading...页面');
}
function showData(){
// 模拟数据加载异步操作
setTimeout(() => {
console.log('数据加载完成');
},1000);
}
function hideUI(){
console.log('隐藏 Loading...页面');
}
loadUI();
showData();
hideUI();
分析上图:我们的实际需求是加载数据完成后才关闭页面,但由于三个函数函数是同步执行的,数据加载需要时间,在数据未加载完成时,就已经隐藏loading页面。通过 Generator 函数可以解决这个问题
function* load() {
loadUI();
yield showData();
hideUI();
}
const itLoad = load();
itLoad.next();
function loadUI(){
console.log('加载 Loading...页面');
}
function showData(){
// 模拟数据加载异步操作
setTimeout(() => {
console.log('数据加载完成');
// 当数据返回后才执行下一步操作
itLoad.next();
},1000);
}
function hideUI(){
console.log('隐藏 Loading...页面');
}
- 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
- Promise 是一个对象,Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。axios 的内部实现原理就是通过 Promise 实现的
Promise
对象有以下两个特点:
- 对象的状态不受外界影响。有三种状态:
pending
(进行中)、fulfilled(成功)和rejected
(失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为resolved和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果
- Promise对象是一个构造函数,用来生成Promise实例,带有一个回调函数,回调函数的两个参数是 resolve(成功) 和
reject(失败),
这两个参数他们也是函数。then
方法的第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数,它们都是可选的。
function timeOut(ms){
// Promise对象是一个构造函数,用来生成Promise实例
return new Promise((resolve, reject) => { // 闭包函数:一个函数里面返回一个函数
// ... 模拟执行异步操作,后端返回的数据
let res = {
// code: 200,
code: 201,
data:{
name: '牛肉粉'
},
error: '失败了';
}
setTimeout(() => {
// 异步执行成功
if (res.code === 200){
resolve(res.data);
// 异步执行失败
} else {
reject(res.error);
}
}, ms);
});
}
// then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
timeOut(2000).then((val) => {
// success
console.log(val); //这里的值接收的是 resolved() 的值,输出 {name: "牛肉粉"}
}, (err) => {
// failure
console.log(err); //这里的值接收的是 rejected() 的值,输出 失败了
});
const getJson = function(url){
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.resposeType = 'json';
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();
xhr.onreadystatechange = () => {
if(this.readyState === 4){
if(this.status === 200){
resolve(this.respose);
}else{
reject(new Error(this.statusText))
}
}
}
})
}
getJson('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976').then((data) => {
console.log(data);
},(error) => {
console.log(error);
})
- 如果一个promise执行完后 返回的还是一个promise 实例(注意,不是原来那个
Promise
实例),会把这个promise 的执行结果,传递给下一次then
中。因此可以采用链式写法,即then
方法后面再调用另一个then
方法。
// then 方法的链式调用
getJson('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976').then((data) => {
return data.HeWeather6;
},then(HeWeather6) => {
console.log(HeWeather6);
})
catch(err=>{})
方法等价于then(null,err=>{}),
用于指定发生错误时的回调函数
getJSON('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976')
.then((json) => {
console.log(json);
}).then(null,err=>{
console.log(err);
})
//等价于
getJSON('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976')
.then((json) => {
console.log(json);
}).catch(err=>{
console.log(err);
})
resolve()
方法将现有对象转换成Promise对象,该实例的状态为 fulfilled 成功
let p = Promise.resolve('foo');
//等价于let p = new Promise(resolve=>resolve('foo'));
p.then((val)=>{
console.log(val);
})
reject()
方法和resolve()
方法一样返回一个新的Promise实例该实例的状态为 rejected(失败)
let p2 = Promise.reject(new Error('出错了'));
//等价于 let p2 = new Promise((resolve,reject)=>reject(new Error('出错了)));
p2.catch(err => {
console.log(err);
})
all()方法提供了并行执行异步操作的能力,并且再所有异步操作执行完后才执行回调
应用:一些游戏类的素材比较多,等待所有图片,静态资源都加载完成,才进行页面的初始化
案例:试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all实现如下
let pro1 = new Promise((resolve, reject) => {});
let pro2 = new Promise((resolve, reject) => {});
let pro3 = new Promise((resolve, reject) => {});
let pAll = Promise.all([pro1, pro2, pro3]);
pAll.then(() => {
// 三个都成功 才成功
}).cath(err => {
// 如果一个失败 则失败
})
// 案例:试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的的个人信息和好友列表,
// 这两个任务是可以并行执行的,用Promise.all实现如下
let meInfoPro = new Promise( (resolve, reject)=> {
// 模拟资源加载需要花费时间
setTimeout(resolve, 500, 'P1');
});
let youInfoPro = new Promise( (resolve, reject)=> {
// 模拟资源加载需要花费时间
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([meInfoPro, youInfoPro]).then( (results)=> {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
注意:
Promise.all()接受一个promise对象的数组,待全部完成之后,统一执行success;
Promise.race()接受一个包含多个promise对象的数组,只要有一个完成,就执行success
接受一个包含多个promise对象的数组,只要有一个完成,就执行success
举个更具体的例子,加深对race()方法的理解
当我们请求某个图片资源,会导致时间过长,给用户反馈
用race给某个异步请求设置超时时间,并且在超时后执行相应的操作
// 请求图片资源
function requestImg(imgSrc) {
return new Promise((resolve, reject) => {
const img = new Image();
// 加载图片资源
img.onload = function () {
resolve(img);
}
img.src = imgSrc;
});
}
//延时函数,用于给请求计时
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('图片请求超时'));
}, 3000);
});
}
// Promise.race()接受一个包含多个promise对象的数组,只要有一个完成,就执行success
// 如果3s 之内加载完成就执行 then 方法, 3s 之后还没加载完成就执行 catch 方法
Promise.race([requestImg('images/2.png'), timeout()]).then((data) => {
console.log(data);
document.body.appendChild(data);
}).catch((err) => {
console.log(err);
});
async 函数,它就是 Generator 函数的 语法糖
作用:使得异步操作更加方便
异步操作是JavaScript编程的麻烦事,很多人认为async函数是异步编程的解决方案
async function test() {
}
let result = test()
console.log(result) //即便代码里test函数什么都没返回,我们依然打出了Promise对象
async function f() {
/*
** 如果 await 命令后面的不是一个Promise实例对象,
** await 命令会自动把await后面转为一个Promise实例对象
*/
return await 'hello async';
}
// 因为返回的是一个Promise实例对象,所以可以用链式编程
f().then(v => {console.log(v)}).catch(e => {console.log(e)}); //输出 hello async
async function f(){
let s = await 'hello world'
let data = await s.split('');
return data;
}
f().then(v => {console.log(v)}).catch(e => {console.log(e)});
await
语句后面的 Promise 对象变为reject
状态,那么整个async
函数都会中断执行。async function f(){
await Promise.reject('出错了');
await Promise.resolve('hello');
}
f().then(v => {console.log(v)}).catch(e => {console.log(e)}); //输出 出错了
/*
** async函数f执行后,await后面的 Promise 对象会抛出一个错误对象,
** 变成了 reject 状态,catch方法的回调函数被调用
*/
async function f2() {
await new Promise(function (resolve, reject) {
// 抛出一个错误对象
throw new Error('出错了');
});
}
f2().then(v => console.log(v)).catch(e => console.log(e))
// Error:出错了
针对上诉遇到
reject
状态,就中断执行的问题,可以通过 try...catch 代码块解决
async function f() {
try {
await Promise.reject('出错了');
} catch (error){
}
return await Promise.resolve('hello');
});
}
f2().then(v => console.log(v)).catch(e => console.log(e)) // 输出 hello
案例演示:获取和风天气,现在 now 的数据
const getJson = function(url){
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.resposeType = 'json';
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();
xhr.onreadystatechange = () => {
if(this.readyState === 4){
if(this.status === 200){
resolve(this.respose);
}else{
reject(new Error(this.statusText))
}
}
}
})
}
async function getNowWeather(url) {
// 发送 ajax 获取实况天气
let await = getJson(url);
// 获取 HeWeather6 的数据 获取未来 3——7 天的数据
let arr = await res.HeWeather6;
return arr[0].now;
}
getNowWeather('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976')
.then(now => {
console.log(now);
})
// es5
/*
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function() {
return this.sayName;
}
let p = new Person('牛肉粉',18);
console.log(p);
*/
// es6
class Person {
// 实例化的时候会立即被调用
constructor(name, age){
this.name = name;
this.age = age;
}
//等同于Person.prototype = function sayName(){}
sayName(){
return this.name;
}
sayAge(){
return this.age;
}
}
// 小技巧:通过 Objec.assign() 方法(详见对象的扩展功能)一次性向类中添加多个方法
/*
Object.assign(Person.prototype, {
sayName(){
return this.name;
}
sayAge(){
return this.age;
}
})
*/
let p = new Person('牛肉粉',18);
console.log(p);
p.sayName();
p.sayAge();
class Animal {
// 实例化的时候会立即被调用
constructor(name, age){
this.name = name;
this.age = age;
}
sayName(){
return this.name;
}
sayAge(){
return this.age;
}
}
class Dog extends Animal{
constructor(name, age, color){
// 如果 子类 继承了 父类,且 子类 中写了构造器,则 子类 构造器的 super 必须要调用
super(name, age); // 等同于 Animal.call(this,name,age); 继承父类的属性
this.color = color;
}
// 子类自己的方法
sayColor(){
return `${this.name}是${this.age}岁了,它的颜色是${this.color}`;
}
// 重写父类的方法
sayName(){
// super 相同于 Animal
return this.name + super.sayAge + this.color;
}
}
let d1 = new Dog('小黄', 28, 'red');
console.log(d1.sayAge()); // 调用继承父类的方法
console.log(d1.sayColor()); // 调用子类自己的方法
console.log(d1.sayName()); // 调用重写父类的方法
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的
require
、Python 的import
,甚至就连 CSS 都有@import
,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
- ES6 模块功能主要有两个命令构成:export 和 import
export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。- 一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用
export
关键字输出该变量
//module/index.js
export const name = '张三';
export const age = 18;
export const sayName = function() {
console.log(fristName);
}
//也可以这样
const name = '张三';
const age = 18;
const sayName = function() {
console.log(fristName);
}
export {name, age, sayName}
//main.js
import {name, age, sayName} from './modules/index.js';//解构赋值 {name, age, sayName}
使用
export default
命令为模块指定默认输出,在其它模块加载该模块时,import
命令可以为该匿名函数指定任意名字
//export-default.js
export default function(){
console.log('foo');
}
//或者写成
function foo() {
console.log('foo');
}
export default foo;
//import-default.js
import customName from './export-default.js'
customNmae();//foo
如果想在一条import语句中,同时输入默认方法(default)和其他接口(非default),可以写成下面这样
//export-default.js
export default function(){
console.log('foo');
}
export function add(){
console.log('add')
}
import customName,{add} from 'export-default.js'
// 方式二 * 代表所有属性和方法 as 代表重命名
import * as f from 'export-default.js'
console.log(f); // 输出一个 模块, 里面保存了 export 传过来的所有属性和方法
console.log(f.default);
export default
也可以用来输出类。
// MyClass.js
export default class Person{ ... }
// main.js
import Person from 'MyClass';
let o = new Person();