资讯专栏INFORMATION COLUMN

JavaScript单线程、加载与模块化

shevy / 2510人阅读

摘要:单线程与浏览器多线程是单线程的因为运行在浏览器中,是单线程的,每个一个线程。若以多线程的方式操作这些,则可能出现操作的冲突。零延迟零延迟并不是意味着回调函数立刻执行。异步编程的中方法包括回调函数事件监听采用事件驱动模式。

JavaScript单线程与浏览器多线程

Javascript是单线程的:因为JS运行在浏览器中,是单线程的,每个window一个JS线程。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。若以多线程的方式操作这些DOM,则可能出现操作的冲突。假设有两个线程同时操作一个DOM元素,线程1要求浏览器删除DOM,而线程2却要求修改DOM样式,这时浏览器就无法决定采用哪个线程的操作。

浏览器不是单线程的,引擎可能存在如下线程:

javascript引擎线程

界面渲染线程

浏览器事件触发线程

Http请求线程等

Event Loop

事件循环机制:
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。

执行栈:所有同步任务都在主线程上执行,形成一个执行栈;

任务队列:主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件,当栈为空时,就会从任务队列中取出一个任务并执行。

定时器:
定时器包括setTimeout与setInterval两个方法。它们的第二个参数是指定其回调函数推迟每隔多少毫秒数后执行。
零延迟 setTimeout(func, 0):零延迟并不是意味着回调函数立刻执行。它取决于主线程当前是否空闲与“任务队列”里其前面正在等待的任务。当计时器时间规定的时候,回调函数会被添加到“任务队列”中,等到执行栈的任务执行完毕,就会来执行这里的任务。所以,有的时候即使计时器已经到0了,也会不立即执行计时器中的回调任务。

Ajax异步请求:
在发起ajax请求的时候,浏览器会开一个线程去执行这一步骤,发请求这一步是属于执行栈中的同步任务,在请求发完之后就会执行栈中的下一个任务,而等到请求有了结果,就会把结果放入“任务队列”中,等待执行;

JavaScript异步编程

JavaScript是单线程的,所谓“单线程”,就是同一时间只能执行一个任务。如果有多个任务,就必须排队,直到前一个任务完成。这种模式的好处就是实现比较简单,尤其在浏览器环境中,可以避免很多不必要的麻烦。坏处就是如果一个任务耗时很长,那么该任务后面的任务就必须一直等待,导致整个页面都卡住无法继续往下执行。

为了解决这个问题,JavaScript将任务的执行模式分为两类,一个是同步模式,另一个是异步模式。

"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

JS异步编程的4中方法包括:

回调函数:

f1();
f2();

function f1(callback){
    setTimeout(function(){
        callback();
    })
}

f1(f2);

事件监听:采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

//jQuery
f1.on("done", f2);

function f1(){
   setTimeout(function () {
       //do something
     f1.trigger("done");//执行完成后,立即触发done事件,从而开始执行f2。
   }, 1000);
}

发布/订阅:又称观察者模式

jQuery.subscribe("done", f2);//订阅"done"这个信号

function f1(){
   setTimeout(function () {
     //do something
     jQuery.publish("done");//f1()执行完之后,发出"done"这个信号,f2开始执行
   }, 1000);
}

jQuery.unsubscribe("done", f2);//取消订阅

Promises:ES6原生提供了Promise对象。所谓Promise对象,就是代表了未来某个将要发生的事件(通常是一个异步操作)。它的好处在于,有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象还提供了一整套完整的接口,使得可以更加容易地控制异步操作。

var promise = new Promise(function(resolve, reject) {
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(function(value) {
  // success
}, function(value) {
  // failure
});

ES6模块管理

ECMAScript 6基于export和import,定义了模块的导出和导入规范,在语言标准层面实现了模块机制。该标准的目标是创建一种能够兼容CommoneJS和AMD两标准的规范,即可以像CommoneJS一样语法简洁、使用单一的接口且支持循环依赖,又可以像AMD支持异步加载和可配置的模块加载。

大致来说,当 JS 引擎运行一个模块的时候,它的行为大致可归纳为以下四步:

解析:引擎实现会阅读模块的源码,并且检查是否有语法错误。

加载:引擎实现会(递归地)加载所有被引入的模块。

链接:引擎实现会为每个新加载的模块创建一个作用域,并且将模块中的声明绑定填入其中,包括从其他模块中引入的。

JS加载

动态加载常用的4种方法:

document.write:document.write("");

动态改变已有script的src属性:js.src = "package.js";

动态创建script元素:

var script = document.createElement("script");
script.src = "XXX";
script.type = "XXX";
document.head.appendChild(script);

用XMLHTTP取得要脚本的内容,再创建 Script 对象。

项目中加载JS的方法:
在页面底部写一个自调用函数,加载入口JS,然后入口中的方法执行,加载需要的其他JS。

在VM平台上,可以选择将几个模块分组打包到一个入口文件中,然后在这个入口文件中,将每个组动态生成一个script标签,插入head中。

异步加载:
浏览器在渲染一个页面的时候,遇到一个

阅读需要支付1元查看
<