JavaScript核心技术开发解密读书笔记(第十章下)

接上节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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容