JavaScript异步编程详解:从事件循环到Promise与脚本加载

作者:拾光

时间:2026-10-29

浏览:592

本文深入解析JavaScript异步编程的核心机制,涵盖事件循环原理、setTimeout执行特性、Promise与Deferred对象的使用,以及传统与异步脚本加载策略。重点探讨Node.js环境下异步IO处理及jQuery中Promise API的应用,帮助开发者理解并优化异步代码结构。

文章目录

JavaScript异步编程概述

在客户端JavaScript环境中,异步编程引发的挑战通常并不显著。然而,随着服务器端JavaScript(如Node.js)的广泛应用,大量的异步I/O操作使得这一问题的严重性日益凸显。针对这一问题,存在多种解决方案,本文仅对部分方法进行了探讨,并未深入所有细节。开发者应根据实际项目需求,选择最适合自身的异步处理方案。

本文将详细介绍JavaScript中的异步编程技术,具体内容包括以下几个方面。

事件机制与异步处理

一、关于事件的异步处理

事件机制是JavaScript最核心的特性之一,Node.js正是基于JavaScript的异步特性进行设计的。因此,了解事件机制对于理解异步编程至关重要。

在JavaScript文件中执行函数主要有两种方式:一是直接调用,例如执行foo();二是通过事件触发,这类函数通常被称为回调函数,例如传递给setTimeout函数的回调或绑定到onready属性的处理函数。

1. setTimeout函数中的异步事件

setTimeout本质上属于一种异步事件。当设定的延迟时间到达时,该事件会被触发。然而,在实际运行中(且在大多数情况下),回调函数的执行时间往往并不严格遵循设定的延迟时间。请参考以下代码示例。


  var start = new Date();
  setTimeout(function() {
   console.log('settimeout1:',new Date()-start);
  }, 500);
  while (new Date() - start < 1000) {
   console.log('in while');
  }
  document.getElementById('test').addEventListener('click', function(){
   console.log('test:',new Date()-start);
  }, false)
  for(var i=0;i<10000;i++){
   console.log('in for');
  }
  setTimeout(function(){
   console.log('settimeout2: ',new Date()-start);
  },1000);
  /* 10214
  in while
  index.jsp (第 19 行)
  10000
  in for
  index.jsp (第 25 行)
  settimeout1: 2263
  index.jsp (第 16 行)
  settimeout2: 3239
  index.jsp (第 28 行)
  test: 10006
  index.jsp (第 22 行)
  test: 28175
  index.jsp (第 22 行)
  test: 28791
  index.jsp (第 22 行)
  test: 28966
  index.jsp (第 22 行) */

按照常规理解,延迟函数应当在500毫秒后中断while循环。但事实上并非如此,并且在while循环和for循环执行期间,即使点击div元素,也不会立即输出test。对此现象的解释如下。

a)事件队列机制:调用setTimeout函数时,传入的回调函数会被加入事件队列(此时事件已初始化并存在于内存中),随后JavaScript继续执行后续代码,直到主线程中没有其他同步代码可执行(不包括事件函数等异步内容)。此时,系统会从事件队列中取出一个合适的事件进行运行。

b)JavaScript是单线程运行的,事件处理器必须在线程空闲时才会执行。

2. 普通事件的异步行为与setTimeout类似

Promise与Deferred对象详解

二、Promise对象和Deferred对象

1. Promise

Promise是一种解决Ajax等异步编程中回调函数嵌套过深导致代码难以阅读和维护的方案,特别是在Node.js环境中,异步操作无处不在。不同框架对Promise的实现方式各异,以下展示了jQuery中Promise的API用法。

此处不深入探讨Promise的实现原理,相关原理将在其他篇章中介绍。

传统的Ajax异步编程写法如下(适用于jQuery 1.5版本之前):


$.get('url', function(){
 $.get('url1', function(){
  $.get('url2', function(){

  }, 'json');
 }, 'json');
}, 'json');

这种写法给代码的开发和维护带来了极大困难。好在jQuery 1.5之后引入了Promise机制,使得代码可以这样编写:


 $.ajax( "example.php" )
.done(function() { alert("success"); })
.fail(function() { alert("error"); })
.always(function() { alert("complete"); });

经过优化后,代码结构明显更加简洁清晰。

2. Deferred对象


var nanowrimoing = $.Deferred();
var wordGoal = 5000;
nanowrimoing.progress(function(wordCount) {
var percentComplete = Math.floor(wordCount / wordGoal * 100);
$('#indicator').text(percentComplete + '% complete');
});
nanowrimoing.done(function(){
$('#indicator').text('Good job!');
});

Worker对象与多线程

三、Worker对象和多线程处理

四、异步脚本加载策略

异步脚本加载策略

1. 传统脚本在页面中的位置

脚本主要分为两大类:阻塞式和非阻塞式。这里的“阻塞”指的是加载过程中的阻塞,而非运行时的阻塞。


<!DOCTYPE html>
<html>
<head>
<script src="headScript"></script>
<script defer src="deferredScript"></script>
</head>
<body>
 <script async defer src="chatWidget"></script>
 <script async defer src="asyncScript"></script>
</body>
</html>

上述代码展示了页面中脚本放置的标准示例:1. 传统未加修饰的headScript属于阻塞式脚本。由于浏览器从上到下解释执行JavaScript,这部分脚本文件会在页面加载初期立即执行,且在执行完成前DOM渲染会被阻塞,但head标签内的CSS样式会正常加载。2. 带有defer属性的脚本会在DOM渲染过程中进行加载,但会在DOM渲染完成后才开始执行。遗憾的是,并非所有浏览器都支持defer属性,因此出现了jquery(function)这种兼容写法。3. 当同时具备async和defer属性时,defer会覆盖async;但若单独使用async属性,脚本会在DOM渲染期间加载并立即运行。

2. 可编程的脚本加载

如果并非在页面初始加载时引入JS文件,而是通过用户交互动态加载JS脚本,可以通过编程方式实现。

浏览器从服务器获取脚本主要有两种方法:一是通过Ajax获取并使用eval函数执行;二是在DOM中插入<script>标签。通常推荐使用第二种方法,因为浏览器会自动生成HTTP请求,而使用eval函数可能会泄露作用域带来安全风险。


var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = '/js/feature.js';
head.appendChild(script);
script.onload = function() {
// 现在可以调用脚本里定义的函数了
}

以上就是本文的全部内容,希望这对大家学习JavaScript异步编程有所帮助。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章