接上节Promise。
Ajax
Ajax是网页与服务端进行数据交互的一种技术,我们可以通过服务端提供的接口,用Ajax想服务端请求我们需要的数据。
// 简单的Ajax原生实现
// 由服务端提供的接口
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
var result;-
var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);
XHR.send();
XHR.onreadystatechange = function () {
if (XHR.readyState == 4 && XHR.status == 200) {
retult = XHR.response;
console.log(result);
}
}
在Ajax的原生实现中,利用了onreadystatechange事件,只有当该事件触发并且符合一定条件时,才能拿到我们想要的数据,之后才能开始处理数据。
但是,当Ajax中嵌套新的Ajax请求时,我们不得不不停地嵌套毁掉函数,以确保下一个接口所需要的参数的正确性。这样的灾难,我们称之为回调地狱。
此时,我们就需要一个叫做Promise的语法来解决这样的问题。
var tag = true;
// new Promise创建一个Promis实例,Promise函数中的第一个参数为一个回调函数,通常情况下,在这个函数中,会执行发起请求操作
// 请求结果有三种状态,分别是pending(等待中,表示还没有得到结果),resolved(得到了我们想要的结果,可以继续执行),以及rejected(得到了错误的,或者不是我们期望的结果,拒绝执行)
// 回调函数中,分别使用resolve与reject将状态修改为对应的resolved与rejected,resolve、reject是回调函数的两个参数,它们能将请求结果的具体数据传递出去
var p = new Promise(function (resolve, reject) {
if (tag) {
resolve('tag is true');
} else {
reeject('tag is false');
}
})
// Promise实例拥有的then方法,可用来处理当请求结果的状态变成resolved时的逻辑,then的第一个参数为一个回调函数,该函数的参数是resolve传递出来的数据,这里是tag is true
// Promise实例拥有的catch方法,可用来处理当请求结果的状态变成rejected时的逻辑,catch的第一个参数为一个回调函数,该函数的参数是reject传递出来的数据,这里是tag is false
p.then(function (result) {
console.log(result);
}).catch(function (error) {
console.log(error);
})
经过简单介绍,我们通过几个例子来感受一下Promise的用法。
例子1:
function fn (num) {
return new Promise(function (resolve, reject) {
if (typeof num == 'number') {
resolve();
} else {
reject();
}
}).then(function () {
console.log('参数是一个number值');
}).catch(function () {
console.log('参数不是一个number值');
})
}
fn('12');
console.log('next code'); //先输出 next code,再输出参数不是一个number值
例子2:
function fn (num) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (typeof num == 'number') {
resolve(num);
} else {
var arr = num + ' is not a number';
reject(arr);
}
}, 2000)
}).then(function (resp) {
console.log(resp);
}).catch(function (arr) {
console.log(arr);
})
}
fn('abc');
console.log('next code'); // 先输出next code,2s后输出 abc is not a number
我们将最开始的Ajax原生请求进行简单的封装。
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
function getJSON (url) {
return new Promise(function (resolve, reject) {
var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);
XHR.send();
XHR.onreadystatechange = function () {
if (XHR.readyState == 4) {
if (XHR.status == 200) {
try {
var response = JSON.parse(XHR.responseText);
resolve(response);
} catch (e) {
reject(e);
}
} else {
reject(new Error(XHR.statusText));
}
}
}
})
}
getJSON.then(function (resp) {
console.log(resp);
})
Promise.all
当有一个Ajax请求,它的参数需要另外两个甚至更多个请求都有返回结果之后才能确定时,就需要用到Promise.all来帮助我们应对这个场景。
Promise.all接收一个Promise对象组成的数组座位参数,当这个数组中所有的Promise对象状态都变成resolved或者rejected时,它才回去调用then方法。
var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
var url1 = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
function renderAll () {
return Promise.all([getJSON(url), getJSON(url1)]);
}
renderAll.then(function (value) {
console.log(value);
})
Promise.race
与Promise.all相似的是,Promise.race也是以一个Promise对象组成的数组作为参数,不同的是,只要当数组中的其中一个Promise状态变成resolved或者rejected时,就可以调用then方法,而传递给then方法的值也会有所不同。
async/await
异步问题不仅可以使用前面学到的Promise解决,还可以用async/await来解决。
async/await是ES7中新增的语法,虽然现在最新的Chrome浏览器已经支持了该语法,但在实际使用中,仍然需要在构建工具中配置对该语法的支持才能放心使用。
async function fn () {
return 30;
}
const fn = async () => {
return 30;
}
console.log(fn()); // Promise {<resolved>: 30}
可以发现fn函数运行后返回的是一个标准的Promise对象,因此可以猜想到async其实是Promise的一个语法糖,目的是为了让写法更加简单,因此也可以使用Promise的相关语法来处理后续的逻辑。
fn().then(res => {
console.log(res); // 30
})
await的含义是等待,意思就是代码需要等待await后面的函数运行完并且有了返回结果之后,才继续执行下面的代码,这正是同步的效果。
需要注意的是,await关键字只能在async函数中使用,并且await后面的函数运行必须返回一个Promise对象才能实现同步的效果。
function fn () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(30);
}, 1000);
})
}
const foo = async () => {
const t = await fn();
console.log(t);
console.log('next code');
}
foo(); // 先输出Promise {<pending>},然后输出30,最后输出next code
通过运行这个例子可以看出,在async函数中,当运行遇到await时,就会等待await后面的函数运行完毕,而不会直接执行next code。
6. 事件循环机制
先看两个简单的例子,例子1:
setTimeout(function () {
console.log(1);
}, 0);
console.log(2);
for (var i = 0; i < 5; i++) {
console.log(3);
}
console.log(4);
// 依次输出 2 3 3 3 3 3 4 1
例子2:
console.log(1);
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log('2-' + i);
}, 0);
}
console.log(3);
// 依次输出 1 3 2-5 2-5 2-5 2-5 2-5
很多人在运行之后可能感到困惑,为什么即使设置了setTimeout的延迟事件为0,它里面的代码仍然是最后执行的?
通常情况下,决定代码执行顺序的是函数调用栈。很明显这里的setTimeout中的执行顺序已经不是用函数调用栈能够解释清楚的了,这是因为队列。
JavaScript的一个特点是单线程,但是很多时候我们仍需要在不同的事件去执行不同的任务,例如给元素添加点击事件,设置一个定时器,或者发起Ajax请求。因此需要一个异步机制来达到这样的目的,事件循环机制也因此而来。
每一个JavaScript程序都拥有唯一的事件循环,大多数代码的执行顺序是可以根据函数调用栈的规则执行的,而setTimeout/setIInterval或者不同的事件绑定(click等)中的代码,则通过队列来执行。
setTimeout为任务源,或者任务分发器,由它们讲不通的任务分发到不同的任务队列中去。每个任务源都有对应的任务队列。
任务队列又分为宏任务(macro-task)与微任务(micro-task)两种,在浏览器中,宏任务包括script,setTimeout/setInterval,I/O,UI rendering等,微任务包括Promise。
(这里写的不好,待我再看看补上)
7. 对象与class
ES6针对对象的写法新增了一些语法简化的写法。
1)当属性与变量同名时
const name = 'Jane';
const age = 20;
// ES6
const person = {
name,
age
}
// 等价于ES5
var person = {
name: 'Jane',
age: 20
}
这样的写法在很多地方都能见到。
const getName = () => person.name;
const getAge = () => person.age;
// commonJS的方式
module.exports = {getName, getAge}
// ES6 modules的方式
export default {getName, getAge}
2)对象中方法的简写
// ES6
const person = {
name,
getName () {
return this.name;
}
}
// ES5
var person = {
name: name,
getName: function getName () {
return this.name;
}
}
3)可以使用变量作为对象的属性,只需用中括号[]包裹即可
const name = 'Jane';
const age = 20;
const person = {
[name]: true,
[age]: true
}
class
ES6为创建对象提供了新的语法class。
// ES5
function Person (name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function () {
return this.name;
}
// ES6
class Person {
constructor (name, age) { // 构造方法
this.name = name;
this.age = age;
}
getName () { // 原型方法
return this.name;
}
static a = 20; // 等同于Person.a = 20
c = 20; // 表示在构造函数中添加属性,在构造函数中等同于this.c = 20
getAge = () => this.age; // 箭头函数的写法表示在构造函数中添加方法,在构造函数中等同于this.getAge = function () {}
}
继承
与ES5相比,ES6的继承要简单的多。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
getName() {
return this.name;
}
}
class Student extends Person {
constructor(name, age, gender, classes) {
super(name, age);
this.gender = gender;
this.classes = classes;
}
getGender() {
return this.gender;
}
}
const s = new Student('Tom', 20, 1, 3);
a.getName(); // Tom
a.getGender(); // 1
子类的构造函数中必须调用super方法,它表示构造函数的继承。
8. 模块化
import
通过import指令,可以在当前模块中引入其他模块。
import registerServiceWorker from './registerServiceWorker';
registerServiceWorker();
- import表示引入/加载一个模块
- registryServiceWorker可以理解为这个模块的名字
- from表示模块来自于哪里
- 当引入.js文件时,可以省略文件后缀名
export
export提供对外接口,常配合import一起使用。
export const name1 = 'Tom';
export const name2 = 'Jake';
import {name1} from './xx';
还可以通过export default来对外提供接口,这种情况下,对外接口通常是一个对象。
const name1 = 'Tom';
const name2 = 'Jake';
export default {name1, name2}
关于ES6模块化的知识,更多请阅读阮一峰大神的ECMAScript 6入门。
以上是我对JavaScript核心技术开发解密第十章(下)的读书笔记,码字不易,请尊重作者版权,转载注明出处。至此,本书全部十章笔记都已写完,中间有拉下的我会晚些补上,再次感谢牛客网提供这样的机会。
By BeLLESS 2018.8.13 22:56