Jquery内核分析

整体结构

Query整个库文件,是一个大的匿名函数(function(window,undefined){...})(window);

window作为参数,立即执行.

匿名函数好处是能够产生一个私有空间,函数内所有变量/函数在该私有作用域内,避免和外面作用域冲突.

1传入window作为参数有两个好处:

     解释执行jQuery库时,jQuery函数作用域内就找到了window(实参在函数作用域中),不需要到顶层作用域找到window对象

     能够压缩代码(jquery-XXX.min.js版本):(function(a,b){})(window);//  a就是window,少用7个字母

 

2 增加undefined参数的作用:避免undefined被重写:

    假如在jQuery匿名函数之前有这样的代码: undefined ="now it's defined";    alert(undefined );

    那么jQuery函数执行的时候,undefined就不是未定义的了!

(function(window,undefined){...})(window);  只传递了一个参数,所以undefined就是真的未定义

 

3从代码看出来, jQuery是一个(构造)函数:function(selector,context){...}

返回的是一个new出来的对象.因此我们构造jQuery对象,不需要 new $(...),

因为返回值就是 new 出来的jQuery对象.这体现了jQuery的设计思想:write less, domore!

 

4 从最后一行代码 window.jQuery = window.$ = jQuery;

看出,$jQuery作为属性值赋给了window全局对象.因此外部程序可以访问到 $ jQuery

 

5 从代码 jQuery.fn = jQuery.prototype = ...

看出, jQuery.fn 就是 jQuery对象的原型. 因此原型上有init等各个属性和方法.

jQuery.fn.init.prototype = jQuery.fn, 所以调用 newjQuery.fn.init(selector, context)

创造出来的对象, jQuery.prototype上所有的属性和方法.

 

(function(window, undefined){

       var jQuery = function(selector, context) {

              returnnew jQuery.fn.init(selector, context);

       };

       //jQuery.fn 就是jQuery.prototype,因此原型上有init等各个属性和方法

       jQuery.fn =jQuery.prototype = {

              init :function(selector, context) {

                     //this初始化各种属性

                     //this指向的是新创建的对象

                     return this;

              }

              // 大量jQuery对象属性,这里省略

       };

       jQuery.fn.init.prototype= jQuery.fn;

       //$变成window全局的方法了,这样所有代码都能使用$

       window.jQuery= window.$ = jQuery;

})(window);

 

extendeach:

jQuery内部一开始定义了很多工具方法,jQuery后面部分实现所利用.

这里重点分析 extendeach方法.

extend

extend 方法是用于扩展一个对象. 这是一个很重要的方法,它既用于扩展jQuery本身,

也用于扩展jQuery对象(原型),还能用于扩展其他对象.而这是通过用户传入参数的不同而做区分的.

完整的extend方法是这样的:

extend([p_deep], p_target, p_object1, [p_objectN])

1 p_deep指定是否深度复制待复制对象

2 p_target是要被扩展的对象, p_objecti 是要被复制的对象

3 当只有一个对象传入时,是扩展jQueryjQuery对象原型,

  而有多个对象传入时,第一个是被扩展对象,其他是被复制对象.

jQuery,这种一个函数,多次复用的的情况很多. 这也许就是jQuery能够那么小的原因吧!

extend方法的详细代码分析,请看2.js中的注释. 这里仅仅强调几点:

1 在复制属性时,jQuery做了很多条件判断,特别是当复制的对象和扩展对象是同一对象时, 要避免递归复制

2 extend方法中,调用了jQuery isPlainObject/isArray方法.

   但是这些方法是在后面才用extend方法添加到jQuery上的.

   这就有了鸡和鸡蛋的先后问题了.而原因是后面jQuery.extend执行的时候,并没有指定深度复制.因此条件

if ( deep && copy&& ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) )

   deep打住,也就没调用 jQuery还没有的isPlainObject/isArray方法.

   因此我们再次认识到,javascript是解释执行的.只要在真正执行那个方法的时候,那个方法存在了就没问题.这种动态的特性,需要我们适应.

3 extend方法会覆盖源对象的同名属性

 

