jQuery实现的ajax队列(queue)

先来看个需求,页面上有2个ajax call。其中一个ajax需要另一个ajax的数据才能操作。如下面这段代码所描述的

$(function(){
    var a_data;
    $.ajax({
        url: "test.php",
        success: function(data){
            a_data = data;
        },
    });
    $.ajax({
        url: "test.php",
        success: function(data){
            if(a_data == "5"){
                //....
            }
        },
    });
});

第二个ajax的操作,需要等待第一个ajax的数据才能进行。(ps:当然以上写法是错误的,这里只是描述这个需求)

相信不少人都遇到ajax queue 队列的问题。好在自从jquery 1.3 以后,有个function能够很好的支持队列,那就是queue

queue(name)
返回指向第一个匹配元素的队列(将是一个函数数组)

要实现ajax队列,可以将ajax引入queue中。如下代码实现:

// 第一个ajax请求
$(document).queue("ajaxRequests", function(){
    //全局变量,储存第一个ajax请求数据
    var a_data;
    $.ajax({
        success: function(data){
            a_data = data;
            $(document).dequeue("myName");
        }
    });
});
// 第二个ajax请求
$(document).queue("ajaxRequests", function() {
  $.ajax({
    success: function(data) {
      alert(a_data);
      $(document).dequeue("myName");
    }
  });
});
// 触发queue往下执行
$(document).dequeue("ajaxRequests");

以上代码实现了ajax队列,2个ajax同步执行,其中dequeue用来结束当前ajax,并调用下一个ajax请求。

接下来,我们再来看看另一个ajax 队列的需求(需求二):
在注册的时候验证邮箱、用户名等时候,单个客户端可以频繁发出无数的ajax请求出去,而我们的结果肯定是以最后一个ajax请求为准的。
首先模拟一个服务端页面

<?PHP
    sleep(5);
    exit(time().'');
?>

然后是前台页面,假设由一个元素触发:
html代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
        <title>测试ajax队列</title>
    </head>
<body>
    <div id="dtitle">点此触发</div>
</body>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script type="text/javascript" src="test.js"></script>
</html>

JS代码:

$(function(){
    $("body").queue([]);
    $("#dtitle").click(function(){
        $("body").queue(function(){
            $.get("test.php?t=" + new Date().getMilliseconds(), function(){
                //这一句让queue往下执行;
                $("body").dequeue();
                if ($("body").queue().length == 0)
                    alert("done");
            });
        });
    });
});

下面是firebug下的执行结果,我连续对dtitle元素点击三次,如愿每5秒才发送一次请求。当然这只是演示的原理,既然发了三次请求,肯定要以最后一次为准,那么可以通过队列的length属性来轮循,一时length变为0了,就是全部请求结束了,你就可以执行你想要的逻辑了

好了,通过上面的代码,应该对queue的用法和ajax队列有一定了解了。人总是要不断追求完美的,粗矿的代码,并不能提升代码水平。既然队列那么常用,那是否需要将这个功能封装?答案是肯定的。网上也有一些已经封装好了的插件。
那就重写$.ajax 这个方法吧。说干就干:

(function($) {
    var ajax = $.ajax,
        pendingRequests = {},
        synced = [],
        syncedData = [],
        ajaxRunning = [];
    $.ajax = function(settings) {
        // create settings for compatibility with ajaxSetup
        settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings));
        var port = settings.port;
        switch (settings.mode) {
            case "abort":
                if (pendingRequests[port]) {
                    pendingRequests[port].abort();
                }
                return pendingRequests[port] = ajax.apply(this, arguments);
            case "queue":
                var _old = settings.complete;
                settings.complete = function() {
                    if (_old) {
                        _old.apply(this, arguments);
                    }
                    if (jQuery([ajax]).queue("ajax" + port).length > 0) {
                        jQuery([ajax]).dequeue("ajax" + port);
                    } else {
                        ajaxRunning[port] = false;
                    }
                };
                jQuery([ajax]).queue("ajax" + port, function() {
                    ajax(settings);
                });
                if (jQuery([ajax]).queue("ajax" + port).length == 1 && !ajaxRunning[port]) {
                    ajaxRunning[port] = true;
                    jQuery([ajax]).dequeue("ajax" + port);
                }
                return;
            case "sync":
                var pos = synced.length;
                synced[pos] = {
                    error: settings.error,
                    success: settings.success,
                    complete: settings.complete,
                    done: false
                };
                syncedData[pos] = {
                    error: [],
                    success: [],
                    complete: []
                };
                settings.error = function() { syncedData[pos].error = arguments; };
                settings.success = function() { syncedData[pos].success = arguments; };
                settings.complete = function() {
                    syncedData[pos].complete = arguments;
                    synced[pos].done = true;
                    if (pos == 0 || !synced[pos - 1])
                        for (var i = pos; i < synced.length && synced[i].done; i++) {
                        if (synced[i].error) synced[i].error.apply(jQuery, syncedData[i].error);
                        if (synced[i].success) synced[i].success.apply(jQuery, syncedData[i].success);
                        if (synced[i].complete) synced[i].complete.apply(jQuery, syncedData[i].complete);
                        synced[i] = null;
                        syncedData[i] = null;
                    }
                };
        }
        return ajax.apply(this, arguments);
    };
})(jQuery);

以上代码加入了1个mode变量,有3个值”abort”(中止),”queue”(队列),”sync”同步。

对于需求二,我们用这个封装好的ajax改写并改进下,js代码部分如下:

$(function(){
    $("body").queue([]);
    $("#dtitle").click(function(){
        $.ajax({
            url: "test.php?t=" + new Date().getMilliseconds(),
            success: function(html){
                jQuery("ul").append(html);
            },
            //用abort而不用queue,是因为需求是需要最后一个ajax request,而之前的ajax request
            //其实并没有用,那何必要等它们执行完呢?中途就可以把它中止掉
            mode: "abort"
        });
    });
});

看到这里,相信你已经完全了解ajax队列了。就算不了解,你也可以直接用封装好的那段js代码,直接用mode: “abort”
就可以了。

再谈mobile web retina 下 1px 边框解决方案

本文实际上想说的是ios8下 1px解决方案。 1px的边框在devicePixelRatio = 2的retina屏下会显示成2px,在iphone 6 plug 下,更显示成3px。由其影响美感。

还好,时代总是进步的。也许很多人都不知道,现在IOS8下,已经支持0.5px了。。 那么意味着,在devicePixelRatio = 2下,我们可以使用如下的css代码:

但是在ios7以下,android等其他系统里,0.5px会被显示为0px,即该解决方案需要写hack兼容老旧系统。

三种方案:

1、JS判断UA,是否是ios8+,是的话则输出类名hairlines,为了防止重绘,这段代码加在head里即可。

if (/iP(hone|od|ad)/.test(navigator.userAgent)) {
	var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/),
		version = parseInt(v[1], 10);
	if(version >= 8){
		document.documentElement.classList.add('hairlines')
	}
}

2、JS判断是否支持0.5px边框,是的话,则输出类名hairlines

if (window.devicePixelRatio && devicePixelRatio >= 2) {
  var testElem = document.createElement('div');
  testElem.style.border = '.5px solid transparent';
  document.body.appendChild(testElem);
  if (testElem.offsetHeight == 1)
  {
    document.querySelector('html').classList.add('hairlines');
  }
  document.body.removeChild(testElem);
}
// This assumes this script runs in <body>, if it runs in <head> wrap it in $(document).ready(function() {   })

相比于第一种方法,这种方法的可靠性更高一些,但是需要把js放在body标签内,相对来说会有一些重绘,个人建议是用第一种方法。

3、服务端做ios版本判断,输出相应的类名

相比于JS的实现,个人更倾向于在服务端完成,这样前端也少几行代码,并且更加可靠。

