欢迎访问 licqi IT技术
我们一直在努力

CORS机制及其风险

licqi阅读(305)

根据同源策略,浏览器默认是不允许XMLHttpRequest对象问非同一站点下的资源的,即用ajax方式访问非同一域名下的资源会出错。比如当google要通过ajax去访问百度的数据,是不行的。

所谓同源,是要求协议,域名,端口都相同。

比如 http://www.aaa.com 和下列URL相比,都不属于同源。

https://www.aaa.com

http://www.aaa.com:8080

http://aaa.com

但下面这种属于同源:

http://username:password@www.aaa.com

禁用跨域访问资源是为了安全,但会牺牲便利性。因此就出现了好几种跨域访问资源的方法。其中一种是称之为CORS的技术(Cross-origin resource sharing)。

CORS的概念

所谓CORS(Cross-origin resource sharing ),是指通过XMLHttpRequest的ajax方式访问其他域名下资源,而不是在A域名的页面上点击打开一个B域名的页面。引入不同域上的js脚本文件也是没用问题的。出于安全考虑,跨域请求不能访问document.cookie对象。

CORS技术允许跨域访问多种资源,比如javascript,字体文件等,这种技术对XMLHttpRequest做了升级,使之可以进行跨域访问。但不是所有的浏览器都支持CORS技术。Firefox和Chrome等浏览器支持的比较好,稍微新一点的版本都支持。IE比较搓,IE10才真正支持这个机制,IE10以下需要用XDomainRequest这个对象,这是IE特有的。

