复习ES(6-11)语法之ES6下篇
异步操作前置知识
- JS是单线程的
单线程即一个时间只能处理一个任务。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
- 同步任务与异步任务
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
const a = 2
const b = 3
console.log(a + b) // 同步任务
// 异步任务
setTimeout(() => { // 延迟1s执行
console.log(a + b)
}, 1000)
console.log(1)
setTimeout(() => {
console.log(2)
}, 1000)
console.log(3)
// 1 3 2
console.log(1)
//不管延迟时间是多少,它都是异步任务,必须等主线程任务执行完成之后再执行
setTimeout(() => {
console.log(2)
}, 0)
console.log(3)
//1 3 2
- Ajax原理
Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发送异步请求,从服务器获得数据,然后用JavaScript来操作DOM而更新页面。这其中最关键的一步就是从服务器获得请求数据。
function ajax(url, callback) {
// 1、创建XMLHttpRequest对象
var xmlhttp;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else {
// 兼容早期浏览器
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2、发送请求
xmlhttp.open("GET", url, true); // 指定发送请求的操作
xmlhttp.send(); // 发送
// 3、服务端响应
// 监听onreadystatechange 方法
xmlhttp.onreadystatechange = function () {
// 事件处理函数
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText);
// console.log(obj)
callback(obj); // 将响应得到的数据传给回调,回调函数是自己定义并传入的
}
};
}
//获取随机猫咪照片
var url = "https://api.thecatapi.com/v1/images/search?limit=1";
ajax(url, (res) => {
console.log(res);
});
- Callback Hell
回调函数可规范调用的顺序,但是当代码层层嵌套越写越深,代码的可维护性、可读性都会降低,就会造成Callback Hell
// 1 -> 2 -> 3
// callback hell
ajax('static/a.json', res => {
console.log(res)
ajax('static/b.json', res => {
console.log(res)
ajax('static/c.json', res => {
console.log(res)
})
})
})
Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
let p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("zzz");
resolve();
}, 1000);
}).then(
(res) => {
console.log("成功");
},
(err) => {
console.log("失败");
}
);
如果Promise内部没有写任何异步操作,那么它是会立即执行的。then方法相当于promise的回调函数(它的微任务),待promise内的函数执行完成便执行。
let p = new Promise((resolve, reject) => {
console.log(1);
resolve();
});
p.then((res) => {
console.log(3);
});
console.log(2);
//依次输出:1 2 3
Promise状态一旦确定下来就无法再改变
let p = new Promise((resolve, reject) => {
resolve(1);
reject(2);
});
p.then(
(res) => {
console.log(res); // 1
},
(err) => {
console.log(err);
}
);
改造回调深渊Callback Hell
// 传入成功回调与失败回调
function ajax(url, successCallback, failCallback) {
// 1、创建XMLHttpRequest对象
var xmlhttp;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else {
// 兼容早期浏览器
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2、发送请求
xmlhttp.open("GET", url, true);
xmlhttp.send();
// 3、服务端响应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText);
// console.log(obj)
successCallback && successCallback(obj);
} else if (xmlhttp.readyState === 4 && xmlhttp.status === 404) {
failCallback && failCallback(xmlhttp.statusText);
}
};
}
function getPromise(url) {
return new Promise((resolve, reject) => {
ajax(
url,
(res) => {
resolve(res);
},
(err) => {
reject(err);
}
);
});
}
getPromise("static/a.json")
.then((res) => {
console.log(res);
return getPromise("static/b.json");
})
.then((res) => {
console.log(res);
return getPromise("static/c.json");
})
.then((res) => {
console.log(res);
});
静态方法:
- Promise.resolve() 表示成功的状态
let p1 = Promise.resolve("success");
p1.then((res) => {
console.log(res); // success
});
- Promise.reject() 表示失败的状态
let p2 = Promise.reject("fail");
p2.catch((err) => {
console.log(err); // fail
});
- Promise.all() 传入一个数组作为参数,数组内的每一个内容都对应一个Promise对象
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1);
resolve("1成功");
}, 1000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(2);
//resolve("2成功");
reject('2失败')
}, 2000);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(3);
resolve("3成功");
}, 3000);
});
//p1,p2,p3都resolve了才执行后面的then,只要有一个失败了就进入失败状态
Promise.all([p1, p2, p3]).then((res) => {
console.log(res);
},err=>{
console.log(err)
});
Promise.all(下文都称p)的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
- Promise.race() 将多个 Promise 实例,包装成一个新的 Promise 实例
//只要有一个率先完成,那整个状态就是完成的,只要有一个率先失败,那整个状态就是失败的
Promise.race([p1, p2, p3]).then(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
);
只要p1、p2、p3之中有一个实例率先改变状态,Promise.race的状态就跟着改变。
Promise的应用场景举例:
①上传图片
const imgArr = ["1.jpg", "2.jpg", "3.jpg"];
let promiseArr = [];
imgArr.forEach((item) => {
promiseArr.push(
new Promise((resolve, reject) => {
//图片上传的操作
resolve();
})
);
});
Promise.all(promiseArr).then((res) => {
console.log("图片全部上传成功");
});
②加载图片
function getImg() {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = function () {
resolve(img.src);
};
// img.src = "http://www/xxx.com/xx.jpg";
img.src = "https://cdn2.thecatapi.com/images/6fk.jpg";
});
}
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("图片请求超时");
}, 2000);
});
}
Promise.race([getImg(), timeout()])
.then((res) => {
console.log("加载图片成功", res);
})
.catch((err) => {
console.log(err);
});
Generator
原理
- 从语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。 - 形式上,Generator 函数是一个普通函数,但是有两个特征。一是, function 关键字与函数名之间有一个星号;二是,函数体内部使用 yield 表达式, 定义不同的内部状态( yield 在英语里的意思就是“产出”)。
- Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。
- 必须调用遍历器对象的 next 方法(next方法可以传递参数),使得指针移向下一个状态。也就是说,每次调用 next 方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式(或 return 语句)为止。换言之,Generator 函数是分段执行的, yield 表达式是暂停执行的标记,而 next 方法可以恢复执行。
用法
Generator是可以暂停的,需要调用next方法手动执行, yield指令只能在生成器内部使用。
由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。
遍历器对象的next方法的运行逻辑如下。
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
//普通函数
function foo() {
for (let i = 0; i < 3; i++) {
console.log(i);
}
}
foo(); // 直接输出 0,1,2
// Generator
function* _foo() {
for (let i = 0; i < 3; i++) {
yield i;
}
}
let f = _foo();
// 通过next手动执行
console.log(f.next()); // { value: 0, done: false }
console.log(f.next()); // { value: 1, done: false }
console.log(f.next()); // { value: 2, done: false }
console.log(f.next()); // { value: undefined, done: true }
// yield指令只能在生成器内部使用
// function* gen(args) {
// args.forEach(item => {
// yield item + 1
// })
// }
需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
function* gen(x) {
let y = 2 * (yield x + 1);
let z = yield y / 3;
return x + y + z;
}
let g = gen(5);
console.log(g.next().value); // 6
// 上一次yield表达式没有返回y值,所以变成y = 2* undefined => NaN
console.log(g.next().value); // NaN
console.log(g.next().value); // NaN y / 3 => NaN
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
//①
let g = gen(5);
// x+1 = 6
console.log(g.next().value);// 输出6
// 传递参数6,前面的 (yield x + 1) = 6,所以y = 2* 6 = 12, y /3 = 4
console.log(g.next(6).value); //输出4
// 传递参数4,前面的 ( yield y / 3 ) = 4,所以z = 4,return x+y+z = 5+12+4 = 21
console.log(g.next(4).value); //输出21
//②
let g = gen(5);
// x+1 = 6
console.log(g.next().value); //输出6
//传递参数8,前面的 (yield x + 1) = 8,所以y = 2* 8 = 16, y /3 = 5.3333
console.log(g.next(8).value); //输出5.3333
// 传递参数12,前面的(yield y /3 ) = 12,所以z = 12, return x+y+z = 5+16+12 = 33
console.log(g.next(12).value); //输出33
可以写一个计数器,每次遇到7的倍数就停止执行
//计数器 遇到7的倍数就停止
function* count(x = 1) {
while (true) {
if (x % 7 === 0) {
yield x;
}
x++;
}
}
let n = count();
console.log(n.next().value); // 7
console.log(n.next().value); // 14
console.log(n.next().value); // 21
console.log(n.next().value); // 28
console.log(n.next().value); // 35
异步状态管理
以ajax为例,请求顺序a→b→c
function ajax(url, callback) {
// 1、创建XMLHttpRequest对象
var xmlhttp;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else {
// 兼容早期浏览器
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
// 2、发送请求
xmlhttp.open("GET", url, true);
xmlhttp.send();
// 3、服务端响应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText);
// console.log(obj)
callback(obj);
}
};
}
// 封装请求方法,调用ajax
function request(url) {
ajax(url, (res) => {
getData.next(res); // 每一次request请求都会调用next,使Generator对象继续执行
});
}
function* gen() {
let res1 = yield request("static/a.json");
console.log(res1);
let res2 = yield request("static/b.json");
console.log(res2);
let res3 = yield request("static/c.json");
console.log(res3);
}
let getData = gen();
getData.next();
Iterator
Iterator是一种接口,为各种不同的数据结构提供统一的访问机制,使得不支持遍历的数据结构“可遍历”。Iterator 接口主要供for...of消费。
Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
function makeIterator(arr) {
let nextIndex = 0;
return {
next() {
return nextIndex < arr.length
? {
value: arr[nextIndex++],
done: false,
}
: {
value: undefined,
done: true,
};
},
};
}
let it = makeIterator(["a", "b", "c"]);
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
原生具备 Iterator 接口的数据结构
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
下面的例子是数组和Map的Symbol.iterator属性。
let arr = ['a', 'b', 'c'] // 对于数组这样的可迭代结构,它里面自带了Symbol.iterator
console.log(arr) // ["a", "b", "c"]
let it = arr[Symbol.iterator]()
console.log(it.next()) // {value: "a", done: false}
console.log(it.next())
console.log(it.next())
console.log(it.next())// {value: undefined, done: true} ,迭代结束
let map = new Map()
map.set('name', 'es')
map.set('age', 5)
let it = map[Symbol.iterator]()
console.log(it.next())
console.log(it.next())
console.log(it.next())
将不可迭代对象改造成符合上述两种协议的结构,便能够实现遍历:
let courses = {
allCourse: {
fronted: ["ES", "Vue", "小程序"],
backend: ["JAVA", "Python"],
webapp: ["Android", "IOS"],
},
};
for (let c of courses) {
console.log(c); // 报错,不可迭代
}
// 可迭代协议:Symbol.iterator
// 迭代器协议:return { next(){ return { value,done } } }
courses[Symbol.iterator] = function () {
let allCourse = this.allCourse;
let keys = Reflect.ownKeys(allCourse);
let values = [];
return {
next() {
if (!values.length) {
if (keys.length) {
values = allCourse[keys[0]];
keys.shift();
}
}
return {
done: !values.length,
value: values.shift(),
};
},
};
};
for (let c of courses) {
console.log(c); // 可迭代
}
Generator遍历不可迭代对象
Generator自带next方法,所以它也能够帮助不可迭代对象遍历。
courses[Symbol.iterator] = function* () {
let allCourse = this.allCourse;
let keys = Reflect.ownKeys(allCourse);
let values = [];
while (1) {
if (!values.length) {
if (keys.length) {
values = allCourse[keys[0]];
keys.shift();
yield values.shift();
} else {
return false;
}
} else {
yield values.shift();
}
}
};
for (let c of courses) {
console.log(c); // 报错,不可迭代
}
模块化规范
CommonJS:Node.js
AMD:require.js
CMD:sea.js
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
import { a } from "./module.js";
console.log(a);
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。
import { a as aa } from "./module.js";
console.log(aa);
使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
// module.js
const a = 5;
export default a;
//index.js
import aa from "./module.js";
console.log(aa);
export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default命令。

浙公网安备 33010602011771号