首页 > 代码库 > Java程序员的JavaScript学习笔记(9—— jQuery工具方法)

Java程序员的JavaScript学习笔记(9—— jQuery工具方法)

计划按如下顺序完成这篇笔记:
 
1.    理念。
2.    属性复制和继承。
3.    this/call/apply。
4.    闭包/getter/setter。
5.    prototype。
6.    面向对象模拟。
7.    jQuery基本机制。
8.    jQuery选择器。
9.    jQuery工具方法。
10.    jQuery-在“类”层面扩展。
11.    jQuery-在“对象”层面扩展。
12.    jQuery-扩展选择器。
13.    jQuery UI。
14.    扩展jQuery UI。

这是笔记的第9篇,从jQuery源码的角度,聊聊jQuery的工具方法。

1、先看几个工具方法如何使用:
var t = $.trim(‘   >>_<<   ‘);
var ps = $.param({x:15,y:16});
jQuery.type(function(){}) === "function"

jQuery的工具方法,相对jQuery本身,是相对独立的。

2、这些方法是如何定义的呢?
我们猜测一下,试试看。
我们知道jQuery(即$)是全局变量,这些方法应该是jQuery变量的属性。
我们可以通过如下语法添加:
jQuery.xx = function(a,b){
 return a + b;
}
// try
var r1 = jQuery.xx(2,3);
console.log(r1);// output : 5
var r2 = $.xx(3,3);
console.log(r2);// output : 6
上面代码,说明$和jQuery引用了相同变量,他们是一样一样的。
这些代码达到了定义工具方法的目的,但jQuery是这样做的吗?

3、
查看jQuery源代码,jQuery通过如下语法定义工具方法:
jQuery.extend({
 noConflict: function( deep ) {
  if ( window.$ === jQuery ) {
   window.$ = _$;
  }

  if ( deep && window.jQuery === jQuery ) {
   window.jQuery = _jQuery;
  }

  return jQuery;
 }
 //, ...
 });
extend方法定义如下:

//定义了一个方法,赋值给jQuery.fn.extend和jQuery.extend.
//jQuery.fn.extend和jQuery.extend虽然定义相同,但调用者不同,从而方法中this指向不同,从而实现的功能不同。
//没有显式声明参数,而是通过arguments动态获得,从而支持更丰富的功能。
jQuery.extend = jQuery.fn.extend = function() {
 var src, copyIsArray, copy, name, options, clone,
  // 将第一个参数作为目标对象
  target = arguments[0] || {},
  // i初始化为1,暂时还不知道做什么用
  i = 1,
  // length表示参数的个数
  length = arguments.length,
  // deep 应该表示是否深层拷贝,默认为否。
  deep = false;

 // Handle a deep copy situation
 if ( typeof target === "boolean" ) {
  //如果第一个参数是布尔类型,则第二个参数为目标对象
  //形如:jQuery.extend(false,UiObject,...);
  deep = target;
  target = arguments[1] || {};
  // skip the boolean and the target
  // i 是当前参数的数组下标,从0开始
  i = 2;
 }

 // Handle case when target is a string or something (possible in deep copy)
 //目标对象是能是函数或者对象,传入其他参数,target默认为空对象。
 if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
  target = {};
 }

 // extend jQuery itself if only one argument is passed
 // 目前,如果第一个参数不是布尔,i值为1,length也为1,即extend(obj)的情况
 // 如果是布尔,i值为2,length也为2,即extend(true,obj)的情况
 // 总之,没有传入 src 
 if ( length === i ) {
  // jQuery 把传入参数收了
  // 参数下标重新指向传入的这个参数,即此时指向了src
  target = this;
  --i;
 }

 // 现在i指向了target后面某个参数,应该是src
 // 把后面传入的每个参数都当src
 for ( ; i < length; i++ ) {
  // Only deal with non-null/undefined values
  // 这样过滤非空啊,如果是undefined会被过滤掉吗?
  // 不做 typeof src 是否 "object"、"function"的判断吗?
  if ( (options = arguments[ i ]) != null ) {
   // 赋值给变量 options
   // Extend the base object
   for ( name in options ) {
    src = http://www.mamicode.com/target[ name ];
    copy = options[ name ];
    
    // 思虑万全:如果已经是自家人,就别嘚瑟了
    // Prevent never-ending loop
    if ( target === copy ) {
     continue;
    }

    // Recurse if we‘re merging plain objects or arrays
    // 对普通对象和数组,考虑递归深层拷贝
    // 问题来了,什么叫普通对象? function 算不算?
    if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
     if ( copyIsArray ) {
      // 设下次循环的默认值
      copyIsArray = false;
      // 目标对象如果原来有这个属性,但不是数组类型,就被干掉了
      clone = src && jQuery.isArray(src) ? src : [];

     } else {
      // 目标对象如果原来没有这个属性或者不是普通对象,默认为{}。
      // 如果已经有了而且是普通对象,那就用它了
      clone = src && jQuery.isPlainObject(src) ? src : {};
     }
     //递归调
     //如果写成 jQuery.extend( deep, clone, copy ) 是否一样
     // Never move original objects, clone them
     target[ name ] = jQuery.extend( deep, clone, copy );

    // Don‘t bring in undefined values
    // 对 undefined 和 != null 还要加深理解
    } else if ( copy !== undefined ) {
     // 临门一脚,如果是 jQuery.extend({xx,xfasf});的情况,扩展的是this,即调用者。
     // 对target直接修改,也没创建clone啥的
     target[ name ] = copy;
    }
   }
  }
 }

 // Return the modified object
 return target;
};