当然不是说浏览器支持了就立刻可以跨域访问了,CORS技术中最重要的关键点是响应头里的Access-Control-Allow-Origin这个Header。 此Header是W3C标准定义的用来检查是可否接受跨域请求的一个标识。实现过程大致如下:A域名中发起跨域请求到B域名,B域名如果发送响应头Access-Control-Allow-Origin: A域名,那么A域名的这次跨域请求就能成功。反之则不成功。这里有一个称之为Preflighted requests 的步骤,这一步骤中,浏览器发起一个OPTIONS请求,去服务器验证是否支持跨域访问。(

用chrome去查看这个option请求始终看不到,只有当跨域访问请求非正常(比如本人笔误,将请求type的get误写成了gey)的情况下,才会看到有个OPTIONS请求,如图:

非常奇怪,不知道为何。

CORS的实现

举个例子。

有2个站点,分别绑定了www.myhost1.com和www.myhost2.com:8001这个两个域名。

假设www.myhost2.com:8001下的a.html文件,需要访问www.myhost1.com域名下的b.aspx文件,代码如下。

a文件代码如下,点击按钮就会去跨域访问b页面:

  
    
    
    
Apage    
    
    
$(function () {    
var options = {    
type: 'get',    
url: "
http://www.myhost1.com/mysite/cors/b.aspx
",    
success: function (result) {    
alert(result);    
}    
};    
$("#btn1").click(function () {    
$.ajax(options);    
});    
});    
    
    
    
    
    

b文件为一个aspx文件,代码非常简单,显示时间


A文件的地址为http://www.myhost2.com:8001/a.html

A文件的请求是按照get方式读取的,当A文件点击按钮,会弹出B文件的内容。由于同源策略,会看到如下错误:

为了避免这个错误,在www.myhost1.com主机的Http响应标头里加上Access-Control-Allow-Origin:http://www.myhost2.com:8001,注意不要打上最后一个斜杠/,意思是允许http://www.myhost2.com:8001这个域名中的XMLHttpRequest对象跨域访问www.myhost1.com主机下的资源。

还有一种方法是修改web.config,Access-Control-Allow-Origin的值可以是通配符*,比如如下:



    
     
       
     
   
  

或者通过代码添加响应标头来实现。

     

当浏览器通过ajax请求访问其他域名下的资源时,如果那个资源的响应头中包含了Access-Control-Allow-Origin,则可以请求成功,否则就会失败,是否设置了响应头,可以在firebug等调试工具内看到。

无响应头

有响应头

跨域访问的风险

默认情况下,CORS机制不读取cookie值,即跨域访问时没有带上cookie,当需要跨域访问带cookie时,需要设置另一个文件头,Access-Control-Allow-Credentials,值为true。该选项设定了是否允许跨域请求带cookie。当设定为true后,对于XMLHttpRequest还需要设定withCredentials为true,在Jquery中为xhrFields: {withCredentials: true}

一旦允许了带cookie的跨域访问,那么遭到CSRF***的概率大大的增加了,比如之前的假设a.html页面内有一段恶意的js,它每隔1分钟去跨域请求用户b页面,并将请求后的数据偷偷的存入自己的数据库内。假设b页面需在要用户登录的情况下,可以请求到很多机密数据,比如信用卡号等,那当用户打开a页面后,在非授意的情况下,获取到b面值的内容,引发泄密。

对之前的演示代码做小的修改:

a页面代码:




Apage


$(function 
() {
var options = {
type: 'get',
xhrFields: {withCredentials: true},
url: "http://www.myhost1.com/mysite/cors/b.aspx",
success: 
function (result) {
alert(result);
}
};
$("#btn1").click(function () 
{
$.ajax(options);
});
});





b.aspx页面代码如下:

  
  

假设b页面的用户登录了一下,种植了一个session,那么就会有一个sessionid被存储到用户的cookie中去,此时,a页面点击按钮后,会获取b页面显示的内容,根据***的行为,可以有更大的破坏力。

演示图:当b页面未登录时,显示未登录。请求中不带cookie。

但是当b页面已经登录,则在a页面中的跨域请求中,带有cookie,会显示已登录的页面。

参考资料:

http://drops.wooyun.org/tips/188

http://en.wikipedia.org/wiki/Cross-origin_resource_sharing

https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

https://dev.opera.com/articles/dom-access-control-using-cors/

http://blog.darkthread.net/post-2014-09-29-cors-options-preflight-and-iis.aspx

http://www.cnblogs.com/idche/p/3190926.html

jQuery 关于ajaxfileupload.js插件的逐步解析(ajaxfileupload.js第二弹)

licqi阅读(158)

如果你看了上一篇《ASP.NET 使用js插件出现上传较大文件失败的解决方法(ajaxfileupload.js第一弹)》的话,应该就知道我是逼不得已要认真学习下ajaxfileupload.js这个上传文件插件的。哈哈,开个玩笑啦,其实学习是给自己学的,而且学会了真的是很享受的~

这篇呢,就是想把这个插件的思路说一下,其中中文注解是我写的,英文注解应该是原作者写的吧~说实话,有些if判断里的东西我也没太弄明白,但是大致思路还是OK的。

jQuery.extend({


    createUploadIframe: function (id, uri) {//id为当前系统时间字符串,uri是外部传入的json对象的一个参数
        //create frame
        var frameId = 'jUploadFrame' + id; //给iframe添加一个独一无二的id
        var iframeHtml = '<iframe id="' + frameId + '" name="' + frameId + '" style="position:absolute; top:-9999px; left:-9999px" ';="" 创建iframe元素="" if="" (window.activexobject)="" {="" 判断浏览器是否支持activex控件="" (typeof="" uri="=" 'boolean')="" iframehtml="" +=" src="" 'javascript:false'="" '"';="" }="" else="" 'string')="" ;="" jquery(iframehtml).appendto(document.body);="" 将动态iframe追加到body中="" return="" jquery('#'="" frameid).get(0);="" 返回iframe对象="" },="" createuploadform:="" function="" (id,="" fileelementid,="" data)="" id为当前系统时间字符串,fileelementid为页面的id,data的值需要根据传入json的键来决定
        //create form    
        var formId = 'jUploadForm' + id; //给form添加一个独一无二的id
        var fileId = 'jUploadFile' + id; //给添加一个独一无二的id
        var form = jQuery('
'); //创建form元素 if (data) {//通常为false for (var i in data) { jQuery('').appendTo(form); //根据data的内容,创建隐藏域,这部分我还不知道是什么时候用到。估计是传入json的时候,如果默认传一些参数的话要用到。 } } var oldElement = jQuery('#' + fileElementId); //得到页面中的对象 var newElement = jQuery(oldElement).clone(); //克隆页面中的对象 jQuery(oldElement).attr('id', fileId); //修改原对象的id jQuery(oldElement).before(newElement); //在原对象前插入克隆对象 jQuery(oldElement).appendTo(form); //把原对象插入到动态form的结尾处 //set attributes jQuery(form).css('position', 'absolute'); //给动态form添加样式,使其浮动起来, jQuery(form).css('top', '-1200px'); jQuery(form).css('left', '-1200px'); jQuery(form).appendTo('body'); //把动态form插入到body中 return form; }, ajaxFileUpload: function (s) {//这里s是个json对象,传入一些ajax的参数 // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout s = jQuery.extend({}, jQuery.ajaxSettings, s); //此时的s对象是由jQuery.ajaxSettings和原s对象扩展后的对象 var id = new Date().getTime(); //取当前系统时间,目的是得到一个独一无二的数字 var form = jQuery.createUploadForm(id, s.fileElementId, (typeof (s.data) == 'undefined' ? false : s.data)); //创建动态form var io = jQuery.createUploadIframe(id, s.secureuri); //创建动态iframe var frameId = 'jUploadFrame' + id; //动态iframe的id var formId = 'jUploadForm' + id; //动态form的id // Watch for a new set of requests if (s.global && !jQuery.active++) {//当jQuery开始一个ajax请求时发生 jQuery.event.trigger("ajaxStart"); //触发ajaxStart方法 } var requestDone = false; //请求完成标志 // Create the request object var xml = {}; if (s.global) jQuery.event.trigger("ajaxSend", [xml, s]); //触发ajaxSend方法 // Wait for a response to come back var uploadCallback = function (isTimeout) {//回调函数 var io = document.getElementById(frameId); //得到iframe对象 try { if (io.contentWindow) {//动态iframe所在窗口对象是否存在 xml.responseText = io.contentWindow.document.body ? io.contentWindow.document.body.innerHTML : null; xml.responseXML = io.contentWindow.document.XMLDocument ? io.contentWindow.document.XMLDocument : io.contentWindow.document; } else if (io.contentDocument) {//动态iframe的文档对象是否存在 xml.responseText = io.contentDocument.document.body ? io.contentDocument.document.body.innerHTML : null; xml.responseXML = io.contentDocument.document.XMLDocument ? io.contentDocument.document.XMLDocument : io.contentDocument.document; } } catch (e) { jQuery.handleError(s, xml, null, e); } if (xml || isTimeout == "timeout") {//xml变量被赋值或者isTimeout == "timeout"都表示请求发出,并且有响应 requestDone = true; //请求完成 var status; try { status = isTimeout != "timeout" ? "success" : "error"; //如果不是“超时”,表示请求成功 // Make sure that the request was successful or notmodified if (status != "error") { // process the data (runs the xml through httpData regardless of callback) var data = jQuery.uploadHttpData(xml, s.dataType); //根据传送的type类型,返回json对象,此时返回的data就是后台操作后的返回结果 // If a local callback was specified, fire it and pass it the data if (s.success) s.success(data, status); //执行上传成功的操作 // Fire the global callback if (s.global) jQuery.event.trigger("ajaxSuccess", [xml, s]); } else jQuery.handleError(s, xml, status); } catch (e) { status = "error"; jQuery.handleError(s, xml, status, e); } // The request was completed if (s.global) jQuery.event.trigger("ajaxComplete", [xml, s]); // Handle the global AJAX counter if (s.global && ! --jQuery.active) jQuery.event.trigger("ajaxStop"); // Process result if (s.complete) s.complete(xml, status); jQuery(io).unbind();//移除iframe的事件处理程序 setTimeout(function () {//设置超时时间 try { jQuery(io).remove();//移除动态iframe jQuery(form).remove();//移除动态form } catch (e) { jQuery.handleError(s, xml, null, e); } }, 100) xml = null } } // Timeout checker if (s.timeout > 0) {//超时检测 setTimeout(function () { // Check to see if the request is still happening if (!requestDone) uploadCallback("timeout");//如果请求仍未完成,就发送超时信号 }, s.timeout); } try { var form = jQuery('#' + formId); jQuery(form).attr('action', s.url);//传入的ajax页面导向url jQuery(form).attr('method', 'POST');//设置提交表单方式 jQuery(form).attr('target', frameId);//返回的目标iframe,就是创建的动态iframe if (form.encoding) {//选择编码方式 jQuery(form).attr('encoding', 'multipart/form-data'); } else { jQuery(form).attr('enctype', 'multipart/form-data'); } jQuery(form).submit();//提交form表单 } catch (e) { jQuery.handleError(s, xml, null, e); } jQuery('#' + frameId).load(uploadCallback); //ajax 请求从服务器加载数据,同时传入回调函数 return { abort: function () { } }; }, uploadHttpData: function (r, type) { var data = !type; data = type == "xml" || data ? r.responseXML : r.responseText; // If the type is "script", eval it in global context if (type == "script") jQuery.globalEval(data); // Get the JavaScript object, if JSON is used. if (type == "json") eval("data = " + data); // evaluate scripts within html if (type == "html") jQuery("
").html(data).evalScripts(); return data; } })

ajaxfileupload.js插件大致的思路就是如上所述,但是对于ajax来说,传值也是相当关键的部分,也就是传入的json对象里的键值对。

调用方法如下:

$.ajaxFileUpload
(
    {
        url: '../../XXXX/XXXX.aspx', //用于文件上传的服务器端请求地址
        secureuri: false,           //一般设置为false
        fileElementId: $("input#xxx").attr("id"), //文件上传控件的id属性   注意,这里一定要有name值   
                                                //$("form").serialize(),表单序列化。指把所有元素的ID,NAME 等全部发过去
        dataType: 'json',//返回值类型 一般设置为json
        complete: function () {//只要完成即执行,最后执行
        },
        success: function (data, status)  //服务器成功响应处理函数
        {
            if (typeof (data.error) != 'undefined') {
                if (data.error != '') {
                    if (data.error == "1001") {//这个error(错误码)是由自己定义的,根据后台返回的json对象的键值而判断
                    }
                    else if (data.error == "1002") {
                    }
                    alert(data.msg);//同error
                    return;
                } else {
                    alert(data.msg);
                }
            }
            /*
                *    这里就是做一些其他操作,比如把图片显示到某控件中去之类的。
                */
        },
        error: function (data, status, e)//服务器响应失败处理函数
        {
            alert(e);
        }
    }
)

整个就是使用ajaxfileupload.js插件的大致方法。当然,明白其工作原理越透彻,我们也就能越好的去操作和使用它。

以上的分析希望对刚接触ajaxfileupload.js插件的朋友们有帮助。

还有最后一弹,如有感兴趣的朋友,请关注:

jQuery 自制上传头像插件-附带Demo实例(ajaxfileupload.js第三弹)

补充:

《jQuery 关于IE9上传文件无法进入后台原因及解决办法(ajaxfileupload.js第四弹)》

学习JavaScript你必须掌握的8大知识点!

licqi阅读(124)

一、JavaScript思维导图之的学习

二、 JavaScript思维导图之

三、JavaScript思维导图之

四、JavaScript思维导图之

五、JavaScript思维导图之

六、 JavaScript思维导图之

七、JavaScript思维导图之

八、JavaScript思维导图之

javascript中函数的5个高级技巧

licqi阅读(127)

函数对任何一门语言来说都是一个核心的概念,在javascript中更是如此。前面曾以深入理解函数系列的形式介绍了函数的相关内容,本文将再深入一步,介绍函数的5个高级技巧

作用域安全的构造函数

  构造函数其实就是一个使用new操作符调用的函数

function Person(name,age,job){    this.name=name;    this.age=age;    this.job=job;
}var person=new Person('match',28,'Software Engineer');
console.log(person.name);//match

  如果没有使用new操作符,原本针对Person对象的三个属性被添加到window对象

function Person(name,age,job){    this.name=name;    this.age=age;    this.job=job;
}          
var person=Person('match',28,'Software Engineer');
console.log(person);//undefinedconsole.log(window.name);//match

  window的name属性是用来标识链接目标和框架的,这里对该属性的偶然覆盖可能会导致页面上的其它错误,这个问题的解决方法就是创建一个作用域安全的构造函数

function Person(name,age,job){    if(this instanceof Person){        this.name=name;        this.age=age;        this.job=job;
    }else{        return new Person(name,age,job);
    }
}var person=Person('match',28,'Software Engineer');
console.log(window.name); // ""console.log(person.name); //'match'var person= new Person('match',28,'Software Engineer');
console.log(window.name); // ""console.log(person.name); //'match'

  但是,对构造函数窃取模式的继承,会带来副作用。这是因为,下列代码中,this对象并非Polygon对象实例,所以构造函数Polygon()会创建并返回一个新的实例

function Polygon(sides){    if(this instanceof Polygon){        this.sides=sides;        this.getArea=function(){            return 0;
        }
    }else{        return new Polygon(sides);
    }
}function  Rectangle(wifth,height){
    Polygon.call(this,2);    this.width=this.width;    this.height=height;    this.getArea=function(){        return this.width * this.height;
    };
}var rect= new Rectangle(5,10);
console.log(rect.sides); //undefined

  如果要使用作用域安全的构造函数窃取模式的话,需要结合原型链继承,重写Rectangle的prototype属性,使它的实例也变成Polygon的实例

function Polygon(sides){    if(this instanceof Polygon){        this.sides=sides;        this.getArea=function(){            return 0;
        }
    }else{        return new Polygon(sides);
    }
}function  Rectangle(wifth,height){
    Polygon.call(this,2);    this.width=this.width;    this.height=height;    this.getArea=function(){        return this.width * this.height;
    };
}
Rectangle.prototype= new Polygon();var rect= new Rectangle(5,10);
console.log(rect.sides); //2

惰性载入函数

  因为各浏览器之间的行为的差异,我们经常会在函数中包含了大量的if语句,以检查浏览器特性,解决不同浏览器的兼容问题。比如,我们最常见的为dom节点添加事件的函数

function addEvent(type, element, fun) {    if (element.addEventListener) {
        element.addEventListener(type, fun, false);
    }    else if(element.attachEvent){
        element.attachEvent('on' + type, fun);
    }    else{
        element['on' + type] = fun;
    }
}

  每次调用addEvent函数的时候,它都要对浏览器所支持的能力进行检查,首先检查是否支持addEventListener方法,如果不支持,再检查是否支持attachEvent方法,如果还不支持,就用dom0级的方法添加事件。这个过程,在addEvent函数每次调用的时候都要走一遍,其实,如果浏览器支持其中的一种方法,那么他就会一直支持了,就没有必要再进行其他分支的检测了。也就是说,if语句不必每次都执行,代码可以运行的更快一些。

  解决方案就是惰性载入。所谓惰性载入,指函数执行的分支只会发生一次,有两种实现惰性载入的方式

  1、第一种是在函数被调用时,再处理函数。函数在第一次调用时,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了

  我们可以用下面的方式使用惰性载入重写addEvent()

function addEvent(type, element, fun) {    if (element.addEventListener) {
        addEvent = function (type, element, fun) {
            element.addEventListener(type, fun, false);
        }
    }    else if(element.attachEvent){
        addEvent = function (type, element, fun) {
            element.attachEvent('on' + type, fun);
        }
    }    else{
        addEvent = function (type, element, fun) {
            element['on' + type] = fun;
        }
    }    return addEvent(type, element, fun);
}

  在这个惰性载入的addEvent()中,if语句的每个分支都会为addEvent变量赋值,有效覆盖了原函数。最后一步便是调用了新赋函数。下一次调用addEvent()时,便会直接调用新赋值的函数,这样就不用再执行if语句了

  但是,这种方法有个缺点,如果函数名称有所改变,修改起来比较麻烦

  2、第二种是声明函数时就指定适当的函数。 这样在第一次调用函数时就不会损失性能了,只在代码加载时会损失一点性能

  以下就是按照这一思路重写的addEvent()。以下代码创建了一个匿名的自执行函数,通过不同的分支以确定应该使用哪个函数实现

var addEvent = (function () {    if (document.addEventListener) {        return function (type, element, fun) {
            element.addEventListener(type, fun, false);
        }
    }    else if (document.attachEvent) {        return function (type, element, fun) {
            element.attachEvent('on' + type, fun);
        }
    }    else {        return function (type, element, fun) {
            element['on' + type] = fun;
        }
    }
})();

函数绑定

  在javascript与DOM交互中经常需要使用函数绑定,定义一个函数然后将其绑定到特定DOM元素或集合的某个事件触发程序上,绑定函数经常和回调函数及事件处理程序一起使用,以便把函数作为变量传递的同时保留代码执行环境

            
    var handler={
        message:"Event handled.",
        handlerFun:function(){
            alert(this.message);
        }
    };
btn.onclick = handler.handlerFun;

  上面的代码创建了一个叫做handler的对象。handler.handlerFun()方法被分配为一个DOM按钮的事件处理程序。当按下该按钮时,就调用该函数,显示一个警告框。虽然貌似警告框应该显示Event handled,然而实际上显示的是undefiend。这个问题在于没有保存handler.handleClick()的环境,所以this对象最后是指向了DOM按钮而非handler

  可以使用闭包来修正这个问题

            var handler={
    message:"Event handled.",
    handlerFun:function(){
        alert(this.message);
    }
};
btn.onclick = function(){
    handler.handlerFun();    
}

  当然这是特定于此场景的解决方案,创建多个闭包可能会令代码难以理解和调试。更好的办法是使用函数绑定

  一个简单的绑定函数bind()接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去

function bind(fn,context){    return function(){        return fn.apply(context,arguments);
    }
}

  这个函数似乎简单,但其功能是非常强大的。在bind()中创建了一个闭包,闭包使用apply()调用传入的函数,并给apply()传递context对象和参数。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数

  function bind(fn,context){    return function(){        return fn.apply(context,arguments);
    }
}          
var handler={
    message:"Event handled.",
    handlerFun:function(){
        alert(this.message);
    }
};
btn.onclick = bind(handler.handlerFun,handler);

  ECMAScript5为所有函数定义了一个原生的bind()方法,进一步简化了操作

  只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。它们主要用于事件处理程序以及setTimeout()和setInterval()。然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用

函数柯里化

  与函数绑定紧密相关的主题是函数柯里化(function currying),它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数

function add(num1,num2){    return num1+num2;
}function curriedAdd(num2){    return add(5,num2);
}
console.log(add(2,3));//5console.log(curriedAdd(3));//8

  这段代码定义了两个函数:add()和curriedAdd()。后者本质上是在任何情况下第一个参数为5的add()版本。尽管从技术来说curriedAdd()并非柯里化的函数,但它很好地展示了其概念

  柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。下面是创建柯里化函数的通用方式

function curry(fn){    var args = Array.prototype.slice.call(arguments, 1);    return function(){        var innerArgs = Array.prototype.slice.call(arguments),
        finalArgs = args.concat(innerArgs);        return fn.apply(null, finalArgs);
    };
}

  curry()函数的主要工作就是将被返回函数的参数进行排序。curry()的第一个参数是要进行柯里化的函数,其他参数是要传入的值。为了获取第一个参数之后的所有参数,在arguments对象上调用了slice()方法,并传入参数1表示被返回的数组包含从第二个参数开始的所有参数。然后args数组包含了来自外部函数的参数。在内部函数中,创建了innerArgs数组用来存放所有传入的参数(又一次用到了slice())。有了存放来自外部函数和内部函数的参数数组后,就可以使用concat()方法将它们组合为finalArgs,然后使用apply()将结果传递给函数。注意这个函数并没有考虑到执行环境,所以调用apply()时第一个参数是null。curry()函数可以按以下方式应用

function add(num1, num2){    return num1 + num2;
}var curriedAdd = curry(add, 5);
alert(curriedAdd(3)); //8

  在这个例子中,创建了第一个参数绑定为5的add()的柯里化版本。当调用cuurriedAdd()并传入3时,3会成为add()的第二个参数,同时第一个参数依然是5,最后结果便是和8。也可以像下例这样给出所有的函数参数:

function add(num1, num2){    return num1 + num2;
}var curriedAdd2 = curry(add, 5, 12);
alert(curriedAdd2()); //17

  在这里,柯里化的add()函数两个参数都提供了,所以以后就无需再传递给它们了

  函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的bind()函数

function bind(fn, context){    var args = Array.prototype.slice.call(arguments, 2);    return function(){        var innerArgs = Array.prototype.slice.call(arguments),
        finalArgs = args.concat(innerArgs);        return fn.apply(context, finalArgs);
    };
}

   对curry()函数的主要更改在于传入的参数个数,以及它如何影响代码的结果。curry()仅仅接受一个要包裹的函数作为参数,而bind()同时接受函数和一个object对象。这表示给被绑定的函数的参数是从第三个开始而不是第二个,这就要更改slice()的第一处调用。另一处更改是在倒数第3行将object对象传给apply()。当使用bind()时,它会返回绑定到给定环境的函数,并且可能它其中某些函数参数已经被设好。要想除了event对象再额外给事件处理程序传递参数时,这非常有用

var handler = {
    message: "Event handled",
    handleClick: function(name, event){
        alert(this.message + ":" + name + ":" + event.type);
    }
};var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));

  handler.handleClick()方法接受了两个参数:要处理的元素的名字和event对象。作为第三个参数传递给bind()函数的名字,又被传递给了handler.handleClick(),而handler.handleClick()也会同时接收到event对象

  ECMAScript5的bind()方法也实现函数柯里化,只要在this的值之后再传入另一个参数即可

var handler = {
    message: "Event handled",
    handleClick: function(name, event){
        alert(this.message + ":" + name + ":" + event.type);
    }
};var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));

  javaScript中的柯里化函数和绑定函数提供了强大的动态函数创建功能。使用bind()还是curry()要根据是否需要object对象响应来决定。它们都能用于创建复杂的算法和功能,当然两者都不应滥用,因为每个函数都会带来额外的开销

函数重写

  由于一个函数可以返回另一个函数,因此可以用新的函数来覆盖旧的函数

function a(){
    console.log('a');
    a = function(){
        console.log('b');
    }
}

  这样一来,当我们第一次调用该函数时会console.log(‘a’)会被执行;全局变量a被重定义,并被赋予新的函数

  当该函数再次被调用时, console.log(‘b’)会被执行

  再复杂一点的情况如下所示

var a = (function(){
    function someSetup(){        var setup = 'done';
    }
    function actualWork(){
        console.log('work');
    }
    someSetup();    return actualWork;
})()

  我们使用了私有函数someSetup()和actualWork(),当函数a()第一次被调用时,它会调用someSetup(),并返回函数actualWork()的引用

SpringMVC环境下实现的Ajax异步请求(JSON格式数据)

licqi阅读(183)

一 环境搭建

首先是常规的spring mvc环境搭建,不用多说,需要注意的是,这里需要引入jackson相关jar包,然后在spring配置文件“springmvc-servlet.xml”中添加json解析相关配置,我这里的完整代码如下:


	
	
	
		
			
				text/html;charset=UTF-8
				application/json;charset=UTF-8
			
		
		
			
				
					
						
					
				
			
		
	
	
	
		
			
				
			
		
	
	
	
		
		
		
		
		
		
		
			
				atom=application/atom+xml
				html=text/html
				json=application/json
				xml=application/xml
				*=*/*
			
		
	
	
	
	

	
	
	
		
		
		
		
		
	

项目结构:

注:我这里测试使用的完整jar包:http://pan.baidu.com/s/1dEUwdmL

仅供参考

二 测试实例

(1)在WEB-INF/jsp目录下新建了一个index.jsp文件,包含了简单的jQuery的ajax请求,请求数据的格式是JSON,具体代码如下:






<base href="">






jQuery i18n

	$().ready(
			function() {
				$("#sub").click(
						function() {
							var name = $("#username").val();	
							var age = 18;
							var user = {"username":name,"age":age};
							$.ajax({
										url : 'hello.json',
										type : 'POST',
										data : JSON.stringify(user), // Request body 
										contentType : 'application/json; charset=utf-8',
										dataType : 'json',
										success : function(response) {
											//请求成功
											alert("你好" + response.username + "[" + response.age + "],当前时间是:" + response.time + ",欢迎访问:http://www.zifangsky.cn");
											
										},
										error : function(msg) {
											alert(msg);
										}
									});
						});
			});



	
	
	

(2)一个简单的model类User,代码如下:

package cn.zifangsky.controller;

public class User {
	private String username;
	private int age;
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
}

(3)controller类TestController.java:

package cn.zifangsky.controller;

import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

@Controller
@Scope("prototype")
public class TestController {

	/**
	 * 转到页面
	 */
	@RequestMapping(value = "/hello.html")
	public ModelAndView list() {
		ModelAndView view = new ModelAndView("index");
		return view;
	}

	/**
	 * ajax异步请求, 请求格式是json
	 */
	@RequestMapping(value = "/hello.json", method = { RequestMethod.POST })
	@ResponseBody
	public Map hello(@RequestBody User user) {
		// 返回数据的Map集合
		Map result = new HashMap();

		Format format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

		// 返回请求的username
		result.put("username", user.getUsername());
		// 返回年龄
		result.put("age", String.valueOf(user.getAge()));
		// 返回当前时间
		result.put("time", format.format(new Date()));

		return result;
	}
}

关于具体的执行步骤我简单说一下:

i)项目启动后,在浏览器中访问:http://localhost:8089/SpringDemo/hello.html,然后会转到执行controller中的list方法,接着会转到/WEB-INF/jsp/index.jsp(PS:在controller中返回的是逻辑视图,跟在springmvc-servlet.xml文件中定义的路径前缀和后缀进行拼接后合成文件的真正路径)