如在wormhole里的实现(wormhole是nodejs环境下的一个服务端渲染模版的容器)

{{#if($plugins.detector.os.name === "ios" && $plugins.detector.os.version >= 8)}}
    {{set (hairlines = "hairlines")}}
{{/if}}
<html class="{{hairlines}}">

加上类名后,就可以针对该类名写相应的css了。比如:

div{border:1px solid #000}
.hairlines div{border-width:0.5px}

也许你会问,那ios7以下和其他android机下怎么搞?我的建议是:还是维持老样,不去处理,随着时间的推移,我相信最终都会支持0.5 和 0.3 px边框的。

如果硬要兼容,怎么整?方案也有很多,稍微介绍下:

1、通过viewport + REM的方式来兼容。

目前这种兼容方案相对比较完美,适合新项目(老项目改用REM单位成本会比较高)。淘宝M首页就是这种方案。

在devicePixelRatio = 2 时,输出viewport

<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">

在devicePixelRatio = 3 时,输出viewport

<meta name="viewport" content="initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no">

同时通过设置对应viewport的rem基准值,这种方式就可以像以前一样轻松愉快的写1px了。关于REM布局,可以参考下我上一篇文章《移动端H5页面之iphone6的适配》

其他方案(该部分内容来源于妙净同学的分享):

2、 transform: scale(0.5)

实现方式

height:1px;
-webkit-transform: scaleY(0.5);
-webkit-transform-origin:0 0;
overflow: hidden;

优点

圆角无法实现,hack代码多,实现4条边框比较闹心

缺点

只能单独使用,如果嵌套,scale的作用也会对包含的元素产生,不想要的影响,所以此种方案配合:after和:before独立使用较多,比如画一个商品的边框四条线,容器的after和before可以画2条线,利用容器的父元素的after、before再画2条线。

.after-scale{
    position: relative;
}
.after-scale:after{
    content:"";
    position: absolute;
    bottom:0px;
    left:0px;
    right:0px;
    border-bottom:1px solid #c8c7cc;
    -webkit-transform:scaleY(.5);
    -webkit-transform-origin:0 0;
}

3、 box-shadow

实现方式

利用css 对阴影处理的方式实现0.5px的效果

底部一条线

-webkit-box-shadow:0 1px 1px -1px rgba(0, 0, 0, 0.5);

优点
基本所有场景都能满足,包含圆角的button,单条,多条线,

缺点

颜色不好处理, 黑色 rgba(0,0,0,1) 最浓的情况了。有阴影出现,不好用。

参考链接

4、 background-image

实现方式

设置1px通过css 实现的image,50%有颜色,50%透明

.border {
        background-image:linear-gradient(180deg, red, red 50%, transparent 50%),
        linear-gradient(270deg, red, red 50%, transparent 50%),
        linear-gradient(0deg, red, red 50%, transparent 50%),
        linear-gradient(90deg, red, red 50%, transparent 50%);
        background-size: 100% 1px,1px 100% ,100% 1px, 1px 100%;
        background-repeat: no-repeat;
        background-position: top, right top,  bottom, left top;
        padding: 10px;
    }

优点

配合background-image,background-size,background-position 可以实现单条,多条边框。边框的颜色随意设置

缺点

如果有圆角的效果,很sorry 圆角的地方没有线框的颜色。都要写的代码也不少

参考链接

5、 用图片

实现方式

.border-image{
    border-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAECAYAAABP2FU6AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAB5JREFUeNpiPnH8zH/G////MzAxAAHTyRNn/wMEGABpvQm9g9TJ1QAAAABJRU5ErkJggg==") 2 0 stretch;
border-width: 0px 0px 1px;
}

优点

缺点

也可以通过修改图片来达到圆角的效果,但是由于图片的原因,压缩过后的图片边缘变模糊了(不放大的情况下不明显),需要引用图片或者base64,边框颜色修改起来不方便。

参考:http://dieulot.net/css-retina-hairline