JavaScript核心技術(shù)開發(fā)解密讀書筆記(第十章下)

接上節(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容