理解javascript异步编程
一、异步机制
JavaScript的执行环境是单线程的,单线程的好处是执行环境简单,不用去考虑诸如资源同步,死锁等多线程阻塞式编程等所需要面对的恼人的问题。但带来的坏处是当一个任务执行时间较长时,后面的任务会等待很长时间。在浏览器端就会出现浏览器假死,鼠标无法响应等情况。所以在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应。所谓异步执行,不同于同步执行(程序的执行顺序与任务的排列顺序是一致的、同步的),每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。既然Javascript是单线程的,那它又如何能够异步的执行呢?
二、Javascript线程模型和事件驱动
JavaScript有一个基于事件循环的并发模式。这个模式与C语言和java有很大不同。
运行时的概念
栈
函数调用形成堆栈帧。
function f(b){ var a = 12; return a+b+35; } function g(x){ var m = 4; return f(m*x); } g(21);
当调用函数g时,创建第一个包含g参数和局部变量的帧。当g函数调用f函数时,创建包含f参数和局部变量第二个堆栈帧并推到第一个堆栈帧的顶部。当f返回时,顶部的堆栈帧元素被弹出(只留下g调用)。当g函数返回时,堆栈为空。
堆
堆是一个大型的非结构化区域,对象被分配到堆中。
队列
一个javascript运行环境包含一个信息队列,这个队列是一系列将被执行的信息列表。每一个消息被关联到一个函数上。当堆栈为空时,从消息队列中取出一个消息并进行处理。该处理包含调用相关的函数(以及因此产生一个初始化的堆栈帧)。当堆栈再次为空时,消息处理结束。
事件循环
事件循环的名字源于它的实现,经常像下面这样:
while(queue.waitForMessage()){ queue.processNextMessage(); }
queue.waitForMessage同步等待一个消息。
1、运行到完成
每个消息完全处理之后,其它消息才会被处理。这样的好处就是当一个函数不能被提前,只能等其他函数执行完毕(并且可以修改数据的函数操作)。这不同于C,例如,如果一个函数在一个线程运行时,它可以停在任何点运行在另一个线程一些其他的代码。这种模式的缺点是,如果一个消息时间过长完成,Web应用程序无法处理像点击或滚动的用户交互。该浏览器可缓解此与“脚本花费的时间太长运行”对话框。一个很好的做法,遵循的是使信息处理短,如果可能削减一个消息到几条消息。
2、添加消息
在网页浏览器中,事件可以在任何时候添加,一个事件发生并伴随事件监听绑定到事件上。如果没有事件监听,则事件丢失。就像点击一个元素,元素上绑定点击事件。调用setTimeout时,当函数的第二个参数时间被传递进去,将添加一个消息到队列中。如果在队列中没有其他消息,该消息被立即处理;然而,如果有消息,则setTimeout的信息将必须等待其它消息以进行处理。由于这个原因,第二个参数是最小的时间,而不是一个保证时间。
3、几个运行环境之间的通信
一个web worker或跨域iframe都有自己的堆栈,堆,和消息队列。两个不同的运行环境只能通过postMessage的方法发送消息进行通信。这种方法增加了一个消息到其他运行时,如果后者监听消息事件。
从不阻塞
事件循环模型是javascript的一个很有意思的属性,不像其它语言,它从不阻塞。假定浏览器中有一个专门用于事件调度的实例(该实例可以是一个线程,我们可以称之为事件分发线程event dispatch thread),该实例的工作就是一个不结束的循环,从事件队列中取出事件,处理所有很事件关联的回调函数(event handler)。注意回调函数是在Javascript的主线程中运行的,而非事件分发线程中,以保证事件处理不会发生阻塞。通过事件和回调的I/O操作是一个典型的表现,所以当应用等待索引型数据库查询返回或XHR请求返回时,它仍然可以处理其他事情比如用户输入。
三、回调
回调是javascript的基础,函数被作为参数进行传递。像下面:
f1(); f2(); f3();
如果f1中执行了大量的耗时操作,而且f2需要在f1之后执行。则程序可以改为回调的形式。如下:
function f1(callback){ setTimeout(function () { // f1的大量耗时任务代码并的到三个结果i,l,you. console.log("this is function1"); var i = "i", l = "love", y = "you"; if (callback && typeof(callback) === "function") { callback(i,l,y); } }, 50); } function f2(a, b, c) { alert(a + " " + b + " " + c); console.log("this is function2"); } function f3(){console.log("this is function3");} f1(f2); f3();
运行结果:
this is function3 this is function1 i love you this is function2
采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。
回调函数的优点是简单,轻量级(不需要额外的库)。缺点是各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。某个操作需要经过多个非阻塞的IO操作,每一个结果都是通过回调,产生意大利面条式(spaghetti)的代码。
operation1(function(err, result) { operation2(function(err, result) { operation3(function(err, result) { operation4(function(err, result) { operation5(function(err, result) { // do something useful }) }) }) }) })
四、事件监听
另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
// plain, non-jQuery version of hooking up an event handler var clickity = document.getElementById("clickity"); clickity.addEventListener("click", function (e) { //console log, since it's like ALL real world scenarios, amirite? console.log("Alas, someone is pressing my buttons…"); }); // the obligatory jQuery version $("#clickity").on("click", function (e) { console.log("Alas, someone is pressing my buttons…"); });
也可以自定义事件进行监听,关于自定义事件,属于另外一部分的内容。这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。
五、观察者模式
我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。
var pubsub = (function(){ var q = {} topics = {}, subUid = -1; //发布消息 q.publish = function(topic, args) { if(!topics[topic]) {return;} var subs = topics[topic], len = subs.length; while(len--) { subs[len].func(topic, args); } return this; }; //订阅事件 q.subscribe = function(topic, func) { topics[topic] = topics[topic] ? topics[topic] : []; var token = (++subUid).toString(); topics[topic].push({ token : token, func : func }); return token; }; return q; //取消订阅就不写了,遍历topics,然后通过保存前面返回token,删除指定元素 })(); //触发的事件 var f2 = function(topics, data) { console.log("logging:" + topics + ":" + data); console.log("this is function2"); } function f1(){ setTimeout(function () { // f1的任务代码 console.log("this is function1"); //发布消息'done' pubsub .publish('done', 'hello world'); }, 1000); } pubsub.subscribe('done', f2); f1();
上面代码的运行结果为:
this is function1 logging:done:hello world this is function2
观察者模式的实现方法有很多种,也可以直接借用第三方库。这种方法的性质与"事件监听"类似(观察者模式和自定义事件非常相似),但是明显优于后者。观察者模式和事件监听一样具有良好的去耦性,并且有一个消息中心,通过对消息中心的处理,可以良好地监控程序运行。
六、Promises对象
Promises的概念是由CommonJS小组的成员在 Promises/A规范 中提出来的。Promises被逐渐用作一种管理异步操作回调的方法,但出于它们的设计,它们远比那个有用得多。Promise允许我们以同步的方式写代码,同时给予我们代码的异步执行。
function f1(){ var def = $.Deferred(); setTimeout(function () { // f1的任务代码 console.log("this is f1"); def.resolve(); }, 500); return def.promise(); } function f2(){ console.log("this is f2"); } f1().then(f2);
上面代码的运行结果为:
this is f1 this is f2
上面引用的是jquery对Promises/A的实现,jquery中还有一系列方法,具体可参考:Deferred Object.关于Promises,强烈建议读一下You're Missing the Point of Promises.还有很多第三方库实现了Promises,如:Q、Bluebird、 mmDeferred 等。Promise(中文:承诺)其实为一个有限状态机,共有三种状态:pending(执行中)、fulfilled(执行成功)和rejected(执行失败)。其中pending为初始状态,fulfilled和rejected为结束状态(结束状态表示promise的生命周期已结束)。状态转换关系为:pending->fulfilled,pending->rejected。随着状态的转换将触发各种事件(如执行成功事件、执行失败事件等)。 下节具体讲述状态机实现js异步编程。
七、状态机
Promises的本质实际就是通过状态机来实现的,把异步操作与对象的状态改变挂钩,当异步操作结束的时候,发生相应的状态改变,由此再触发其他操作。这要比回调函数、事件监听、发布/订阅等解决方案,在逻辑上更合理,更易于降低代码的复杂度。关于Promises可参考:JS魔法堂:剖析源码理解Promises/A规范 。
八、ES6对异步的支持
这是一个新的技术,成为2015年的ECMAScript(ES6)标准的一部分。该技术的规范已经完成,但实施情况在不同的浏览器不同,在浏览器中的支持情况如下。
var f1 = new Promise(function(resolve, reject) { setTimeout(function () { // f1的任务代码 console.log("this is f1"); resolve("Success"); }, 500); }); function f2(val){ console.log(val + ":" + "this is f2"); } function f3(){ console.log("this is f3") } f1.then(f2); f3();
以上代码在Chrome 版本43中的运行结果为:
this is f3 this is f1 Success:this is f2
以上就是针对javascript异步编程的了解学习,之后还有相关文章进行分享,不要错过哦。
相关文章
使用PHP+JavaScript将HTML页面转换为图片的实例分享
这篇文章主要介绍了使用PHP+JavaScript将HTML元素转换为图片的实例分享,文后结果的截图只能体现出替换的字体,也不能说将静态页面转为图片可以加快加载,只是这种做法比较interesting XD需要的朋友可以参考下...2016-04-19- 在昨天的《Javascript权威指南》学习笔记之十:ECMAScript 5 增强的对象模型一文中,对于一段代码的调试出现了一个奇怪现象,现将源代码贴在下面: 复制代码 代码如下: <script type="text/javascript"> function Person(){}...2014-05-31
- 最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
- 复制代码 代码如下: //element:需要添加新样式的元素,value:新的样式 function addClass(element, value ){ if (!element.className){ element.className = value; }else { newClassName = element.className; newClas...2014-05-31
- 在javascritp中,不一定只有对象方法的上下文中才有this, 全局函数调用和其他的几种不同的上下文中也有this指代。 它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript 中函数的调用有以下...2015-03-15
- 有一道js面试题,题目是这样的:下列代码的执行结果是什么,为什么? 复制代码 代码如下: var i, j, k; for (i=0, j=0; i<10, j<6; i++, j++) { k = i+j; } document.write(k); 答案是显示10,这道题主要考察JavaScript的逗...2015-03-15
- 事件触发器从字面意思上可以很好的理解,就是用来触发事件的,但是有些没有用过的朋友可能就会迷惑了,事件不是通常都由用户在页面上的实际操作来触发的吗?这个观点不完全正确,因为有些事件必须由程序来实现,如自定义事件,jQue...2014-06-07
- 首先,我想到的是另建一个结果数组,用来存储原始数组中不重复的数据。遍历原始数组依次跟结果数组中的元素进行比较,检测是否重复。于是乎,我写出了如下代码A: Array.prototype.clearRepetitionA = function(){ var resul...2015-11-08
- 1、ActiveX向Javascript传参 复制代码 代码如下: <script language="javascript" for="objectname" event="fun1(arg)"> fun2(arg); </script> objectname为ActiveX控件名,通过<object>标签里的id属性设定,如下; 复制...2014-06-07
- 这篇文章主要介绍了Javascript类型转换的规则实例解析,涉及到javascript类型转换相关知识,对本文感兴趣的朋友一起学习吧...2016-02-27
- Window有navigator对象让我们得知浏览器的全部信息.我们可以利用一系列的API函数得知浏览器的信息.JavaScript代码如下:function message(){ txt = "<p>浏览器代码名: " + navigator.appCodeName + "</p>";txt+= "<p>...2015-11-24
- 通过 HTML DOM,可访问 JavaScript HTML 文档的所有元素。 HTML DOM (文档对象模型) 当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。 HTML DOM 模型被构造为对象的树: 通过可编程的对象模型,Java...2015-10-23
- 虽然ES6都还没真正发布,但已经有用ES6重写的程序了,各种关于ES789的提议已经开始了,这你敢信。潮流不是我等大众所能追赶的。潮流虽然太快,但我们不停下学习的步伐,就不会被潮流丢下的,下面来领略下ES6中新特性,一堵新生代JS...2015-11-24
- 神马是“解释器模式”?先翻开《GOF》看看Definition:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。在开篇之前还是要科普几个概念: 抽象语法树: 解释器模式并未解释如...2014-06-07
- ---恢复内容开始---1.location.href.....(1)self.loction.href="http://www.cnblogs.com/url" window.location.href="http://www.cnblogs.com/url" 以上两个用法相同均为在当前页面打开URL页面 (2)this.locati...2015-10-30
- tab切换在网页中很常见,故最近总结了4种实现方法。 首先,写出tab的框架,加上最简单的样式,代码如下: <!DOCTYPE html> <html> <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><style> *{ pa...2015-11-08
基于JavaScript如何实现私有成员的语法特征及私有成员的实现方式
前言在面向对象的编程范式中,封装都是必不可少的一个概念,而在诸如 Java,C++等传统的面向对象的语言中, 私有成员是实现封装的一个重要途径。但在 JavaScript 中,确没有在语法特性上对私有成员提供支持, 这也使得开发人员使...2015-10-30- 这篇文章主要介绍了JavaScript预解析,对象的的相关资料,小编觉得这篇文章写的还不错,需要的朋友可以参考下,希望能够给你带来帮助...2021-11-10
- 这篇文章主要为大家介绍了JavaScript设计模式中的装饰者模式,对JavaScript设计模式感兴趣的小伙伴们可以参考一下...2016-01-21
- 生生把写过的java版改成javascript版,第一次写,很不专业,见谅。唉,我是有多闲。复制代码 代码如下: var Sudoku = { init: function (str) { this.blank = []; this.fixed = []; this.cell =...2015-03-15