接上節(jié)Promise。
Ajax
Ajax是網(wǎng)頁與服務(wù)端進行數(shù)據(jù)交互的一種技術(shù),我們可以通過服務(wù)端提供的接口,用Ajax想服務(wù)端請求我們需要的數(shù)據(jù)。
// 簡單的Ajax原生實現(xiàn)
// 由服務(wù)端提供的接口
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的原生實現(xiàn)中,利用了onreadystatechange事件,只有當該事件觸發(fā)并且符合一定條件時,才能拿到我們想要的數(shù)據(jù),之后才能開始處理數(shù)據(jù)。
但是,當Ajax中嵌套新的Ajax請求時,我們不得不不停地嵌套毀掉函數(shù),以確保下一個接口所需要的參數(shù)的正確性。這樣的災(zāi)難,我們稱之為回調(diào)地獄。
此時,我們就需要一個叫做Promise的語法來解決這樣的問題。
var tag = true;
// new Promise創(chuàng)建一個Promis實例,Promise函數(shù)中的第一個參數(shù)為一個回調(diào)函數(shù),通常情況下,在這個函數(shù)中,會執(zhí)行發(fā)起請求操作
// 請求結(jié)果有三種狀態(tài),分別是pending(等待中,表示還沒有得到結(jié)果),resolved(得到了我們想要的結(jié)果,可以繼續(xù)執(zhí)行),以及rejected(得到了錯誤的,或者不是我們期望的結(jié)果,拒絕執(zhí)行)
// 回調(diào)函數(shù)中,分別使用resolve與reject將狀態(tài)修改為對應(yīng)的resolved與rejected,resolve、reject是回調(diào)函數(shù)的兩個參數(shù),它們能將請求結(jié)果的具體數(shù)據(jù)傳遞出去
var p = new Promise(function (resolve, reject) {
if (tag) {
resolve('tag is true');
} else {
reeject('tag is false');
}
})
// Promise實例擁有的then方法,可用來處理當請求結(jié)果的狀態(tài)變成resolved時的邏輯,then的第一個參數(shù)為一個回調(diào)函數(shù),該函數(shù)的參數(shù)是resolve傳遞出來的數(shù)據(jù),這里是tag is true
// Promise實例擁有的catch方法,可用來處理當請求結(jié)果的狀態(tài)變成rejected時的邏輯,catch的第一個參數(shù)為一個回調(diào)函數(shù),該函數(shù)的參數(shù)是reject傳遞出來的數(shù)據(jù),這里是tag is false
p.then(function (result) {
console.log(result);
}).catch(function (error) {
console.log(error);
})
經(jīng)過簡單介紹,我們通過幾個例子來感受一下Promise的用法。
例子1:
function fn (num) {
return new Promise(function (resolve, reject) {
if (typeof num == 'number') {
resolve();
} else {
reject();
}
}).then(function () {
console.log('參數(shù)是一個number值');
}).catch(function () {
console.log('參數(shù)不是一個number值');
})
}
fn('12');
console.log('next code'); //先輸出 next code,再輸出參數(shù)不是一個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請求,它的參數(shù)需要另外兩個甚至更多個請求都有返回結(jié)果之后才能確定時,就需要用到Promise.all來幫助我們應(yīng)對這個場景。
Promise.all接收一個Promise對象組成的數(shù)組座位參數(shù),當這個數(shù)組中所有的Promise對象狀態(tài)都變成resolved或者rejected時,它才回去調(diào)用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對象組成的數(shù)組作為參數(shù),不同的是,只要當數(shù)組中的其中一個Promise狀態(tài)變成resolved或者rejected時,就可以調(diào)用then方法,而傳遞給then方法的值也會有所不同。
async/await
異步問題不僅可以使用前面學到的Promise解決,還可以用async/await來解決。
async/await是ES7中新增的語法,雖然現(xiàn)在最新的Chrome瀏覽器已經(jīng)支持了該語法,但在實際使用中,仍然需要在構(gòu)建工具中配置對該語法的支持才能放心使用。
async function fn () {
return 30;
}
const fn = async () => {
return 30;
}
console.log(fn()); // Promise {<resolved>: 30}
可以發(fā)現(xiàn)fn函數(shù)運行后返回的是一個標準的Promise對象,因此可以猜想到async其實是Promise的一個語法糖,目的是為了讓寫法更加簡單,因此也可以使用Promise的相關(guān)語法來處理后續(xù)的邏輯。
fn().then(res => {
console.log(res); // 30
})
await的含義是等待,意思就是代碼需要等待await后面的函數(shù)運行完并且有了返回結(jié)果之后,才繼續(xù)執(zhí)行下面的代碼,這正是同步的效果。
需要注意的是,await關(guān)鍵字只能在async函數(shù)中使用,并且await后面的函數(shù)運行必須返回一個Promise對象才能實現(xiàn)同步的效果。
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函數(shù)中,當運行遇到await時,就會等待await后面的函數(shù)運行完畢,而不會直接執(zhí)行next code。
6. 事件循環(huán)機制
先看兩個簡單的例子,例子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
很多人在運行之后可能感到困惑,為什么即使設(shè)置了setTimeout的延遲事件為0,它里面的代碼仍然是最后執(zhí)行的?
通常情況下,決定代碼執(zhí)行順序的是函數(shù)調(diào)用棧。很明顯這里的setTimeout中的執(zhí)行順序已經(jīng)不是用函數(shù)調(diào)用棧能夠解釋清楚的了,這是因為隊列。
JavaScript的一個特點是單線程,但是很多時候我們?nèi)孕枰诓煌氖录?zhí)行不同的任務(wù),例如給元素添加點擊事件,設(shè)置一個定時器,或者發(fā)起Ajax請求。因此需要一個異步機制來達到這樣的目的,事件循環(huán)機制也因此而來。
每一個JavaScript程序都擁有唯一的事件循環(huán),大多數(shù)代碼的執(zhí)行順序是可以根據(jù)函數(shù)調(diào)用棧的規(guī)則執(zhí)行的,而setTimeout/setIInterval或者不同的事件綁定(click等)中的代碼,則通過隊列來執(zhí)行。
setTimeout為任務(wù)源,或者任務(wù)分發(fā)器,由它們講不通的任務(wù)分發(fā)到不同的任務(wù)隊列中去。每個任務(wù)源都有對應(yīng)的任務(wù)隊列。
任務(wù)隊列又分為宏任務(wù)(macro-task)與微任務(wù)(micro-task)兩種,在瀏覽器中,宏任務(wù)包括script,setTimeout/setInterval,I/O,UI rendering等,微任務(wù)包括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為創(chuàng)建對象提供了新的語法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) { // 構(gòu)造方法
this.name = name;
this.age = age;
}
getName () { // 原型方法
return this.name;
}
static a = 20; // 等同于Person.a = 20
c = 20; // 表示在構(gòu)造函數(shù)中添加屬性,在構(gòu)造函數(shù)中等同于this.c = 20
getAge = () => this.age; // 箭頭函數(shù)的寫法表示在構(gòu)造函數(shù)中添加方法,在構(gòu)造函數(shù)中等同于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
子類的構(gòu)造函數(shù)中必須調(diào)用super方法,它表示構(gòu)造函數(shù)的繼承。
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}
關(guān)于ES6模塊化的知識,更多請閱讀阮一峰大神的ECMAScript 6入門。
以上是我對JavaScript核心技術(shù)開發(fā)解密第十章(下)的讀書筆記,碼字不易,請尊重作者版權(quán),轉(zhuǎn)載注明出處。至此,本書全部十章筆記都已寫完,中間有拉下的我會晚些補上,再次感謝??途W(wǎng)提供這樣的機會。
By BeLLESS 2018.8.13 22:56