在定义了extend方法之后, 马上就使用了jQuery.extend方法,jQuery添加了不少属性和方法.其中有我们常用的each方法.

each

each方法会在对象的每个成员上调用回调函数.

如果对象没有length属性或者是函数的的, foreach方式遍历

如果有length,用下标方式遍历.

这里拿出下标遍历的代码:

for ( var value = object[0];

              i < length&& callback.call( value, i, value ) !== false; value = object[++i] ) {}

可以看出,它是遍历object下标0length-1的成员, 把下标和成员作为参数调用回调函数callback,直到回调返回false为之. 而我们知道,jQuery对象内包含了多个dom元素.

因此我们写回调函数时

 function(index, domEle){/*this代表dome元素,domEle===this*/}

而当回调函数返回false,条件不满足,结束循环.

 

(function(window, undefined) {

       var jQuery =function(selector, context) {

              return newjQuery.fn.init(selector, context);

       };

       jQuery.fn =jQuery.prototype = {

              init :function(selector, context) {

                     return this;

              }

       };

       jQuery.fn.init.prototype =jQuery.fn;

       window.jQuery = window.$ =jQuery;   

      

       /*****************************part2begin****************************/

jQuery.extend = jQuery.fn.extend= function() {

       var target = arguments[0]|| {},

       i = 1, length = arguments.length,deep = false, options, name, src, copy;

       if( typeof target === "boolean" ) {

              deep = target;

              target =arguments[1] || {};

              i = 2;

       }

       if ( typeof target !=="object" && !jQuery.isFunction(target) ) {

              target = {};

       }

       if( length === i ) {

              target= this;

              --i;

       }

       for ( ; i < length; i++) {

              if ( (options =arguments[ i ]) != null ) {

                     for ( name inoptions ) {

                            src =target[ name ];

                            copy =options[ name ];

                            if( target === copy ) {

                                   continue;

                            }

                           

              if ( deep &&copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) {

              var clone = src&& ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src

                                          :jQuery.isArray(copy) ? [] : {};

 

                                   target[name ] = jQuery.extend( deep, clone, copy );

                            } elseif ( copy !== undefined ) {

                                   target[name ] = copy;

                            }

                     }

              }

       }

       return target;

};

 

jQuery.extend({

       noConflict:function( deep ) {

              window.$= _$;

              if( deep ) {

                     window.jQuery= _jQuery;

              }

              returnjQuery;

       },

       isReady: false,

       ready: function(){/*.............*/},

       bindReady: function(){/*.............*/},

       isFunction: function( obj ){     return toString.call(obj) ==="[object Function]";  },

       isArray: function( obj ) {          return toString.call(obj) ==="[object Array]";           },

       isPlainObject: function(obj ) {},

 

       isEmptyObject: function(obj ) {},

       each:function( object, callback, args ) {

              var name, i = 0,

                     length =object.length,

                     isObj =length === undefined || jQuery.isFunction(object);

              if ( args ) {

                     if ( isObj ){

                            for (name in object ) {

                                   if( callback.apply( object[ name ], args ) === false ) {

                                          break;

                                   }

                            }

                     } else {

                            for( ; i < length; ) {

                                   if( callback.apply( object[ i++ ], args ) === false ) {

                                          break;

                                   }

                            }

                     }

              } else {

                     if ( isObj ){

                            for (name in object ) {

                                   if( callback.call( object[ name ], name, object[ name ] ) === false ) {

                                          break;

                                   }

                            }

                     } else {

              for( var value = object[0];

                     i< length && callback.call( value, i, value ) !== false; value = object[++i] ) {}

                     }

              }

              return object;

       }

       });

       /**********************part2end*************************/

})(window);

 

命名冲突

由于$是不少js库使用的名称,因此为了避免冲突,jQuery提供了一个noConflict,返回的是jQuery本身.

这样用户能够执行决定jQuery使用什么样的名称.

一开始,先保存原有的window.jQuerywindow.$属性.

_jQuery = window.jQuery,

_$ = window.$;

