> 웹 프론트엔드 > JS 튜토리얼 > 자바스크립트 비동기 프로그래밍_javascript 기술 이해

자바스크립트 비동기 프로그래밍_javascript 기술 이해

WBOY
풀어 주다: 2016-05-16 15:17:38
원래의
1047명이 탐색했습니다.

1. 비동기 메커니즘

JavaScript의 실행 환경은 단일 스레드입니다. 단일 스레드의 장점은 실행 환경이 간단하고 리소스 동기화, 교착 상태 및 기타 다중 스레드 차단 프로그래밍과 같은 성가신 문제를 고려할 필요가 없다는 것입니다. 그러나 단점은 작업을 실행하는 데 오랜 시간이 걸리면 후속 작업이 오랜 시간 동안 대기한다는 것입니다. 브라우저 측에서는 브라우저가 정지하거나 마우스가 반응하지 않는 등의 문제가 발생할 수 있습니다. 따라서 브라우저 측에서는 브라우저가 응답하지 않는 것을 방지하기 위해 장기 실행 작업을 비동기식으로 수행해야 합니다. 소위 비동기 실행은 동기 실행과 다릅니다(프로그램의 실행 순서는 작업 순서와 일관되고 동기적입니다). 각 작업에는 하나 이상의 콜백 함수(콜백)가 있습니다. 작업이 실행되지 않고 콜백 함수를 실행하며, 후자의 작업은 이전 작업이 완료되기 전에 실행되므로 프로그램의 실행 순서와 작업의 순서가 일치하지 않고 비동기적입니다. Javascript는 단일 스레드이므로 어떻게 비동기적으로 실행할 수 있습니까?

2. 자바스크립트 스레딩 모델 및 이벤트 기반

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.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