阮一峰JS教程读后感(十一)异步操作

一、概述

1. JavaScript脚本是如何执行的?

JavaScript最初被设计时考虑到其只是在浏览器中执行的脚本,不应该像其他语言那么繁琐,所以JavaScript自始至终都是一门单线程语言。

为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。

所以js脚本执行时,只有一个主线程,它还会有很多个子线程,这些子线程都是有回调函数的异步操作,子线程存储在任务队列中,js引擎首先把主线程上所有任务执行完毕之后,就开始循环检查任务队列中的子线程是否有返回值,如果有返回了,那么将子线程任务调至主线程中开始执行,执行完毕后又开始循环检查。

2. 如何理解 发布/订阅 模式?

事件完全可以理解成“信号”,如果存在一个“信号中心”,某个任务执行完成,就向信号中心“发布”(publish)一个信号,其他任务可以向信号中心“订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式”(publish-subscribe pattern),又称“观察者模式”(observer pattern)。

3. 异步函数的串行执行

我们可以编写一个流程控制函数,让它来控制异步任务,一个任务完成以后,再执行另一个。这就叫串行执行。

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];

function async(arg, callback) {
  console.log('参数为 ' + arg +' , 1秒后返回结果');
  setTimeout(function () { callback(arg * 2); }, 1000);
}

function final(value) {
  console.log('完成: ', value);
}

function series(item) {
  if(item) {
    async( item, function(result) {
      results.push(result);
      return series(items.shift());
    });
  } else {
    return final(results[results.length - 1]);
  }
}

series(items.shift());

上面代码中,函数series就是串行函数,它会依次执行异步任务,所有任务都完成后,才会执行final函数。items数组保存每一个异步任务的参数,results数组保存每一个异步任务的运行结果。

4. 异步函数的并行执行

流程控制函数也可以是并行执行,即所有异步任务同时执行,等到全部完成以后,才执行final函数。

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];

function async(arg, callback) {
  console.log('参数为 ' + arg +' , 1秒后返回结果');
  setTimeout(function () { callback(arg * 2); }, 1000);
}

function final(value) {
  console.log('完成: ', value);
}

items.forEach(function(item) {
  async(item, function(result){
    results.push(result);
    if(results.length === items.length) {
      final(results[results.length - 1]);
    }
  })
});

上面代码中,forEach方法会同时发起六个异步任务,等到它们全部完成以后,才会执行final函数。

5. 串行并行结合执行

所谓并行与串行的结合,就是设置一个门槛,每次最多只能并行执行n个异步任务,这样就避免了过分占用系统资源。

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
var running = 0;
var limit = 2;

function async(arg, callback) {
  console.log('参数为 ' + arg +' , 1秒后返回结果');
  setTimeout(function () { callback(arg * 2); }, 1000);
}

function final(value) {
  console.log('完成: ', value);
}

function launcher() {
  while(running < limit && items.length > 0) {
    var item = items.shift();
    async(item, function(result) {
      results.push(result);
      running--;
      if(items.length > 0) {
        launcher();
      } else if(running == 0) {
        final(results);
      }
    });
    running++;
  }
}

launcher();

上面代码中,最多只能同时运行两个异步任务。变量running记录当前正在运行的任务数,只要低于门槛值,就再启动一个新的任务,如果等于0,就表示所有任务都执行完了,这时就执行final函数。

这段代码需要三秒完成整个脚本,处在串行执行和并行执行之间。通过调节limit变量,达到效率和资源的最佳平衡。

二、定时器

1. 如果setTimeout定时器中调用的方法是对象中的方法,那么this的指向是?

this指向为window,而非定义方法的对象。

var x = 1;

var obj = {
  x: 2,
  y: function () {
    console.log(this.x);
  }
};

setTimeout(obj.y, 1000) // 1

2. 请问setTimeout(f, 0)的含义是什么?

首先我们需要明白setTimeout的运行机制,定时器会把f的代码先移出本轮循环,然后下一次能循环到来时检查时间是否符合设置的delay,如果不符合则继续等待下一轮的轮询,循环往复,直到执行该func,所以此表达式的意思并非是立即执行f,而是在下一轮事件循环一开始就执行f。

三、Promise对象

About

在此教程中讲述较少,不能很好理解,以后看完ES6后会重新写

1. Promise的回调函数与普通异步操作的回调函数有什么区别?

普通异步操作的回调函数会被放置到新一轮的事件循环中,也就是说必须等主线程中的任务执行完毕才会去检查异步操作是否有返回值,如果有就执行相应的对调函数,但是Promise的回调函数属于微任务,它不会被加入到子进程的任务队列中,而是放在主线程任务的末端,即在本次事件循环中执行,所以Promise的回调函数会先于普通异步操作的回调函数执行。

你可能感兴趣的:(阮一峰JS教程读后感(十一)异步操作)