4、再看看jQuery.isPlainObject如何定义的
 isPlainObject: function( obj ) {
  
  // Must be an Object.
  // Because of IE, we also have to check the presence of the constructor property.
  // Make sure that DOM nodes and window objects don‘t pass through, as well
  // 翻译:
  // 必须是一个对象。
  // 因为IE的缘故,我们还必须检查是否有constructor属性。
  // 确认别放过Dom节点和window对象,他们不是普通对象。
  if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
   // 以下不通过空、undefined、0、jQuery.type返回不是object的、dom对象、window对象
   // 所以 typeof 为string、number、function、正则、date的都不成
   return false;
  }

  try {
   // Not own constructor property must be Object
   if ( obj.constructor &&
    !core_hasOwn.call(obj, "constructor") &&
    !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
    return false;
   }
  } catch ( e ) {
   // IE8,9 Will throw exceptions on certain host objects #9897
   return false;
  }
  // 枚举的时候,自有属性会先被枚举出来,如果最后一个属性是自有属性,那么所有属性都是自有属性。
  // 简而言之,不能有继承来的,可以被枚举到的属性,你妹!为什么有这样的要求?
  // Own properties are enumerated firstly, so to speed up,
  // if last one is own, then all properties are own.

  var key;
  for ( key in obj ) {}

  return key === undefined || core_hasOwn.call( obj, key );
 }
5、
试试看

jQuery.extend({
 sayHi:function(you){
  console.log(‘hi‘,you);
 }
});

$.sayHi(‘Stanley‘); // output : hi Stanley

还不错!如果想覆盖jQuery已有的方法呢?
jQuery.extend({
 isPlainObject:function(obj){
  return ‘baby, I am not sure.‘;
 }
});

var r = $.isPlainObject({});
console.log(r); // output : baby, I am not sure.
这...好吧,可以得逞。想想别的办法吧,不能这样。

再玩儿:
jQuery.extend({
 extend:function(obj){
  // do nothing
  return ‘who am i?‘;
 }
});

var r = $.extend({x:function(){ alert();}});
console.log(r); // output : who am i?
jQuery.x();// error : function not be defined
又得逞,这样,再没人可以通过extend扩展jQuery的方法了。
但有更直接的方法。



Java程序员的JavaScript学习笔记(9—— jQuery工具方法)