这里有新鲜出炉的 Javascript 教程,程序狗速度看过来!
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
这篇文章主要为大家介绍了 javascript 异步编程,从浅入深的学习 javascript 异步编程,对 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
来源: http://www.phperz.com/article/17/0407/267344.html