在后面提供一个方法:

       noConflict: function( deep) {

              /**

               * 把原来的$属性恢复了

               */

              window.$ = _$;

              if ( deep ) {

                     window.jQuery= _jQuery;

              }

              /**

               *返回jQuery,使用什么名称,用户决定吧

               */

              return jQuery;

       }

因此如果用户调用jQuery.noConfict(),那么$就不再代表jQuery.

可以 var XXX = jQuery.noConfict(). XXX就是jQuery的名称.

 

jQuery插件机制

jQuery提供两种插件机制:

1 jQuery.fn.extend(object)

2 jQuery.extend(object)

正如内核分析一开始介绍的,jQuery.fn 就等同于 jQuery.prototype, 也等同于jQuery.fn.init.prototype.

因此第一种方式能为jQuery原型添加新属性/方法. 于是 newjQuery.fn.init(..)得到的jQuery对象也就有了这些新属性/方法.

而第二种方式是扩展jQuery本身.因此扩展出来的方法/属性,可以在全局使用 $.newFunc(...);

Js代码架构问题

通常JS代码分为三层:

1  底层用最核心的代码——公共(工具)类库

2  常用的业务逻辑的类库——即公共的业务逻辑

3  针对页面具体JS的代码,解决具体的问题

比如jQuery,主要是第一第二层的代码.jQuery一开始就定义了extend,each,pushStack等工具函数. 这些函数在后面的属性操作,事件处理,DOM操作,CSS操作等代码中被经常的使用.对于我们而言,除非真的要写js框架,否则我们主要是写第三层的页面相关的js代码. 这些代码往往变动较大,并且和业务关联很大.因此在这一层,首先应该强调理解好需求以及业务.

 

对于大量的js,我们应该考虑下面两个问题:

返回结果形式

js代码提供的方法,应该如何被外部访问到?又该如何对外隐藏内部实现?

代码组织

如何组织代码,才能有良好的可维护性和可读性?

 

第一个问题,其实主要解决api友好以及命名空间的问题.

常用的方式是通过匿名函数,并且传入一个window$(jQuery)作为参数:

(fuction($){

   //$$.fn添加属性/方法

})($);

 

在匿名函数内部把定义的方法作为属性添加到window$.

这样在后面就可以这样访问匿名函数内定义的方法:

window.myFunc(...)        $.myFunc(...)     myFunc(...)

 

jQuery就是把window作为匿名函数参数的执行的.最后暴露给我们的就是 $.

 

匿名函数一大好处就是能创建一个私有空间,即作用域链,避免和外界命名冲突.

如果采用这种方式,需要注意一点的是,避免在window $上添加大量方法/属性.

最好和jQuery那样,仅仅把$作为属性添加到window,extend/ajax等其他方法,都是

$的方法,而不直接是window的方法. 这主要是避免同名方法冲突.

zTree3.5版本中,也就仅仅向$添加了一个属性: $.fn.zTree

而其他方法都是在zTree下的.

 

第二种方式是调用一个函数,返回一个json格式.

function createMathUtil(){

       return {

              sortArray:function(){...},

              standardDeviation:function(){...}

              ......

       }

}

这种方式较少使用,不做介绍.

 

第二个问题:代码组织

js代码超过200行的时候,如果不对js代码做好组织,会难于维护.

因此需要组织好代码.在组织代码时,主要是考虑如何划分模块.下面给出一些

常用模块:

常量/设置,数据,初始化,事件,用户操作/页面显示,回调函数,工具......

模块划分应该结合业务需求,并且应该尽量减少模块耦合性,提高模块内聚性.

每个模块通常可以采用json的格式封装起来.比如下面就是zTree3.5一开始的代码:

(function($){

       var settings = {}, roots ={}, caches = {},

       //default consts of core

       _consts= {

              className: {......},

              event: {},

              id: {},

              line: {},

              folder: {},

              node: {}

       },

       //default setting of core

       _setting= {

              treeId:"",

              treeObj: null,

              view: {......},

              data: {

                     key: {            },

                     simpleData:{......},

                     keep: {}

              },

              async: {},

              callback: {}

       },

       ......

});

一开始的_const_setting就是常量和设置.

模块划分有一点要注意的是,子模块的深度不能太大,否则访问一个属性时很不方便,像下面那样:

kynamic.kyanmicTreeOption.pNode.kid

一共四层!

我们再看看老师的权限树代码整体解构:

var privilege = {

          data:{ },

          init:{

             initEvent:function(){

              },

             initData:function(){

              }

          },

          operation:{

              divOption:{

              },

              userOption:{

              },

              privilegeTreeOption:{

              }

          }

      };

我认为这样的格式有两个很明显的缺点:

1 强制的把所有js代码放到一个json中了.我很怀疑其必要性.

2 把整个json赋给了 var privilege,结果在json里面访问其他属性,总是要以 privilege. 开始, 因此很容易导致访问一个属性要达到4! 相当不爽!

其实模仿 jQuery/zTree那样使用匿名函数就很好,像下面这样:

(function(window) {

var  _data = { },

         _init = {

                initEvent:function(){

                },

                initData:function(){

                       // 访问data内的值,只需要 data.XXX

                       // 不需要 kynamic.data.XXX

                }

         },

        _operation = {

                divOption:{

                },

                userOption:{

                },

                privilegeTreeOption:{

                }

         }

         // 最后把我们要暴露给外部的方法赋给 window就可以了

    window.kynamic ={init:_init};

})(window);

采用匿名函数,在匿名函数内部,每个模块是平级的,并且访问模块内的属性时减少一层kynamic.

并且在匿名函数内,编码自由度较 json格式内部编码更高.

 

 

Post请求重用

createTree封装生成树的方法

我一直很不明白,为什么老师还封装PostzTree方法.

因为在我看来,$.postzTree函数已经是很简单的了,没必要对它们再做封装.

封装肯定要有好处才去做的.常用好处是:

1 提高代码可读性,内聚性.比如减少程序员需要记忆的参数.

2 提高代码可重用性. 由于我们主要是写页面相关的js代码,因此主要是封装公共的业务逻辑的js代码.

而老师createTree的封装是这样的:

(function($){

       var treePlugin = '';

       var setting = {

              isSimpleData: true,

        treeNodeKey:"mid",

        treeNodeParentKey:"pid",

        showLine: true,

        root: {

            isRoot: true,

            nodes: []

        }

       };

       $.fn.createTree =function(treeJSON){

              //在调用的时候,把程序员写的setting覆盖掉原来的setting

              var treeObj =$(this);

              var treePlugin = '';

              $.tree =function(){};

              $.extend(setting,treeJSON.setting);

              //createTree方法是由树的容器调用

              $.post(treeJSON.url,treeJSON.parameter,function(data){

                     //treeObj.zTree(setting,data.menuitemList);

                     treeJSON.callback(treeObj,data,setting);

              });  

              //setInterval("blackF(treePlugin)",50);

       }

})($);

 

// 使用createTree

$("#kynamicTree").createTree({

              setting:{

                     treeNodeKey:'kid',

                     callback:{

                            "click":function(event,treeId, treeNode){

                                   $.tree.addNodes(treeNode,{

                                          kid:100,

                                          name:'aa',

                                          isParent:true,

                                          pid:1

                                   },false); 

                            }

                     }

              },

              url:'kynamicAction_showKynamics.action',

              callback:function(treeObj,data,setting){

                     $.tree =treeObj.zTree(setting,data.kynamicList);

              }

       });

结果你发现,createTree并没有简化多少. 其实原因很简单,因为这样封装,最多也就

省略setting 的几个参数,使用默认值而已. 事实上,这样封装,使得程序员还得把所有

内容封装为一个json格式,这就要程序员看createTree指定的 key值了.

更恶心的是,程序员要么记住所有默认参数,要么在setting中写所有设置.

事实上,默认参数的使用都应该有意义,程序员才能记住.像下面那样的默认参数,

也就仅仅限于访问menuitem数据库有用而已,如果使用其他的结点id,就没用了.

        treeNodeKey:"mid",

        treeNodeParentKey:"pid",

总之,我很不赞同封装zTree,以及$.post. jQueryzTree已经封装好了,我们没必要再封装,

除非我们要封装的是公共业务逻辑.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值