ii)在index.jsp页面输入文字然后点击按钮,将会触发ajax请求,这个请求会获取输入框中的数据和默认的“age”参数拼接成json格式字符串最后提交到“hello.json”这个请求,也就是执行controller中的hello方法

iii)hello方法执行完毕后会返回一系列数据最后在页面中显示出来

(4)效果如下:

Java实现Web页面前数字字母验证码实现

licqi阅读(117)

最近公司做项目开发中用到了验证码实现功能,将实现代码分享出来,

前段页面实现代码:

为了表达清晰,样式部分代码去掉了,大家根据自己的需求,自己添加样式。

页面JS代码:触发变动验证码改变的JS

   
   //请求获取验证码
   function onclickValidateCode(obj){
	   $(obj).attr("src","${baseURL }/validateCode?"+new Date().getTime());
   }

后台 Controller处理:

package com.njcc.pay.controller.login;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.alibaba.dubbo.common.utils.StringUtils;

/**
 * 验证马  Controller
 * 
 * @author Administrator
 *
 */
@Controller
public class ValidateCodeController {
	
	@SuppressWarnings("unused")
	private static final Log LOG = LogFactory.getLog(ValidateCodeController.class);
	
    public static final String VALIDATE_CODE = "validateCode";
	private int w = 70;
	private int h = 23;
	
