您的当前位置:首页JavaScript异步编程之jsdeferred原理解析
广告

JavaScript异步编程之jsdeferred原理解析

2023-12-01 来源:吉希宠物网

1. 前言

最近在看司徒正美的《JavaScript框架设计》,看到异步编程的那一章介绍了jsdeferred这个库,觉得很有意思,花了几天的时间研究了一下代码,在此做一下分享。

异步编程是编写js的一个很重要的理念,特别是在处理复杂应用的时候,异步编程的技巧就至关重要。那么下面就来看看这个被称为里程碑式的异步编程库吧。

2. API源码解析

2.1 构造函数

这里使用了安全的构造函数,避免了在没有使用new调用构造函数时出错的问题,提供了两个形式俩获取Deferred对象实例。

function Deferred() { return (this instanceof Deferred) ? this.init() : new Deferred();}// 方式1 var o1 = new Deferred();// 方式2var o2 = Deferred();

2.2 Deferred.define()

这个方法可以包装一个对象,指定对象的方法,或者将Deferred对象的方法直接暴露在全局作用域下,这样就可以直接使用。

Deferred.methods = ["parallel", "wait", "next", "call", "loop", "repeat", "chain"];/* @Param obj 赋予该对象Deferred的属性方法 @Param list 指定属性方法*/Deferred.define = function(obj, list){ if(!list)list = Deferred.methods; // 获取全局作用域的技巧,利用立即执行函数的作用域为全局作用域的技巧 if(!obj) obj = (function getGlobal(){return this})(); // 将属性都挂载到obj上 for(var i = 0; i < list.length; i++){ var n = list[i]; obj[n] = Deferred[n]; } return Deferred;}this.Deferred = Deferred;

2.3 异步的操作实现

在JSDeferred中有许多异步操作的实现方式,也是作为这个框架最为出彩的地方,方法依次是:

script.onreadystatechange(针对IE5.5~8)

img.onerror/img.onload(针对现代浏览器的异步操作方法)

针对node环境的,使用process.nextTick来实现异步调用(已经过时)

setTimeout(default)

它会视浏览器选择最快的API。

使用script的onreadystatechange事件来进行,需要注意的是由于浏览器对并发请求数有限制,(IE5.5~8为2~3,IE9+和现代浏览器为6),当并发请求数大于上限时,会让请求的发起操作排队执行,导致延时更严重。代码的思路是以150ms为一个周期,每个周期以通过setTimeout发起的异步执行为起始,周期内的其他异步执行操作通过script请求实现,如果此方法被频繁调用的话,说明达到并发请求数上限的可能性越高,因此可以下调一下周期时间,例如设为100ms,避免因排队导致的高延时。

Deferred.next_faster_way_readystatechange = ((typeof window === "object") && (location.protocol == "http:") && !window.opera &&/MSIE/.test(navigator.userAgent)) &&function (fun) {var d = new Deferred();var t = new Date().getTime();if(t - arguments.callee._prev_timeout_called < 150){var cancel = false; // 因为readyState会一直变化,避免重复执行var script = document.createElement("script");script.type = "text/javascript";// 发送一个错误的url,快速触发回调,实现异步操作script.src = "data:text/javascript,";script.onreadystatechange = function () { if(!cancel){ d.canceller(); d.call(); }};d.canceller = function () { if(!cancel){ cancel = true; script.onreadystatechange = null; document.body.removeChild(script);// 移除节点 }};// 不同于img,需要添加到文档中才会发送请求document.body.appendChild(script);} else {// 记录或重置起始时间arguments.callee._prev_timeout_called = t; // 每个周期开始使用setTimeoutvar id = setTimeout(function (){ d.call()}, 0);d.canceller = function () {clearTimeout(id)};}if(fun)d.callback.ok = fun;return d;}

使用img的方式,利用src属性报错和绑定事件回调的方式来进行异步操作

Deferred.next_faster_way_Image = ((typeof window === "object") &&(typeof Image != "undefined") && !window.opera && document.addEventListener) && function (fun){var d = new Deffered();var img = new Image();var hander = function () {d.canceller();d.call();}img.addEventListener("load", handler, false);img.addEventListener("error", handler, false);d.canceller = function (){img.removeEventListener("load", handler, false);img.removeEventListener("error", handler, false);}// 赋值一个错误的URLimg.src = "data:imag/png," + Math.random();if(fun) d.callback.ok = fun;return d;}

针对Node环境的,使用process.nextTick来实现异步调用

Deferred.next_tick = (typeof process === 'object' &&typeof process.nextTick === 'function') && function (fun) {var d = new Deferred();process.nextTick(function() { d.call() });if (fun) d.callback.ok = fun;return d;};

setTimeout的方式,这种方式有一个触发最小的时间间隔,在旧的IE浏览器中,时间间隔可能会稍微长一点(15ms)。

Deferred.next_default = function (fun) {var d = new Deferred();var id = setTimeout(function(){clearTimeout(id);d.call(); // 唤起Deferred调用链}, 0)d.canceller = function () {try{ clearTimeout(id);}catch(e){}};if(fun){d.callback.ok = fun;}return d;}

默认的顺序为

Deferred.next = Deferred.next_faster_way_readystatechange || // 处理IE Deferred.next_faster_way_Image || // 现代浏览器 Deferred.next_tick || // node环境 Deferred.next_default; // 默认行为

根据JSDeferred官方的数据,使用next_faster_way_readystatechange和next_faster_way_Image这两个比原有的setTimeout异步的方式快上700%以上。

看了一下数据,其实对比的浏览器版本都相对比较旧,在现代的浏览器中性能提升应该就没有那么明显了。

2.4 原型方法

Deferred的原型方法中实现了

_id 用来判断是否是Deferred的实例,原因好像是Mozilla有个插件也叫Deferred,因此不能通过instanceof来检测。cho45于是自定义标志位来作检测,并在github上提交fxxking Mozilla。

init 初始化,给每个实例附加一个_next和callback属性

next 用于注册调用函数,内部以链表的方式实现,节点为Deferred实例,调用的内部方法_post

error 用于注册函数调用失败时的错误信息,与next的内部实现一致。

call 唤起next调用链

fail 唤起error调用链

cancel 执行cancel回调,只有在唤起调用链之前调用才有效。(调用链是单向的,执行之后就不可返回)

Deferred.prototype = { _id : 0xe38286e381ae, // 用于判断是否是实例的标识位 init : function () { this._next = null; // 一种链表的实现思路 this.callback = { ok : Deferred.ok, // 默认的ok回调 ng : Deferred.ng // 出错时的回调 }; return this; }, next : function (fun) { return this._post("ok", fun); // 调用_post建立链表 }, error : function (fun) { return this._post("ng", fun); // 调用_post建立链表 }, call : function(val) { return this._fire("ok", val); // 唤起next调用链 }, fail : function (err) { return this._fire("ng", err); // 唤起error调用链 }, cancel : function () { (this.canceller || function () {}).apply(this); return this.init(); // 进行重置 }, _post : function (okng, fun){ // 建立链表 this._next = new Deferred(); this._next.callback[okng] = fun; return this._next; }, _fire : function (okng, fun){ var next = "ok"; try{ // 注册的回调函数中,可能会抛出异常,用try-catch进行捕捉 value = this.callback[okng].call(this, value); } catch(e) { next = "ng"; value = e; // 传递出错信息 if (Deferred.onerror) Deferred.onerror(e); // 发生错误的回调 } if (Deferred.isDeferred(value)) { // 判断是否是Deferred的实例 // 这里的代码就是给Deferred.wait方法使用的, value._next = this._next; } else { // 如果不是,则继续执行 if (this._next) this._next._fire(next, value); } return this; }}

2.5 辅助静态方法

上面的代码中,可以看到一些Deferred对象的方法(静态方法),下面简单介绍一下:

// 默认的成功回调Deferred.ok = function (x) {return x};// 默认的失败回调Deferred.ng = function (x) {throw x};// 根据_id判断实例的实现Deferred.isDeferred = function (obj) { return !!(obj && obj._id === Deferred.prototype._id);}

2.6 简单小结

看到这里,我们需要停下来,看看一个简单的例子,来理解整个流程。

Defferred对象自身有next属性方法,在原型上也定义了next方法,需要注意这一点,例如以下代码:

var o = {};Deferred.define(o);o.next(function fn1(){ console.log(1);}).next(function fn2(){ console.log(2);});

o.next()是Deffered对象的属性方法,这个方法会返回一个Defferred对象的实例,因此下一个next()则是原型上的next方法。

第一个next()方法将后续的代码变成异步操作,后面的next()方法实际上是注册调用函数。

在第一个next()的异步操作里面唤起后面next()的调用链(d.call()),开始顺序的调用,换句话说就是,fn1和fn2是同步执行的。

那么,如果我们希望fn1和fn2也是异步执行,而不是同步执行的,这就得借助Deferred.wait方法了。

2.7 wait & register

我们可以使用wait来让fn1和fn2变成异步执行,代码如下:

Deferred.next(function fn1() { console.log(1)}).wait(0).next(function fn2() { console.log(2)});

wait方法很有意思,在Deferred的原型上并没有wait方法,而是在静态方法上找到了。

Deferred.wait = function (n) { var d = new Deferred(), t = new Date(); // 使用定时器来变成异步操作 var id = setTimeout(function () { d.call((new Date()).getTime() - t.getTime()); }, n * 1000); d.canceller = function () { clearTimeout(id); } return d;}

那么这个方法是怎么放到原型上的?原来是通过Deferred.register进行函数转换,绑定到原型上的。

Deferred.register = function (name, fun){ this.prototype[name] = function () { // 柯里化 var a = arguments; return this.next(function(){ return fun.apply(this, a); }); }};// 将方法注册到原型上Deferred.register("wait", Deferred.wait);

我们需要思考为什么要用这种方式将wait方法register到Deferred的原型对象上去?,因为明显这种方式有点难以理解。

结合例子,我们进行讨论,便能够彻底地理解上述的问题。

Deferred.next(function fn1(){ // d1 console.log(1);}).wait(1) // d2.next(function fn2(){ // d3 console.log(2);});

这段代码首先会建立一个调用链

4153318316-581dfe86654b6_articlex.jpg

之后,执行的过程为(如图所示)

4153318316-581dfe86654b6_articlex.jpg

我们来看看执行过程的几个关键点

图中的d1、d2、d3、d_wait表示在调用链上生成的Deferred对象的实例

在调用了d2的callback.ok即包装了wait()方法的匿名函数之后,返回了在wait()方法中生成的Deferred对象的实例d_wait,保存在变量value中,在_fire()方法中有一个if判断

if(Deferred.isDeferred(value)){ value._next = this._next;}
在这里并没有继续往下执行调用链的函数,而是重新建立了一个调用链,此时链头为d_wait,在wait()方法中使用setTimeout,使其异步执行,使用d.call()重新唤起调用链。

理解了整个过程,就比较好回到上面的问题了。之所以使用register的方式是因为原型上的wait方法并非直接使用Deferred.wait,而是把Deferred.wait方法作为参数,对原型上的next()方法进行curry化,然后返回一个柯里化之后的next()方法。而Deferred.wait()其实和Deferred.next()的作用很类似,都是异步执行接下来的操作。

2.8 并归结果 parallel

设想一个场景,我们需要多个异步网络查询任务,这些任务没有依赖关系,不需要区分前后,但是需要等待所有查询结果回来之后才能进一步处理,那么你会怎么做?在比较复杂的应用中,这个场景经常会出现,如果我们采用以下的方式(见伪代码)

var result = [];$.ajax("task1", function(ret1){ result.push(ret1); $.ajax("task2", function(ret2){ result.push(ret2); // 进行操作 });});

这种方式可以,但是却无法同时发送task1和task2(从代码上看还以为之间有依赖关系,实际上没有)。那怎么解决?这就是Deferred.parallel()所要解决的问题。

我们先来个简单的例子感受一下这种并归结果的方式。

Deferred.parallel(function () { return 1;}, function () { return 2;}, function () { return 3;}).next(function (a) { console.log(a); // [1,2,3]});

在parallel()方法执行之后,会将结果合并为一个数组,然后传递给next()中的callback.ok中。可以看到parallel里面都是同步的方法,先来看看parallel的源码是如何实现,再来看看能不能结合所学来改造实现我们所需要的ajax的效果。

Deferred.parallel = function (dl) { /* 前面都是对参数的处理,可以接收三种形式的参数 1. parallel(fn1, fn2, fn3).next() 2. parallel({ foo : $.get("foo.html"), bar : $.get("bar.html") }).next(function (v){ v.foo // => foo.html data v.bar // => bar.html data }); 3. parallel([fn1, fn2, fn3]).next(function (v) { v[0] // fn1执行的结果 v[1] // fn2执行的结果 v[3] // fn3执行返回的结果 }); */ var isArray = false; // 第一种形式 if (arguments.length > 1) { dl = Array.prototype.slice.call(arguments); isArray = true; // 其余两种形式,数组,类数组 } else if (Array.isArray && Array.isArray(dl) || typeof dl.length == "number") { isArray = true; } var ret = new Deferred(), // 用于归并结果的Deferred对象的实例 value = {}, // 收集函数执行的结果 num = 0 ; // 计数器,当为0时说明所有任务都执行完毕 // 开始遍历,这里使用for-in其实效率不高 for (var i in dl) { // 预防遍历了所有属性,例如toString之类的 if (dl.hasOwnProperty(i)) { // 利用闭包保存变量状态 (function (d, i){ // 使用Deferred.next()开始一个异步任务,并且执行完成之后,收集结果 if (typeof d == "function") dl[i] = d = Deferred.next(d); d.next(function (v) { values[i] = v; if( --num <= 0){ // 计数器为0说明所有任务已经完成,可以返回 if(isArray){ // 如果是数组的话,结果可以转换成数组 values.length = dl.length; values = Array.prototype.slice.call(values, 0); } // 调用parallel().next(function(v){}),唤起调用链 ret.call(values); } }).error(function (e) { ret.fail(e); }); num++; // 计数器加1 })(d[i], i); } } // 当计算器为0的时候,处理可能没有参数或者非法参数的情况 if (!num) { Deferred.next(function () { ret.call(); }); } ret.canceller = function () { for (var i in dl) { if (dl.hasOwnProperty(i)) { dl[i].cancel(); } } }; return ret; // 返回Deferred实例};

结合上述知识,我们可以在parallel中使用异步方法,代码如下

Deferred.parallel(function fn1(){ var d = new Deferred(); $.ajax("task1", function(ret1){ d.call(ret1); }); return d;}, function () { var d = new Deferred(); $.ajax("task2", function fn2(ret2) { d.call(ret2) }); return d;}).next(function fn3(ret) { ret[0]; // => task1返回的结果 ret[1]; // => task2返回的结果});

为什么可以这样?我们来图解一下,加深一下理解。

892670031-581ee02140c15_articlex.png

我们使用了_fire中的if判断,建立了新的调用链,获得去统计计数函数(即parallel中--num)的控制权,从而使得在parallel执行异步的方法。

小编还为您整理了以下内容,可能对您也有帮助:

javascript如何解决异步加载实现方式总结


默认情况javascript是同步加载的,也就是javascript的加载时阻塞的,后面的元素要等待javascript加载完毕后才能进行再加载,对于一些意义不是很大的javascript,如果放在页头会导致加载很慢的话,是会严重影响用户体验的。

(1) defer,只支持IE
defer属性的定义和用法
defer 属性规定是否对脚本执行进行延迟,直到页面加载为止。
有的 javascript 脚本 document.write 方法来创建当前的文档内容,其他脚本就不一定是了。
如果您的脚本不会改变文档的内容,可将 defer 属性加入到 <script> 标签中,以便加快处理文档的速度。因为浏览器知道它将能够安全地读取文档的剩余部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止。
示例:
<script type="text/javascript" defer="defer">
alert(document.getElementById("p1").firstChild.nodeValue);
</script>(2) async:
async的定义和用法(是HTML5的属性)
async 属性规定一旦脚本可用,则会异步执行。
示例:
<script type="text/javascript" src="demo_async.js" async="async"></script>注释:async 属性仅适用于外部脚本(只有在使用 src 属性时)。
注释:有多种执行外部脚本的方法:
?如果 async="async":脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)
?如果不使用 async 且 defer="defer":脚本将在页面完成解析时执行
?如果既不使用 async 也不使用 defer:在浏览器继续解析页面之前,立即读取并执行脚本
(3) 创建script,插入到DOM中,加载完毕后callBack,见代码:
function loadScript(url, callback){
var script = document.createElement_x("script")
script.type = "text/javascript";
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others: Firefox, Safari, Chrome, and Opera
script.onload = function(){
callback();
};
}
script.src = url;
document.body.appendChild(script);
}

javascript如何解决异步加载实现方式总结


默认情况javascript是同步加载的,也就是javascript的加载时阻塞的,后面的元素要等待javascript加载完毕后才能进行再加载,对于一些意义不是很大的javascript,如果放在页头会导致加载很慢的话,是会严重影响用户体验的。

(1) defer,只支持IE
defer属性的定义和用法
defer 属性规定是否对脚本执行进行延迟,直到页面加载为止。
有的 javascript 脚本 document.write 方法来创建当前的文档内容,其他脚本就不一定是了。
如果您的脚本不会改变文档的内容,可将 defer 属性加入到 <script> 标签中,以便加快处理文档的速度。因为浏览器知道它将能够安全地读取文档的剩余部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止。
示例:
<script type="text/javascript" defer="defer">
alert(document.getElementById("p1").firstChild.nodeValue);
</script>(2) async:
async的定义和用法(是HTML5的属性)
async 属性规定一旦脚本可用,则会异步执行。
示例:
<script type="text/javascript" src="demo_async.js" async="async"></script>注释:async 属性仅适用于外部脚本(只有在使用 src 属性时)。
注释:有多种执行外部脚本的方法:
?如果 async="async":脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)
?如果不使用 async 且 defer="defer":脚本将在页面完成解析时执行
?如果既不使用 async 也不使用 defer:在浏览器继续解析页面之前,立即读取并执行脚本
(3) 创建script,插入到DOM中,加载完毕后callBack,见代码:
function loadScript(url, callback){
var script = document.createElement_x("script")
script.type = "text/javascript";
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others: Firefox, Safari, Chrome, and Opera
script.onload = function(){
callback();
};
}
script.src = url;
document.body.appendChild(script);
}

javascript异步编程方法有哪些

JavaScript实现异步编程的方法有:易于理解和实现但是代码不好维护的回调方法,发布/订阅方法,易于理解并且可绑定多个事件但是工作流程混乱的事件监听方法,Promises方法

异步模式是非常重要,在浏览器端长时间运行的操作应该异步执行,以避免不响应。接下来将在文章中为大家详细介绍JavaScript中异步编程方法的实现,具有一定参考作用,希望对大家有所帮助。

【推荐课程:JavaScript教程】

大家都知道JavaScript的执行环境是单线程的,单线程就意味着在任何时候都只能运行一个任务。如果遇到多任务的时候就需要在队列中等待上一任务的完成。因此耗费时间比较多,同步模式就类似于这种单线程模式,异步模式是完全不同的,每一个任务都有一个回调函数,当一个任务完成后,它将执行回调函数,后面的任务可以与前一个任务同时运行。任务的执行顺序与队列中的任务序列不同。

方法一:回调方法

这个方法是异步编程的基本方法,假设有两个函数f1和f2,后者将等待第一个函数的结果。

F1();

F2();如果f1是长时间运行的操作,则可以重写f1并将f2作为f1的回调函数。

function f1(callback){

setTimeout(function () {

callback();

}, 1000);

}使用此模式,同步操作就可以转换为异步操作,f1不会阻止程序执行,它会将先执行主逻辑然后再执行耗时的操作

回调函数的优点是易于理解和实现,缺点是代码不可读和可维护,不同的组件高度耦合,工作流非常混乱,每个任务只能有一个回调函数。

方法二:发布/订阅

此事件可以理解为信号,假设存在信号中心,如果一个任务完成,它将向信号中心发布信号,其他任务可以从订阅信号中心接收指定信号。这种方法就称为发布/订阅模式或者是观察者模式。

例:f2向信号中心订阅完成信号

jQuery.subscribe(“done”,f2);然后写f1为

function f1(){

setTimeout(function () {

jQuery.publish("done");

}, 1000);

}jQuery.publish(“done”)表示当f1完成执行时它将向信号中心发送一个完成信号,然后f2将开始执行。

当f2完成执行时,它可以取消订阅。

jQuery.unsubscribe(“done”,f2);方法三:事件监听

另一种方法是事件驱动模式,一个任务的执行不依赖于代码顺序,它们等待一个事件发生。在本例中仍然使用f1和f2,首先将一个事件绑定到f1。

f1.on('done',f2);上述代码的含义是如果f1完成事件发生,则执行f2。

function f1(){

setTimeout(function () {

f1.trigger('done');

}, 1000);

}f1.trigger('done')表示它将触发done事件,然后在执行完成时执行f2。

优点是它易于理解并且可以绑定多个事件,每个事件都可以具有许多回调函数,并且它可以解耦哪个有利于模块化。缺点是整个程序将被事件驱动,工作流程不是很清楚。

方法四:Promises方法

Promises对象是CommonJS提出的标准,目的是为异步编程提供通用接口。每个异步任务都会返回一个Promises对象,这个对象有一个允许设置回调函数的then方法。例如,f1的回调函数f2:

F1().then(F2)f1应写成:

function f1(){

var dfd = $.Deferred();

setTimeout(function () {

dfd.resolve();

}, 500);

return dfd.promise;

}优点是回调函数是链接的,程序的工作流程非常清晰,它有一套完整的链接方法,可用于实现强大的功能。

例如,设置多个回调函数:

f1().then(f2).then(f3);还有一个例子,如果有错误:

f1().then(f2).fail(f3);其他三种方法不具备的一个优点是一旦完成一个任务,如果添加更多的回调函数,它们将立即执行。缺点是它不容易理解。

总结:

怎么实现JS同步、异步、延迟加载

这次给大家带来怎么实现JS同步、异步、延迟加载,实现JS同步、异步、延迟加载的注意事项有哪些,下面就是实战案例,一起来看一下。

一:同步加载

我们平时使用的最多的一种方式。

<script src="http://yourdomain.com/script.js"></script>

同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止后续的解析,只有当当前加载完成,才能进行下一步操作。所以默认同步执行才是安全的。但这样如果js中有输出document内容、修改dom、重定向等行为,就会造成页面堵塞。所以一般建议把<script>标签放在<body>结尾处,这样尽可能减少页面阻塞。

二:异步加载

异步加载又叫非阻塞加载,浏览器在下载执行js的同时,还会继续进行后续页面的处理。主要有三种方式。

方法一:也叫Script DOM Element

(function(){

var scriptEle = document.createElement("script");

scriptEle.type = "text/javasctipt";

scriptEle.async = true;

scriptEle.src = "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js";

var x = document.getElementsByTagName("head")[0];

x.insertBefore(scriptEle, x.firstChild);

})();<async>属性是HTML5中新增的异步支持。此方法被称为Script DOM Element 方法。Google Analytics 和 Google+ Badge 都使用了这种异步加载代码。

(function(){;

var ga = document.createElement('script');

ga.type = 'text/javascript';

ga.async = true;

ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';

var s = document.getElementsByTagName('script')[0];

s.parentNode.insertBefore(ga, s);

})();但是这种加载方式执行完之前会阻止onload事件的触发,而现在很多页面的代码都在onload时还执行额外的渲染工作,所以还是会阻塞部分页面的初始化处理。

方法二:onload时的异步加载

(function(){

if(window.attachEvent){

window.attachEvent("load", asyncLoad);

}else{

window.addEventListener("load", asyncLoad);

}

var asyncLoad = function(){

var ga = document.createElement('script');

ga.type = 'text/javascript';

ga.async = true;

ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';

var s = document.getElementsByTagName('script')[0];

s.parentNode.insertBefore(ga, s);

}

)();这种方法只是把插入script的方法放在一个函数里面,然后放在window的onload方法里面执行,这样就解决了阻塞onload事件触发的问题。

注:DOMContentLoaded与load的区别。前者是在document已经解析完成,页面中的dom元素可用,但是页面中的图片,视频,音频等资源未加载完,作用同jQuery中的ready事件;后者的区别在于页面所有资源全部加载完毕。

方法三:其他方法

由于JavaScript的动态性,还有很多异步加载方法: XHR Injection、 XHR Eval、 Script In Iframe、 Script defer属性、 document.write(script tag)。

XHR Injection(XHR 注入):通过XMLHttpRequest来获取javascript,然后创建一个script元素插入到DOM结构中。ajax请求成功后设置script.text为请求成功后返回的responseText。

//获取XMLHttpRequest对象,考虑兼容性。

var getXmlHttp = function(){

var obj;

if (window.XMLHttpRequest)

obj = new XMLHttpRequest();

else

obj = new ActiveXObject("Microsoft.XMLHTTP");

return obj;

};

//采用Http请求get方式;open()方法的第三个参数表示采用异步(true)还是同步(false)处理

var xmlHttp = getXmlHttp();

xmlHttp.open("GET", "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js", true);

xmlHttp.send();

xmlHttp.onreadystatechange = function(){

if (xmlHttp.readyState == 4 && xmlHttp.status == 200){

var script = document.createElement("script");

script.text = xmlHttp.responseText;

document.getElementsByTagName("head")[0].appendChild(script);

}

}XHR Eval:与XHR Injection对responseText的执行方式不同,直接把responseText放在eval()函数里面执行。

//获取XMLHttpRequest对象,考虑兼容性。

var getXmlHttp = function(){

var obj;

if (window.XMLHttpRequest)

obj = new XMLHttpRequest();

else

obj = new ActiveXObject("Microsoft.XMLHTTP");

return obj;

};

//采用Http请求get方式;open()方法的第三个参数表示采用异步(true)还是同步(false)处理

var xmlHttp = getXmlHttp();

xmlHttp.open("GET", "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js", true);

xmlHttp.send();

xmlHttp.onreadystatechange = function(){

if (xmlHttp.readyState == 4 && xmlHttp.status == 200){

eval(xmlHttp.responseText);

//alert($);//可以弹出$,表明JS已经加载进来。click事件放在其它出会出问题,应该是还没加载进来

$("#btn1").click(function(){

alert($(this).text());

});

}

}Script In Irame:在父窗口插入一个iframe元素,然后再iframe中执行加载JS的操作。

var insertJS = function(){alert(2)};

var iframe = document.createElement("iframe");

document.body.appendChild(iframe);

var doc = iframe.contentWindow.document;//获取iframe中的window要用contentWindow属性。

doc.open();

doc.write("<script>var insertJS = function(){};</script><body onload='insertJS()'></body>");

doc.close();GMail Mobile:业内JS内容被注释,所以不会执行,在需要的时候,获取script中的text内容去掉注释,调用eval()执行。

<script type="text/javascript">

/*

var ...

*/

</script>HTML5新属性:async和defer属性

defer属性:IE4.0就出现。defer属声明脚本中将不会有document.write和dom修改。浏览器会并行下载其他有defer属性的script。而不会阻塞页面后续处理。注:所有的defer脚本必须保证按顺序执行的。

<script type="text/javascript" defer></script>

async属性:HTML5新属性。脚本将在下载后尽快执行,作用同defer,但是不能保证脚本按顺序执行。他们将在onload事件之前完成。

<script type="text/javascript" defer></script>

Firefox 3.6、Opera 10.5、IE 9和最新的Chrome和Safari都支持async属性。可以同时使用async和defer,这样IE 4之后的所有IE都支持异步加载。

没有async属性,script将立即获取(下载)并执行,期间阻塞了浏览器的后续处理。如果有async属性,那么script将被异步下载并执行,同时浏览器继续后续的处理。

总结: 对于支持HTML5的浏览器,实现JS的异步加载只需要在script元素中加上async属性,为了兼容老版本的IE还需加上defer属性;对于不支持HTML5的浏览器(IE可以用defer实现),可以采用以上几种方法实现。原理基本上都是向DOM中写入script或者通过eval函数执行JS代码,你可以把它放在匿名函数中执行,也可以在onload中执行,也可以通过XHR注入实现,也可以创建一个iframe元素,然后在iframe中执行插入JS代码。

三:延迟加载

有些JS代码在某些情况在需要使用,并不是页面初始化的时候就要用到。延迟加载就是为了解决这个问题。将JS切分成许多模块,页面初始化时只加载需要立即执行的JS,然后其它JS的加载延迟到第一次需要用到的时候再加载。类似图片的延迟加载。

JS的加载分为两个部分:下载和执行。异步加载只是解决了下载的问题,但是代码在下载完成后就会立即执行,在执行过程中浏览器处于阻塞状态,响应不了任何需求。

解决思路:为了解决JS延迟加载的问题,可以利用异步加载缓存起来,但不立即执行,需要的时候在执行。如何进行缓存呢?将JS内容作为Image或者Object对象加载缓存起来,所以不会立即执行,然后在第一次需要的时候在执行。

1:模拟较长的下载时间:

利用thread让其sleep一段时间在执行下载操作。

2:模拟较长的JS代码执行时间

var start = Number(new Date());

while(start + 5000 > Number(new Date())){//执行JS}这段代码将使JS执行5秒才完成!

JS延迟加载机制(LazyLoad):简单来说,就是在浏览器滚动到某个位置在触发相关的函数,实现页面元素的加载或者某些动作的执行。如何实现浏览器滚动位置的检测呢?可以通过一个定时器来实现,通过比较某一时刻页面目标节点位置和浏览器滚动条高度来判断是否需要执行函数。

相信看了本文案例你已经掌握了方法,更多精彩请关注Gxl网其它相关文章!

推荐阅读:

vue自动化表单有哪几种方式

vue-element怎么做出音乐播放器

吉希宠物网还为您提供以下相关内容希望对您有帮助:

javascript如何解决异步加载实现方式总结

默认情况javascript是同步加载的,也就是javascript的加载时阻塞的,后面的元素要等待javascript加载完毕后才能进行再加载,对于一些意义不是很大的javascript,如果放在页头会导致加载很慢的话,是会严重影响用户体验的。 (1) def...

javascript异步编程方法有哪些

方法一:回调方法这个方法是异步编程的基本方法,假设有两个函数f1和f2,后者将等待第一个函数的结果。F1(); F2();如果f1是长时间运行的操作,则可以重写f1并将f2作为f1的回调函数。function f1(callback){setTimeout...

...得到的是同步执行的效果,为什么会是叫做异步

因为它的内部实现是ajax,本来就是异步的,所以是异步编程。jQuery.when(deferreds)参数deferreds,一个或多个延时对象或JS对象,我们初略的认为它就是一个或多个异步请求。例如:[javascript] view plain copy .when($.aj...

怎么实现JS同步、异步、延迟加载

原理基本上都是向DOM中写入script或者通过eval函数执行JS代码,你可以把它放在匿名函数中执行,也可以在onload中执行,也可以通过XHR注入实现,也可以创建一个iframe元素,然后在iframe中执行插入JS代码。三:延迟加载有些JS代码在某些情况在需要...

javascript同步和异步的区别与实现方式

promise对象是CommonJS工作组提供的一种规范,用于异步编程的统一接口。promise对象通常实现一种then的方法,用来在注册状态发生改变时作为对应的回调函数。promise模式在任何时刻都处于以下三种状态之一:未完成(unfulfilled)、已...

JS是什么?

JavaScript是一种可以实现异步编程的语言,支持回调函数和Promise等异步编程模式,可以轻松处理异步操作和事件响应。JavaScript的重要特点:1、JavaScript是一种动态类型语言。这意味着您不需要在声明变量时指定其类型。例如,您可以...

angularjs的¥q的用法

在用JQuery的时候就知道 promise 是 Js异步编程模式的一种模式,但是不是很明白他跟JQuery的deferred对象有什么区别。随着公司项目的进行,要跟后台接数据了,所以决定搞定它。Promise Promise是一种模式,以同步操作的流程形式...

怎样用JS实现异步转同步

观察了这两种最广泛的异步转同步式调用,我们发现均没有采用js来实现。似乎从现象层面上来看js无法原生支持,但是这还不够,我们探究在js语义下上面的自旋锁/信号量的特性模拟实现(我知道你们一定会嗤之以鼻,==js本身就是单线程的,只是...

JS编程是什么意思?

简单来说,就是编写编定程序,让计算机代码解决某个问题,对某个计算体系规定一定的运算方式,使计算体系按照该计算方式运行,并最终得到相应结果的过程。为了使计算机能够理解人的想法,人类通过某种方式,将需解决的问题的思路...

关于generator异步编程的理解以及如何动手写

"javascript是单线程的,顺序执行一段代码,执行到了异步操作,按正常的逻辑走的话就是主队列中的代码继续执行,这时异步队列中的代码还未执行,我们继续执行的代码也就会发生报错。那么解决问题的关键就是,我们能够手动控制...

Top