jQuery链式操作

系统 1849 0

两个问题

1.jQuery的链式操作是如何实现的?

2.为什么要用链式操作?

大家认为这两个问题哪个好回答一点呢?

 

链式操作

原理相信百度一下一大把,实际上链式操作仅仅是通过对象上的方法最后

return this

把对象再返回回来,对象当然可以继续调用方法啦,所以就可以链式操作了。那么,简单实现一个:

      
        //
      
      
        定义一个JS类
      
      
        function
      
      
         Demo() {

}

      
      
        //
      
      
        扩展它的prototype
      
      
Demo.prototype =
      
        {
    setName:
      
      
        function
      
      
         (name) {
        
      
      
        this
      
      .name = 
      
        name;
        
      
      
        return
      
      
        this
      
      
        ;
    },
    getName:
      
      
        function
      
      
         () {
        
      
      
        return
      
      
        this
      
      
        .name;
    },
    setAge:
      
      
        function
      
      
         (age) {
        
      
      
        this
      
      .age = 
      
        age;
        
      
      
        return
      
      
        this
      
      
        ;
    }
};


      
      
        //
      
      
        //工厂函数
      
      
        function
      
      
         D() {
    
      
      
        return
      
      
        new
      
      
         Demo();
}

      
      
        //
      
      
        去实现可链式的调用
      
      
D().setName("CJ").setAge(18).setName();
    

 

但……为什么要用呢?

一般的解释:

节省代码量,代码看起来更优雅。

例如如果没有链式,那么你可能需要这样写代码:

      document.getElementById("ele"
      
        ).dosomething();
document.getElementById(
      
      "ele").dootherthing();
    

这个代码中调用了两次document.getElementById来获取DOM树的元素,这样消耗比较大,而且要写两行,而链式只要写一行,节省了代码……

但我们也可以用缓存元素啊。比如:

      
        var
      
       ele = document.getElementById("ele"
      
        );
ele.dosomething();
ele.dootherthing();
      
    

而且两行并没有比一行多多少代码,甚至相应的封装反而使得代码更多了。

最糟糕的是所有对象的方法返回的都是对象本身,也就是说没有返回值,这不一定在任何环境下都适合。

举个例子,我们想弄一个超大整数BigInteger(意思是如果用Javascript的Number保存可能会溢出的整数),顺便扩展他的运算方法,会适合用链式操作么?

 例如运算3 1415926535 * 4 -  271828182,如果设计成链式风格的方法可能会是这样的:

      
        var
      
       result = (
      
        new
      
       BigInteger("31415926535")).multiply(
      
        new
      
       BigInteger("4")).subtract(
      
        new
      
       BigInteger("271828182"
      
        )).val();
console.log(
      
      "result == " + result);
    

这看起来似乎也很优雅,但是如果我们想要中间的结果怎么办呢?或许会写成这样:

      
        var
      
       bigInteger = 
      
        new
      
       BigInteger("31415926535"
      
        );

      
      
        var
      
       result1 = bigInteger.multiply(
      
        new
      
       BigInteger("4"
      
        )).val();

      
      
        var
      
       result2 = bigInteger.subtract(
      
        new
      
       BigInteger("271828182"
      
        )).val();
console.log(
      
      "result1 == " + result1 + ", result2 == " + result2);
    

这似乎一点也不优雅了,和不用链式操作没啥不同嘛!

那么如果要求是原来的BigInteger不能改变呢?好吧,链式操作似乎不能满足这个需求了。

jQuery专注于DOM对象操作,而DOM的操作会在页面上体现,不需要在Javascript中通过返回值来表示,但计算操作却不一样,我们很可能需要通过Javascript返回中间过程值另作他用。

在设计的时候,我们需要考虑链式带来的好处和坏处,因为别人用了链式,所以就用链式,可能并不是一个很好的方案。

那么到底为什么要用链式操作呢?


为了更好的异步体验

Javascript是无阻塞语言,所以他不是没阻塞,而是不能阻塞,所以他需要通过事件来驱动,异步来完成一些本需要阻塞进程的操作。

但是异步编程是一种令人疯狂的东西……运行时候是分离的倒不要紧,但是编写代码时候也是分离的就……

常见的异步编程模型有哪些呢?

  • 回调函数 

所谓的回调函数,意指先在系统的某个地方对函数进行注册,让系统知道这个函数的存在,然后在以后,当某个事件发生时,再调用这个函数对事件进行响应。

      
        function
      
      
         f(num, callback){  
    
      
      
        if
      
      (num<0
      
        )  {   
        alert(
      
      "调用低层函数处理!"
      
        );  
        alert(
      
      "分数不能为负,输入错误!"
      
        );   
    }
      
      
        else
      
      
        if
      
      (num==0
      
        ){  
        alert(
      
      "调用低层函数处理!"
      
        );  
        alert(
      
      "该学生可能未参加考试!"
      
        );  
    }
      
      
        else
      
      
        {  
        alert(
      
      "调用高层函数处理!"
      
        );  
        setTimeout(function(){callback();}, 1000);  
    }  
}
    
      
    

这里callback则是回调函数。可以发现只有当num为非负数时候callback才会调用。

但是问题,如果我们不看函数内部,我们并不知道callback会几时调用,在什么情况下调用,代码间产生了一定耦合,流程上也会产生一定的混乱。

虽然回调函数是一种简单而易于部署的实现异步的方法,但从编程体验来说它却不够好。

  • 事件监听

也就是采用事件驱动,执行顺序取决于事件顺序。 

      
        function
      
      
         EventTarget(){
    
      
      
        this
      
      .handlers =
      
         {};
}

