JavaScript异步编程(一) 深入理解JavaScript事件

 JavaScript异步编程

 

深入理解JavaScript事件

 

・事件的调度

 JavaScript事件处理器在线程空闲之前不会运行

 

 线程的阻塞

var start = new Date();

 
// setTimeout和setInterval的计时精度比期望值差

setTimeout(function(){

         var end = new Date();

         console.log('Time elapsed', end - start, 'ms');

}, 500);

 
while(new Date - start < 1000) {};

  结果上看出setTimeout没有使用另一线程

 

  队列

  调用setTimeout时,会有一个延时事件排入队列;

  输入事件的工作方式完全一样,如单机事件发生时,会有一个单击事件排入队列。但是,单击事件处理器要等到当前所有正在运行的代码均结束后才会执行。

 

・异步函数类型

  两大类:I/O函数和计时函数

 

  异步的I/O函数

  Node.js的出现是为了建立一个在某高级语言之上的事件驱动型服务器框架。

  JavaScript语言可以完美地实现非阻塞式I/O。

  如:

  var ajaxRequest = new XMLRequest();

  ajaxRequest.open(‘GET’, url);

  ajaxRequest.send(null);

  // 只需要附加一个事件处理器,随即返回事件队列

  ajaxRequest.onreadystatechange = function() {

   // …

  }

  因此,开发者需要做的只是定义一个回调就可以了。

 

  WebKit的console.log是异步的吗?

 

  var a = {name: '1'};

  console.log(a);

  a.name = '2';

  console.log(a);

  执行后打开控制台结果:

  先打开控制台,然后再执行的结果:

 

  原因:

  Console是浏览器的控制台所提供的对象,只有在控制台打开时才起作用;

  console.log并没有立即拍摄对象快照,只是存储了一个对象的引用;

 

  异步的计时函数

  可能为了作动画或模拟;

 

  setTimeout与setInterval(不精确的计时工具)

  以上两个函数的缺陷:即使时延设置为0,在浏览器中的执行频率也大约为200次/秒;

  原因:HTML规范推行的延时/时隔最小值为4毫秒;

  需要更细粒度计时的方案:requestAnimationFrame函数

 

・异步函数的编写

  任何函数只要使用了异步的函数,从上到下都是异步的。

 

  异步函数的特性:非阻塞

  非阻塞强调了异步函数的高速度

 

  间或异步的函数

  有些函数某些时候是异步的,但其他时候却不然;

  例:jQuery的同名函数可用于延迟函数直到DOM已经结束加载;

      但DOM如果早已结束了加载,则不存在任何延迟,$的回调将会立即触发;

 

  // application.js

  $(function() {

       utils.log('Ready');

  });

 

  // utils.js

  window.utils = {

          log: function() {

                  if(window.console)

                        console.log.apply(console, arguments);

          }

  };

  <script src="application.js" type="text/javascript"> </script>

  <script src="utils.js" type="text/javascript"> </script>

 

  这段代码运行得很好,但前提是浏览器并未从缓存中加载页面(这会导致DOM 早在脚本运行之前就已加载就绪)。如果出现这种情况,传递给$的回调就会在设置utils.log 之前运行,从而导致一个错误。

 

  缓存型异步函数

  function runCalculation(formula, callback) {

    if (formula in calculationCache) {

        return callback(calculationCache[formula]);

    };

    if (formula in calculationCallbacks) {

        return setTimeout(function() {

            runCalculation(formula, callback);

        }, 0);

    };

    mathWorker.postMessage(formula);

    calculationCallbacks[formula] = callback;

 }

  公式已经计算完成,于是结果位于calculationCache 中。这种情况下,runCalculation 是同步的。

  公式已经发送给Worker 对象,但尚未收到结果。这种情况下,runCalculation 设定了一个延时以便再次调用自身;重复这一过程直到结果位于calculationCache 中为止。

 

  异步递归与回调存储

  避免异步递归,尽量采用回调存储。

 

  返值与回调

  永远不要定义一个潜在同步而返值却有可能用于回调的函数。

  避免使用计时器方法来等待某个会变化的东西。如果同一个函数既返值又运行回调,则请确保回调在返值之后才运行。

 

・异步错误的处理

  大多数JavaScript环境会提供一个有用的堆栈轨迹。

 

  回调内抛出的错误

setTimeout(function A() {
    setTimeout(function B() {
        setTimeout(function C() {
            throw new Error('Something terrible has happened!');
        }, 0);
      }, 0);
}, 0);

 A和B并未出现在堆栈轨迹中,因为这三个函数都是从事件队列直接运行的;

  同理,try/catch语句块不能捕获从异步回调中抛出的错误;

  要记住的是,只能在回调内部处理源于回调的异步错误。

  windows.onerror 处理器返回true,能阻止浏览器的默认错误处理行为。

  避免两层以上的函数嵌套。关键是找到一种在激活异步调用之函数的外部存储异步结果的方式,这样回调本身就没有必要再嵌套了。

posted @ 2016-12-27 19:21  dreamerjdw  阅读(655)  评论(0编辑  收藏  举报