	/**
	 * @throws Exception 
	 * 函数功能说明 : 进入后台登陆页面.
	 * 
	 * @参数: @return
	 * @return String
	 * @throws
	 */
	@RequestMapping(value = "/validateCode")
	public void validateCode(HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		createImage(request,response);
	}
	
	
	private void createImage(HttpServletRequest request,HttpServletResponse response) throws IOException {
		response.setHeader("Pragma", "no-cache");
		response.setHeader("Cache-Control", "no-cache");
		response.setDateHeader("Expires", 0);
		response.setContentType("image/jpeg");

		String width = request.getParameter("width");
		String height = request.getParameter("height");
		if (StringUtils.isNumeric(width) && StringUtils.isNumeric(height)) {
			w = NumberUtils.toInt(width);
			h = NumberUtils.toInt(height);
		}
		
		BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		Graphics g = image.getGraphics();

		/*
		 * 生成背景
		 */
		createBackground(g);

		/*
		 * 生成字符
		 */
		String s = createCharacter(g);
		request.getSession().setAttribute(VALIDATE_CODE, s);

		g.dispose();
		OutputStream out = response.getOutputStream();
		ImageIO.write(image, "JPEG", out);
		out.close();

	}
	/**
	 * 生成颜色
	 * @param fc
	 * @param bc
	 * @return
	 */
	private Color getRandColor(int fc,int bc) { 
		int f = fc;
		int b = bc;
		Random random=new Random();
        if(f>255) {
        	f=255; 
        }
        if(b>255) {
        	b=255; 
        }
        return new Color(f+random.nextInt(b-f),f+random.nextInt(b-f),f+random.nextInt(b-f)); 
	}
	
	/**
	 * 生成背景
	 * @param g
	 */
	private void createBackground(Graphics g) {
		// 填充背景
		g.setColor(getRandColor(220,250)); 
		g.fillRect(0, 0, w, h);
		// 加入干扰线条
		for (int i = 0; i < 8; i++) {
			g.setColor(getRandColor(40,150));
			Random random = new Random();
			int x = random.nextInt(w);
			int y = random.nextInt(h);
			int x1 = random.nextInt(w);
			int y1 = random.nextInt(h);
			g.drawLine(x, y, x1, y1);
		}
	}