EventTarget.prototype 
      
      =
      
         {
    constructor: EventTarget,
    addHandler: 
      
      
        function
      
      
        (type, handler){
        
      
      
        this
      
      .handlers[type] =
      
         [];
    },
    fire: 
      
      
        function
      
      
        (){
        
      
      
        if
      
      (!
      
        event.target){
            event.target 
      
      = 
      
        this
      
      
        ;
        }
        
      
      
        if
      
      (
      
        this
      
      .handlers[event.type 
      
        instanceof
      
      
         Array]){
            
      
      
        var
      
       handlers = 
      
        this
      
      
        .handlers[event.type];
            
      
      
        for
      
      (
      
        var
      
       i = 0, len = handlers.length, i < len; i++
      
        ){
                handlers[i](event);
            }
        }
    },
    removeHandler: 
      
      
        function
      
      
        (type, handler){
        
      
      
        if
      
      (
      
        this
      
      .handlers[type] 
      
        instanceof
      
      
         Array){
            
      
      
        var
      
       handlers = 
      
        this
      
      
        .handlers[type];
            
      
      
        for
      
      (
      
        var
      
       i = 0, le = handlers.length; i < len; i++
      
        ){
                
      
      
        if
      
      (handlers[i] ===
      
         handler){
                    
      
      
        break
      
      
        ;
                }
            }

            handlers.splice(i, 
      
      1
      
        );
        }
    }
};    
      
    

上面是《JavaScript高级程序设计》中的自定义事件实现。于是我们就可以通过addHandler来绑定事件处理函数,用fire来触发事件,用removeHandler来删除事件处理函数。

虽然通过事件解耦了,但流程顺序更加混乱了。

  • 链式异步 

个人觉得链式操作最值得称赞的还是其解决了异步编程模型的执行流程不清晰的问题。jQuery中$(document).ready就非常好的阐释了这一理念。DOMCotentLoaded是一个事件,在DOM并未加载前,jQuery的大部分操作都不会奏效,但jQuery的设计者并没有把他当成事件一样来处理,而是转成一种“选其对象,对其操作”的思路。$选择了document对象,ready是其方法进行操作。这样子流程问题就非常清晰了,在链条越后位置的方法就越后执行。

      (
      
        function
      
      
        (){
       
      
      
        var
      
       isReady=
      
        false
      
      ; 
      
        //
      
      
        判断onDOMReady方法是否已经被执行过
      
      
        var
      
       readyList= [];
      
        //
      
      
        把需要执行的方法先暂存在这个数组里
      
      
        var
      
       timer;
      
        //
      
      
        定时器句柄
      
      
       ready=
      
        function
      
      
        (fn) {
              
      
      
        if
      
      
         (isReady )
                     fn.call( document);
              
      
      
        else
      
      
        
                     readyList.push( 
      
      
        function
      
      () { 
      
        return
      
       fn.call(
      
        this
      
      
        );});
              
      
      
        return
      
      
        this
      
      
        ;
       }
       
      
      
        var
      
       onDOMReady=
      
        function
      
      
        (){
              
      
      
        for
      
      (
      
        var
      
       i=0;i<readyList.length;i++
      
        ){
                     readyList[i].apply(document);
              }
              readyList 
      
      = 
      
        null
      
      
        ;
       }
       
      
      
        var
      
       bindReady = 
      
        function
      
      
        (evt){
              
      
      
        if
      
      (isReady) 
      
        return
      
      
        ;
              isReady
      
      =
      
        true
      
      
        ;
              onDOMReady.call(window);
              
      
      
        if
      
      
        (document.removeEventListener){
                     document.removeEventListener(
      
      "DOMContentLoaded", bindReady, 
      
        false
      
      
        );
              }
      
      
        else
      
      
        if
      
      
        (document.attachEvent){
                     document.detachEvent(
      
      "onreadystatechange"
      
        , bindReady);
                     
      
      
        if
      
      (window ==
      
         window.top){
                            clearInterval(timer);
                            timer 
      
      = 
      
        null
      
      
        ;
                     }
              }
       };
       
      
      
        if
      
      
        (document.addEventListener){
              document.addEventListener(
      
      "DOMContentLoaded", bindReady, 
      
        false
      
      
        );
       }
      
      
        else
      
      
        if
      
      
        (document.attachEvent){
              document.attachEvent(
      
      "onreadystatechange", 
      
        function
      
      
        (){
                     
      
      
        if
      
      ((/loaded|complete/
      
        ).test(document.readyState))
                            bindReady();
              });
              
      
      
        if
      
      (window ==
      
         window.top){
                     timer 
      
      = setInterval(
      
        function
      
      
        (){
                            
      
      
        try
      
      
        {
                                   isReady
      
      ||document.documentElement.doScroll('left');
      
        //
      
      
        在IE下用能否执行doScroll判断dom是否加载完毕
      
      
                            }
      
        catch
      
      
        (e){
                                   
      
      
        return
      
      
        ;
                            }
                            bindReady();
                     },
      
      5
      
        );
              }
       }
})();
      
    

上面的代码不能用$(document).ready,而应该是window.ready。

  • Deferred & Promise

CommonJS中的异步编程模型也延续了这一想法,

每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。  

所以我们可以这样写:

f1().then(f2).then(f3);

这种方法我们无需太过关注实现,也不太需要理解异步,只要懂得通过函数选对象,通过then进行操作,就能进行异步编程。 

 

参考资料

Javascript异步编程的4种方法

jQuery链式操作


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论