	/**
	 * 生成字符
	 * @param g
	 * @return
	 */
	private String createCharacter(Graphics g) {
		char[] codeSeq = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
				'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
				'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9' };
		String[] fontTypes = {"Arial","Arial Black","AvantGarde Bk BT","Calibri"}; 
		Random random = new Random();
		StringBuilder s = new StringBuilder();
		for (int i = 0; i < 4; i++) {
			String r = String.valueOf(codeSeq[random.nextInt(codeSeq.length)]);//random.nextInt(10));
			g.setColor(new Color(50 + random.nextInt(100), 50 + random.nextInt(100), 50 + random.nextInt(100)));
			g.setFont(new Font(fontTypes[random.nextInt(fontTypes.length)],Font.BOLD,26)); 
			g.drawString(r, 15 * i + 5, 19 + random.nextInt(8));
//			g.drawString(r, i*w/4, h-5);
			s.append(r);
		}
		return s.toString();
	}
	
	
	
	
}

WebApp(JqueryMobile) 实战(一)

licqi阅读(155)

其实WebApp这个版块已经放置一段时间了,但是一直没有写文章,那是因为我还在学习中。今天的话,我们就来写一篇关于布局的,以前在没有BootStrap,Jquery EasyUI,Jquery Mobile之前不论是做web项目还是web 移动站点都是那么费劲,尤其是我这种学不会css的人,实在是搞不了前台。OK,废话不多说,我们来看一下今天要仿制的页面,手机头条网。

看到了吧,这个界面如果用BootStrap的话可能更简单一些,因为它是单纯的三列或者两列布局,利用Bootstrap的网格系统可以很好的布局出来。那今天的话还是用Jquery Mobile css。

首先看一下最顶端的头部,我的代码如下

@section Header
{
    

今日头条

  • 推荐
  • 热点
  • 社会
  • 娱乐
  • 科技
}

其实这里我们使用Jquery Mobile css中的一些样式及属性。我们看一下data-role=”header”

在这里我们设置头部不会在点击屏幕的时候缩回去data-tap-toggle=”false”,这样当页面内容随着滚动条上移时,会被头部盖住。data-theme=”f”这个是我自己定义的主题,主题可以是a-z。而Jquery Mobile css默认只提供了五种主题。

所以f需要自己定义,我们来看一下

.ui-bar-f {
    background-color: red;
    color: white;
    font-weight: bold;
    height:45px;
    font-family:微软雅黑;
}

.img-corner {
    border-radius: 50%;
}

.a-header {
    text-decoration:none;
    color:white;
}

body {
    font-family:微软雅黑;
    background-color:white;
}

.img-shrink {
    height:150px;
    width:100%;
    border:1px solid white;
}

.news-footer {
    font-size:10px;
}

第一个css就是我们自己定义的主题,必须是以ui-bar-[a-z]来命名。头部的话在这里共分了三列,因为我们的第一个div是

这个css就把屏幕分成了相等的三份。

然后我们在每一份放我们的元素。

效果如上,那么这个圆圈图片是怎么实现的呢,很简单,css3很好实现,将下面的css应用到图片即可。

.img-corner {
    border-radius: 50%;
}

OK,头部还有一个Nav bar,这个其实将div的data-role设置为navbar,然后在div中嵌套

头部的这个导航条如果被点击的话,它的颜色会变,这个功能是Jquery Mobile实现的,我们不需要做什么。

头部说完了我们看内容页,为了简单,有一些css我直接写在元素上。

什么人会买二手车?这四类人才是最精明的消费者


《芈月转》孙俪近照曝光网友:太美了!

初二女生收到情书,超牛老妈只说了五句话!

那些发生在汽车历史上的第一次!

其实我们观察一下,就会发现,第一行是标题,第二行是三个图片,第三行是一些其他的信息。

那么第一部分我们布局就可以采用

完成这三部分。第一行没什么说的,第二行我们使用网格系统,将屏幕分成三列,每列一张图,最后一行需要注意这个”刚刚+”是在右边,需要使用float=”right”,看一下效果

怎么样,还行吧。OK,我们接着看下面的布局,下面的布局我在使用Jquery mobile的网格系统的时候没有查到像BootStrap那样的跨列功能。所以我暂时先用50%/50%的网格。

第一个网格我们放置文字,第二个网格我们放置图片。

需要注意的是上面的两个height,我们设置总高度为150,设置文字的高度为135,那么小文字的高度为15,此时我们设置小文字所在的层的float:left,因为高度不够,它就会自动沉底。

如上图,小字都沉底了。OK,其实真个头条网都采用这样的布局方式,其实我们只需要在数据库表创建好类型(是一行文字+一行3图片+小字或者是左边文字(打字加小字)+右边图片),标题,小字内容,图片我们就可以动态生成或者加载页面,这个是我下节要实现的内容。

最后我们还给这个页面加了foot。

@section Footer
{
    

}

头条网,一个神奇的网站。这个脚我们设置点击屏幕时该脚会收缩。好了,我们看一下全部页面的效果

收缩掉脚,我们再看看

OK,页面今天就到这里,最后告诉大家怎么创建移动项目的

点击确定

选择移动应用程序,确定,项目就创建好了。

最后,大家如果需要源码的话去下载,下载请点击这里源码

通过 ES6 Promise 和 jQuery Deferred 的异同学习 Promise

licqi阅读(114)

Deferred 和 Promise

ES6 和 jQuery 都有 Deffered 和 Promise,但是略有不同。不过它们的作用可以简单的用两句话来描述

  • Deffered 触发 resolve 或 reject
  • Promise 中申明 resolve 或 reject 后应该做什么(回调)

在 jQuery 中

var deferred = $.Deferred();
var promise = deferred.promise();

在 ES6 中

var deferred = Promise.defer();
var promise= defered.promise;

MDN 宣布 Deferred 在 Gecko 30 中被申明为过期,不应该再使用,而应该用 new Promise() 来代替。关于 new Promise() 将在后面说明。

jQuery 的 Deferred/Promise

jQuery 中最常用的 Promise 对象是 $.ajax() 返回的,最常用的方法不是 then,而是 donefailalways。除了 $.ajax() 外,jQuery 也提供了 $.get()$.post()$.getJSON() 等简化 Ajax 调用,它们返回的和 $.ajax() 的返回值一样,是个 Promise 对象。

实际上 $.ajax() 返回的是一个 jqXHR 对象。但 jqXHR 实现了 jQuery 的 Promise 接口,所以也是一个 Promise 对象。

done()fail()always()

done() 添加 deferred.resolve() 的回调,fail() 添加 deferred.reject() 的回调。所以在 Ajax 调用成功的情况下执行 done() 添加的回调,调用失败时执行 fail() 添加的回调。但不管成功与否,都会执行 always() 添加的回调。

这里 done()fail()always() 都是以类似事件的方式添加回调,也就意味着,不管执行多次次 done()fail()always(),它们添加的若干回调都会在符合的条件下依次执行。

一般情况下会这样执行 Ajax

// 禁用按钮以避免重复提交
$("#theButton").prop({
    disabled: true
});

// 调用 Ajax 提交数据,假设返回的是 JSON 数据
var jqxhr = $.ajax("do/example", {
    type: "post",
    dataType: "json",
    data: getFormData()
});

jqxhr.done(function(jsonObject) {
    // Ajax 调用成功
    console.log("success with data", jsonObject);
}).fail(function() {
    // Ajax 调用失败
    console.log("failed")
}).always(function() {
    // 不管成功与否,都会执行,取消按钮的禁用状态
    $("#theButton").prop({
        disabled: false
    });
});

上面是最普通最常用的用法,但是在一个项目中总是这么写 Ajax,有点累,稍微约定一下再封装一下就使用起来就会便捷得多。首先,假设我们定义返回的 JSON 是这样的格式:

{
    "code": "int, 0 表示成功,其它值表示出错",
    "message": "string, 附加的消息,可选",
    "data": "object,附加的数据,可选
}

然后为项目公共类 app 定义一个 ajax 方法

app.ajax = function(button, url, data) {
    if (button) {
        button.prop("disabled", true);
    }

    return $.ajax(url, {
        type: "post",
        dataType: "json",
        data: data
    }).done(function(json) [
        if (json.code !== 0) {
            showError(json.message || "操作发生错误");
        }
    }).fail(function() {
        showError("服务器错误,请稍后再试");
    }).always(function() {
        if (button) {
            button.prop("disabled", false);
        }
    });
};

// 调用
app.ajax("do/example", getFormData()).done(function(json) {
    if (json.code === 0) {
        // 只需要处理正确的情况啦
    }
});

不过还是有点不爽,如果不需要判断 json.code === 0 就更好了。这个……可以自己用一个 Deferred 来处理:

app.ajax = function(button, url, data) {
    if (button) {
        button.prop("disabled", true);
    }

    var deferred = $.Deferred();
    $.ajax(url, {
        type: "post",
        dataType: "json",
        data: data
    }).done(function(json) [
        if (json.code !== 0) {
            showError(json.message || "操作发生错误");
            deferred.reject();
        } else {
            deferred.resolve(json);
        }
    }).fail(function() {
        showError("服务器错误,请稍后再试");
        deferred.reject();
    }).always(function() {
        if (button) {
            button.prop("disabled", false);
        }
    });
    return deferred.promise();
};

// 调用
app.ajax("do/example", getFormData()).done(function(json) {
    // json.code === 0 总是成立
    // 正常处理 json.data 就好
});

注意,这里已经不是直接返回 $.ajax() 的结果 jqXHR 对象了,返回的是新建 Deferred 对象的 promise 对象。

复习了 Ajax,现在需要切入正题,找到 jQuery Promise 和 ES6 Promise 接近的地方——then()

jQuery deferred.then()

在 jQuery 1.8 以前(不含 1.8,比如 jQuery 1.7.2),deferred.then() 就是一个把 done()fail() 放在一起的语法糖。jQuery 在 1.8 版本的时候修改了 deferred.then() 的行为,使 then() 的行为与 Promise 的 then() 相似。从 jQuery 的文档可以看到 1.8 版本的变化——干掉了 callback,换成了 filter:

// version added: 1.5, removed: 1.8
deferred.then( doneCallbacks, failCallbacks )

// version added: 1.7, removed: 1.8
deferred.then( doneCallbacks, failCallbacks [, progressCallbacks ] )

// version added: 1.8
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )

可以简单的把 callback 当作一个事件处理,值用于 callback 之后一般不会改变;而 filter 不同,一个值传入 filter 再从 filter 返回出来,可能已经变了。还是举个例子来说明

var deferred = $.Deferred();
var promise = deferred.promise();
promise.then(function(v) {
    console.log(`then with ${v}`);
}).done(function(v) {
    console.log(`done with ${v}`);
});
deferred.resolve("resolveData");

在 jQuery 1.7.2 中的结果

then with resolveData
done with resolveData

在 jQuery 1.8.0 中的结果

then with resolveData
done with undefined

从上面来看,jQuery 的 deferred.then() 语义和 ES6 Promise.then() 语义基本一致。如果把上面的 app.ajax 换成 then() 实现会有助于对 ES6 Promise 的理解。

app.ajax = function(button, url, data) {
    if (button) {
        button.prop("disabled", true);
    }

    return $.ajax(url, {
        type: "post",
        dataType: "json",
        data: data
    }).then(function(json) {
        if (json.code !== 0) {
            showError(json.message || "操作发生错误");
            return $.Deferred().reject().promise();
        } else {
            return $.Deferred().resolve(json).promise();
        }
    }, function() {
        showError("服务器错误,请稍后再试");
        deferred.reject();
    }).always(function() {
        if (button) {
            button.prop("disabled", false);
        }
    });
};

// 调用方式没变,用 done,也可以用 then
app.ajax("do/example", getFormData()).done(function(json) {
    // json.code === 0 总是成立
    // 正常处理 json.data 就好
});

从 jQuery Promise 到 ES6 Promise

上面的代码太长,提炼一下关键部分(示意,不能运行)

var promise = $.ajax();
promise.then(function(data) {
    // resolve
    return data.code
        ? new Promise().reject()
        : new Promise().resolve(data);

    // 如果没有错,就返回一个新的 promise,并使用 data 来 resolve,
    // 也可以直接返回 data,
    // 这样后面 then 的 resolve 部分才能收到数据
}, function() {
    // rejected
});

// 调用阶段
promise.then(function(data) {
    // 处理 data
});

也许你没注意到,其实上面的代码基本上就是 ES6 的 Promise 了。下面正式用 ES6 Promise 改写上面的示意代码

var promise = new Promise(function(resolve, reject) {
    $.ajax().then(resolve, reject);
    // 上面这句没看懂?那换成这样你一定会懂
    // $.ajax().then(function(data) {
    //     resolve(data);
    // }, function() {
    //     reject();
    // });
}).then(function(data) {
    return data.code
        ? Promise.reject()
        : Promise.resolve(data);

    // 这里 Promise.resolve(data) 同样可以直接替换为 data
});

// 调用没变
promise.then(function(data) {
    // 处理 data
});

怎么样,差别不大吧。不知不觉就会 ES6 Promise 了!

ES6 的 Promise

上面已经把 ES6 的 Promise 带出来了,现在只需要把常用方法列出来作为参考即可

注意,小写的 promise 表示 Promise 对象

  • new Promise(executor),产生一个新的 Promise 对象

    executor(resolve, reject)
    executorresolvereject 均为函数,在 executor 中,正确处理调用 resolve() 返回数据,异常处理直接 throw new Error(...) 或调 reject() 返回数据。

  • Promise.resolve(data),产生 Promise 对象并 resolve

  • Promise.reject(),产生 Promise 对象并 reject

  • promise.then(onResolve, onReject),然后……继续处理

  • promise.catch(onReject)project.then(null, onReject) 的语法糖,和 jQuery 的 promise.fail() 差不多(但不同)。

参考

  • ECMAScript 2015 Language Specification – ECMA-262 6th Edition
  • Deferred – Mozilla | MDN
  • Promise – Mozilla | MDN
  • Deferred Object | jQuery Documentation

JavaScript中错误正确处理方式,你用对了吗?

licqi阅读(113)

JavaScript的事件驱动范式增添了丰富的语言,也是让使用JavaScript编程变得更加多样化。如果将浏览器设想为JavaScript的事件驱动工具,那么当错误发生时,某个事件就会被抛出。理论上可以认为这些发生的错误只是JavaScript中的简单事件。

本文将会讨论客户端JavaScript中的错误处理。主要介绍JavaScript中的易犯错误、错误处理、异步代码编写等内容。

下面就让我们一起看看如何正确处理JavaScript中的错误。

Demo演示

本文中使用的demo可以在GitHub上找到,运行之后会是这样的页面:

每个按钮都会引发一个“错误(Exception)”,同时这个错误会模拟出一个被抛出的异常TypeError。下面是模块的定义:

// scripts/error.jsfunction error() {  var foo = {};  return foo.bar();
}

首先,这个函数声明了一个空对象foo。需要注意的是,bar( )未在任何地方定义。接下来验证这个单元测试是否会引发“错误”:

// tests/scripts/errorTest.jsit('throws a TypeError', function () {
  should.throws(error, TypeError);
});

这个单元测试在Mocha中,同时在 Should.js中有测试声明。Mocha是测试运行工具,而Should.js是断言库。这个单元测试运行在Node上,不需要使用浏览器。

error( )定义一个空对象,然后尝试访问一个方法。因为bar( )在对象内不存在,所以就会引发异常。这种发生在像JavaScript这样的动态语言上的错误,每个人可能都会遇到!

错误处理(一)

通过以下代码,对上述错误进行处理:

// scripts/badHandler.jsfunction badHandler(fn) {  try {    return fn();
  } catch (e) { }  return null;
}

该处理程序将 fn 作为输入参数,然后 fn 在处理函数内部会被调用。单元测试会体现出以上错误处理程序的作用:

// tests/scripts/badHandlerTest.jsit('returns a value without errors', function() {  var fn = function() {    return 1;
  };  var result = badHandler(fn);
  result.should.equal(1);
});

it('returns a null with errors', function() {  var fn = function() {    throw new Error('random error');
  };  var result = badHandler(fn);
  should(result).equal(null);
});

如果出现问题,错误处理程序就会返回 null。fn( ) 回调函数可以指向一个合法的方法或错误。

以下的点击事件会继续进行事件处理:

// scripts/badHandlerDom.js(function (handler, bomb) {  var badButton = document.getElementById('bad');  if (badButton) {
    badButton.addEventListener('click', function () {
      handler(bomb);
      console.log('Imagine, getting promoted for hiding mistakes');
    });
  }
}(badHandler, error));

这种处理方式在代码中隐藏了一个错误,并且很难发现。隐藏的错误可能会花费好几个小时的调试时间。尤其是在具有深度调用堆栈的多层解决方案中,这个错误会更难发现。所以这是一种很差的错误处理方式。

错误处理(二)

下面是另一个错误处理方式。

// scripts/uglyHandler.jsfunction uglyHandler(fn) {  try {    return fn();
  } catch (e) {    throw new Error('a new error');
  }
}

处理异常的方式如下所示:

// tests/scripts/uglyHandlerTest.jsit('returns a new error with errors', function () {  var fn = function () {    throw new TypeError('type error');
  };
  should.throws(function () {
    uglyHandler(fn);
  }, Error);
});


以上对错误的处理程序有明显的改进。在这里异常会调用堆栈进行冒泡。同时错误会展开堆栈,这对调试非常有帮助。除了抛出异常,解释器还会沿着栈寻找另外的处理。这也带来了可以从堆栈顶部处理错误的可能。但这还是一种较差的错误处理,需要我们从堆栈中一步步追溯原始的异常。


可以采用一种替代方案,用自定义的错误方式来结束这种较差的错误处理。当你向错误中添加更多详细信息时,会让这种方法变得很有帮助。


例如:

// scripts/specifiedError.js// Create a custom errorvar SpecifiedError = function SpecifiedError(message) {  this.name = 'SpecifiedError';  this.message = message || '';  this.stack = (new Error()).stack;
};
SpecifiedError.prototype = new Error();
SpecifiedError.prototype.constructor = SpecifiedError;
// scripts/uglyHandlerImproved.jsfunction uglyHandlerImproved(fn) {  try {    return fn();
  } catch (e) {    throw new SpecifiedError(e.message);
  }
}
// tests/scripts/uglyHandlerImprovedTest.jsit('returns a specified error with errors', function () {  var fn = function () {    throw new TypeError('type error');
  };
  should.throws(function () {
    uglyHandlerImproved(fn);
  }, SpecifiedError);
});

指定的错误会添加更多详细信息并保留原始的错误消息。有了这个改进,以上的处理不再是较差的处理方式了,而是一个清晰有用的方式。


经过了上面的处理,我们还收到了一个未处理的异常。接下来让我们看看浏览器在处理错误时,有什么帮助。

展开堆栈

处理异常的一种方式是在调用堆栈的顶部加入 try…catch。

比如说:

function main(bomb) {  try {
    bomb();
  } catch (e) {    // Handle all the error things  }
}

但是,浏览器是事件驱动的, JavaScript 中的异常也是一个事件。发生异常时,解释器会暂停执行并展开:

// scripts/errorHandlerDom.jswindow.addEventListener('error', function (e) {  var error = e.error;
  console.log(error);
});

此事件处理程序会捕获任何执行上下文中发生的错误。各个目标发生的错误事件会触发各种类型的错误。这种集中在代码中的错误处理是非常激进的。你可以使用菊花链处理方式来处理特定的错误。如果你遵循 SOLID 原则,就可以采用具有单一目的错误处理方式。这些处理程序可以随时进行注册,解释器会循环执行需要执行的处理程序。代码库可以从 try…catch 块中释放出来,这也使得调试变得容易。在 JavaScript 中,把错误处理当作事件处理很重要。

捕获堆栈

在解决问题时,调用堆栈会非常有用,同时浏览器正好可以提供这些信息。虽然堆栈属性不是标准的一部分,但是最新的浏览器已经可以查看这些信息了。

下面是在服务器上记录错误的示例:

// scripts/errorAjaxHandlerDom.jswindow.addEventListener('error', function (e) {  var stack = e.error.stack;  var message = e.error.toString();  if (stack) {
    message += 'n' + stack;
  }  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/log', true);  // Fire an Ajax request with error details  xhr.send(message);
});

每个错误处理都具有单个目的,这样可以保持代码的DRY原则(目的单一,不要重复自己原则)。

在浏览器中,需要将事件处理添加到DOM。这意味着如果你正在构建第三方库,那么你的事件会与客户端代码共存。window.addEventListener( )会帮你进行处理,同时也不会抹去现有的事件。

这是服务器上日志的截图:

可以通过命令提示符查看日志,但是Windows上,日志是非动态的。

通过日志可以清楚的看到,具体什么情况触发了什么错误。在调试时调用堆栈也会非常有用,所以不要低估调用堆栈的作用。

在JavaScript中,错误信息仅适用于单个域。因为在使用来自不用域的脚本时,将会看不到任何错误详细信息。

一种解决方案是重新抛出错误,同时保留错误消息:

try {  return fn();
} catch (e) {  throw new Error(e.message);
}


一旦重新启动了错误备份,全局错误处理程序就会完成其余的工作。确保你的错误处理处在相同域中,这样会保留原始消息,堆栈和自定义错误对象。

异步处理

JavaScript在运行异步代码时,进行下面的异常处理,会产生一个问题:

// scripts/asyncHandler.jsfunction asyncHandler(fn) {  try {    // This rips the potential bomb from the current context
    setTimeout(function () {
      fn();
    }, 1);
  } catch (e) { }
}

通过单元测试来查看问题:

// tests/scripts/asyncHandlerTest.jsit('does not catch exceptions with errors', function () {  // The bomb
  var fn = function () {    throw new TypeError('type error');
  };  // Check that the exception is not caught
  should.doesNotThrow(function () {
    asyncHandler(fn);
  });
});

这个异常没有被捕获,我们通过单元测试来验证。尽管代码包含了try…catch,但是try…catch语句只能在单个执行上下文中工作。当异常被抛出时,解释器已经脱离了try…catch,所以异常未被处理。Ajax调用也会发生同样的情况。

所以,一种解决方案是在异步回调中捕获异常:

setTimeout(function () {  try {
    fn();
  } catch (e) {    // Handle this async error  }
}, 1);

这种做法会比较奏效,但仍有很大的改进空间。


首先,这些try…catch block在整个区域纠缠不清。事实上,V8浏览器引擎不鼓励在函数内使用try … catch block。V8是Chrome浏览器和Node中使用的JavaScript引擎。一种做法是将try…catch block移动到调用堆栈的顶部,但这却不适用于异步代码编程。


由于全局错误处理可以在任何上下文中执行,所以如果为错误处理添加一个窗口对象,那么就能保证代码的DRY和SOLID原则。同时全局错误处理也能保证你的异步代码很干净。


以下是该异常处理在服务器上的报告内容。请注意,输出内容会根据浏览器的不同而不同。

从错误处理中可以看到,错误来自于异步代码的setTimeout( )功能。

结论

在进行错误处理时,不要隐藏问题,而应该及时发现问题,并采用各种方法追溯问题的根源以便解决问题。虽然编写代码时,时常难免会埋下错误,但是我们也无须为错误的发生过于感到羞愧,及时解决发现问题从而避免更大的问题发生,正是我们现在需要做的。

JavaScript 开发工具介绍

SpreadJS 纯前端表格控件是基于 HTML5 的 JavaScript 电子表格和网格功能控件,提供了完备的公式引擎、排序、过滤、输入控件、数据可视化、Excel 导入/导出等功能,适用于 .NET、Java 和移动端等各平台在线编辑类 Excel 功能的表格程序开发。

网站前端_jQuery-基础入门.玩转jQuery基本/层次/过滤/表单选择器?

licqi阅读(153)

对比选择:


1. CSS依靠CSS选择器使得网页的结构和表现样式完全分离,CSS选择器能轻松定位并修改指定元素样式,CSS选择器包括标签选择器(以文档元素作为选择器),ID选择器(以文档元素的唯一标识ID作为选择器),类选择器(以文档元素的class作为选择器),群组选择器(多个选择器应用同样的样式规则),后代选择器(元素X的任意后代元素Y),通配选择器(以文档的所有元素作为选择器),伪类选择器(以元素特定行为作为选择器),子选择器(元素X的直接子属元素Y),兄弟选择器(元素X的直属兄弟元素Y),属性选择器(以文档元素特定属性为选择器),详情参考(http://www.w3school.com.cn/css/css_selector_type.asp)

2. jQuery选择器完全继承了CSS选择器的风格,CSS选择器能轻松定位并修改指定元素属性和行为,而无需担心浏览器是否支持这一选择器,jQuery的行为规则都必须在获取到元素后才能生效


说明: jQuery不仅可以像原生CSS样修改元素样式,而且还可以修改元素行为,需要注意的是jQuery中涉及操作CSS样式的部分比单纯的CSS功能更为强大,并且拥有跨浏览器的兼容性.

错误处理:


说明: 原生JS如果返回的对象不存在则浏览器会报错,如果加if判断会增加代码量,而jQuery中$(‘selector’)返回的永远是封装后的jQuery对象,因此不能直接if判断,也不需要if判断,如果非要去判断的话可通过判断$(‘selector’).length属性是否大于0或$(‘selector’)[0]转换为DOM对象(其实是调用的$(‘selector’).toArray()转换为DOM对象数组然后再取指定DOM对象的)判断对象是否存在



    
        
        前端开发
    
    
    
    
    
        // 方式一: 通过判断其length属性
        if ($("body").length > 0){
            alert("存在")
        } 
        // 方式二: 转换为DOM对象判断是否存在
        if ($('body')[0]){
            alert("存在")
        }
    

JQ选择器:



    
        
        前端开发
        
            div, span, p {
                width: 200px;
                height: 200px;
                margin: 5px;
                background: #aaa;
                border: #000 1px solid;
                float: left;
                font-size: 17px;
            }
            div.mini {
                width: 66px;
                height: 66px;
                background-color: #aaa;
                font-size: 12px;
            }
            div.hide {
                display: none;
            }
        
    
    
        

just for test

id为one,class为one的div
class为mini的div
id为two,class为one,title为test的div
class为mini,title为other的div
class为mini,title为test的div
class为one的div
class为mini的div
class为mini的div
class为mini的div
class为one的div
class为mini的div
class为mini的div
class为mini的div
class为mini,title为test的div
style的display为"none"的div
class为"hide"的div
包含input的type为"hidden"的div
正在执行动画的span

# 基本选择器

说明: 基本选择器包括元素选择器/ID选择器/类选择器/通用选择器/分组选择器.



    // 改变id为one的元素背景颜色为#bbffaa
    $("#one").css("background-color", "#bbffaa")
    // 改变class为mini的元素的背景颜色为#bbffaa
    $(".mini").css("background-color", "#bbffaa")
    // 改变所有div元素的背景颜色为#bbffaa
    $("div").css("background-color", "#bbffaa")
    // 改变所有元素的背景颜色为#bbffaa
    $("*").css("background-color", "#bbffaa")
    // 改变所有span和id为two的元素的背景颜色为#bbffaa
    $("span, #two").css("background-color", "#bbffaa")

# 层次选择器

说明: 层次选择器包括子选择器/后代选择器/兄弟选择器/后续选择器.


    // 改变body里面所有div元素的背景颜色为#bbffaa
    $("body div").css("background-color", "#bbffaa")
    // 改变body内第一层同级div元素的背景颜色为#bbffaa
    $("body>div").css("background-color", "#bbffaa")
    // 改变class为one的后面第一个同级div的背景颜色为#bbffaa
    $(".one + div").css("background-color", "#bbffaa")
    // 改变id为two后面所有div元素的背景颜色为#bbffaa
    $("#two ~ div").css("background-color", "#bbffaa")

扩展: 在层次选择器中子选择器和后代选择器比较常用,而兄弟选择器和后续选择器在jQuery中可以用更简单的方法代替,兄弟选择器可通过.next(“selector”)代替,而后续选择器可通过.nextAll(“selector”)代替,需要注意的是.next(“selector”)和.nextAll(“selector”)都是基于同级的,还有一个基于同级但是不论前后的兄弟元素获取方法是.siblings(“selector”)

# 过滤选择器

说明: 过滤选择器包括基本过滤选择器/内容过滤选择器/可见性过滤选择器/属性过滤选择器/子元素过滤选择器/表单属性过滤选择器.


    // 基本过滤选择器
    // 改变第一个div元素的背景色为#bbffaa
    $("div:first").css("background-color", "#bbffaa")
    // 改变最后一个div元素的背景色为#bbffaa
    $("div:last").css("background-color", "#bbffaa")
    // 改变class不为one的元素的背景色为#bbffaa
    $("div:not(.one)").css("background-color", "#bbffaa")
    // 改变索引为偶数的元素的背景颜色为#bbffaa
    $("div:even").css("background-color", "#bbffaa")
    // 改变索引为奇数的元素的背景颜色为#bbffaa
    $("div:odd").css("background-color", "#bbffaa")
    // 改变索引等于某值元素的背景颜色为#bbffaa
    $("div:eq(0)").css("background-color", "#bbffaa")
    // 改变索引大于某值的元素的背景颜色为#bbffaa
    $("div:gt(0)").css("background-color", "#bbffaa")
    // 改变索引小于某值的元素的背景颜色为#bbffaa
    $("div:lt(1)").css("background-color", "#bbffaa")
    // 改变所有的标题元素(h1~h6)的背景颜色为#bbffaa
    $(":header").css("background-color", "#bbffaa")
    // 改变所有正在执行动画的元素的背景颜色为#bbffaa
    $(":animated").css("background-color", "#bbffaa")
    
    // 内容过滤选择器
    // 设置包含文本内容为某值的元素背景色为#bbffaa
    $("div:contains(为)").css("background-color", "#bbffaa")
    // 设置不包含任何子元素或文本内容的元素背景色为#bbffaa
    $("div:empty").css("background-color", "#bbffaa")
    // 设置包含指定选择器的元素的元素背景色为#bbffaa
    $("div:has(div)").css("background-color", "#bbffaa")
    // 设置包含有子元素或文本的元素的背景色为#bbffaa(和empty相反)
    $("div:parent").css("background-color", "#bbffaa")
    
    // 可见过滤选择器
    // 设置所有不可见元素显示
    $("div:hidden").show(3000)
    // 设置所有可见div元素背景色设置为#bbffaa
    $("div:visible").css("background-color", "#bbffaa")
    
    // 属性过滤选择器
    // 设置含有title属性元素的背景色为#bbffaa
    $("[title]").css("background-color", "#bbffaa")
    // 设置title属性值等于test的元素背景色为#bbffaa
    // $("[title=test]").css("background-color", "#bbffaa")
    // 设置title属性值不等于test的元素背景色为#bbffaa
    $("[title!=test]").css("background-color", "#bbffaa")
    // 设置title属性值以te开头的元素背景色为#bbffaa
    $("[title^=te]").css("background-color", "#bbffaa")
    // 设置title属性值以st结尾的元素背景色为#bbffaa
    $("[title$=st]").css("background-color", "#bbffaa")
    // 设置title属性值中含有es的元素背景色为#bbffaa
    $("[title*=es]").css("background-color", "#bbffaa")
    // 设置包含id属性且title属性值为test的元素的背景色为"#bbffaa"
    $("[id][title=test]").css("background-color", "#bbffaaa")
    
    // 子元素过滤选择器
    // 设置class为one的div下的第一个子元素背景色为#bbffaa
    $("div.one :first-child").css("background-color", "#bbffaa")
    // 设置class为one的div下的最后一个子元素背景色为#bbffaa
    $("div.one :last-child").css("background-color", "#bbffaa")
    // 设置class为one的div下只有一个子元素的元素背景色为#bbffaa
    $("div.one :only-child").css("background-color", "#bbffaa")
    // 设置每个元素下的第index个子元素或偶数或奇数元素的背景色为#bbffaa
    $("div.one :nth-child(odd)").css("background-color", "#bbffaa")

说明: 在可见性过滤选择器中,:hidden不仅包括样式属性display:none的元素,也包括文本隐藏域和visible:hidden之类的元素,在子元素过滤选择器中,:nth-child将为每一个符合条件的父元素匹配子元素,但需要注意的是索引是从1开始,而:eq的索引从0开始,同理:first和:first-child,:last和:last-child类似

# 表单选择器

说明: 表单选择器包括表单对象属性过滤选择器和扩展表单过滤选择器.


    // 表单对象属性过滤选择器
    // 设置id为form1的表单下的可用元素的值为你改变了~
    $("#form1 input:enabled").val("你改变了~")
    // 设置id为form1的表单下的不可用元素的值为你改变了~
    $("#form1 input:disabled").val("你改变了~")
    // 获取多选框中选中的个数并显示在div中
    $("div:first").html($("input:checked").length + "个被选中!").css({
        "color": "red",
        "font-weight": "border"
    })
    // 获取多选下拉列表中被选中的个数并显示在div中
    $("div:eq(1)").html($("select:first [selected=selected]").length + "个被选中").css({
        "color": "red",
        "font-weight": "border"
    })
    // 获取单选下拉框中被选中的值并显示在div中
    $("div:last").html($("select:last [selected=selected]").text() + "被选中").css({
        "color": "red",
        "font-weight": "border"
    })
    // 表单选择器
    // :input可匹配/