jQuery源碼剖析學習筆記

jQuery源碼剖析學習筆記

jQuery源碼剖析(一)

1. 沙箱的第二個參數undefined

(function( window, undefined ) {
     //用一個函數域包起來,就是所謂的沙箱
     //在這裡邊var定義的變量,屬於這個函數域內的局部變量,避免污染全局
     //把當前沙箱需要的外部變量通過函數參數引入進來
     //隻要保證參數對內提供的接口的一致性,你還可以隨意替換傳進來的這個參數
    "use strict";
    window.jQuery = window.$ = jQuery;
})( window );

作用1:壓縮

(function( window, undefined ) {
  var a = undefined;
  if (a == undefined){blabla...}

  ....
  if (c == undefined) return;
})( window );

(function(w, u) {
  var a = u;
  if (a == u){blabla...}

  ....
  if (c == u) return;
})(w);

作用2:防止undefined值被篡改

在ECMAScript5之前undefined都是可寫的,也就是undefined可以賦值的。jQuery作者這麼做的目的還有防止2B程序員對undefined進行賦值後使得代碼出現瞭不可預料的bug。、

2. 存儲數組和對象方法的原因

class2type = {},
core_deletedIds = [],
core_version = "1.9.0",

//Save a reference to some core methods
core_concat = core_deletedIds.concat,
core_push = core_deletedIds.push,
core_slice = core_deletedIds.slice,
core_indexOf = core_deletedIds.indexOf,
core_toString = class2type.toString,
core_hasOwn = class2type.hasOwnProperty,
core_trim = core_version.trim,

//等同以下代碼:
core_concat = Array.prototype.concat, 
//文章一開始的介紹有稍微提到prototype
//core_deletedIds是一個數組實例
//core_deletedIds.concat方法就相當於調瞭Array類中的成員方法concat。

作用1:效率問題

調用實例arr的方法concat時,首先需要辨別當前實例arr的類型是Array,在內存空間中尋找Array的concat內存入口,把當前對象arr的指針和其他參數壓入棧,跳轉到concat地址開始執行。 當保存瞭concat方法的入口core_concat時,完全就可以省去前面兩個步驟,從而提升一些性能。

作用2:防止報錯

var obj = {}; 此時調用obj.concat是非法的,但是如果jQuery采用上邊方式二或者三的話,能夠解決這個問題。 也即是讓類數組也能用到數組的方法(這就是call跟apply帶來的另一種用法),尤其在jQuery裡邊引用一些DOM對象時,也能完美的用這個方法去解決

3. $.inArray

core_deletedIds = [],
core_indexOf = core_deletedIds.indexOf,
//相當於 core_indexOf = Array.indexOf;

//elem 規定需檢索的值。
//arr 數組
//i 可選的整數參數。規定在數組中開始檢索的位置。它的合法取值是 0 到 arr.length - 1。如省略該參數,則將從數組首元素開始檢索。
inArray: function( elem, arr, i ) {
  var len;

  if ( arr ) {
    //原生的Array對象支持indexOf方法,直接調用
    if ( core_indexOf ) {
      return core_indexOf.call( arr, elem, i );
    }

    len = arr.length;
    //當i為負數的時候,從數組後邊len+i的位置開始索引
    i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;

    for ( ; i < len; i++ ) {
      // Skip accessing in sparse arrays
      //jQuery這裡的(i in arr)判斷是為瞭跳過稀疏數組中的元素
      //例如 var arr = []; arr[2] = 1;
      //此時 arr == [undefined, undefined, 1]
      //結果是 => (0 in arr == false) (1 in arr == false) (2 in arr == true)
      //但是不解的是這裡
      //測試瞭一下 $.inArray(undefined, arr, 0)是返回-1的

      //---------------------attention---------------------------
      if ( i in arr && arr[ i ] === elem ) {
      //---------------------attention---------------------------

        return i;
      }
    }
  }

  //全部都不符合,返回-1
  return -1;
},

稀疏數組指 var arr = []; arr[1] = 1; 這種,此時 arr[0] === undefined 為 true ,但是期望得到的結果是 -1

4. $.grep

callback(value, key) ,註意與 $.each 的 callback(key, value) 相區別

5. $.map

callback(value, key) ,註意與 $.each 的 callback(key, value) 相區別 return 的特殊寫法:

core_deletedIds = [],
core_concat = core_deletedIds.concat,
// arg is for internal usage only
map: function( elems, callback, arg ) {
  var value,
    i = 0,
    length = elems.length,
    isArray = isArraylike( elems ),
    ret = [];

  // Go through the array, translating each of the items to their
  if ( isArray ) {
    for ( ; i < length; i++ ) {
      value = callback( elems[ i ], i, arg );

      if ( value != null ) {//如果返回值是null,則不加入結果中
        ret[ ret.length ] = value;
      }
    }

  // Go through every key on the object,
  } else {
    for ( i in elems ) {
      value = callback( elems[ i ], i, arg );

      if ( value != null ) {
        ret[ ret.length ] = value;
      }
    }
  }

  // Flatten any nested arrays
  //這裡相當於 var a = [];a.concat(ret)

  //---------------------attention---------------------------
  return core_concat.apply( [], ret );
  //---------------------attention---------------------------

},

return concat 的原因:

$.map( [0,1,2], function(n){
  return [ n, n + 1 ];
});
//輸出:[0, 1, 1, 2, 2, 3]
//如果是return ret的話,輸出將會是:[[0,1], [1,2], [2,3]]

6. $.trim

core_version = "1.9.0",
core_trim = core_version.trim,
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,

//---------------------attention---------------------------
trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
//---------------------attention---------------------------

  function( text ) {
    return text == null ?
      "" :
      core_trim.call( text );
  } :

  // Otherwise use our own trimming functionality
  function( text ) {
    return text == null ?
      "" :
      ( text + "" ).replace( rtrim, "" );
  }

var core_trim = String.prototype.trim; if (core_trim && !core_trim.call("\uFEFF\xA0")) 相當於: if (String.prototype.trim && "\uFEFF\xA0".trim() == "") 高級的瀏覽器已經支持原生的 String 的 trim 方法,但是jQuery還為瞭避免它沒法解析全角空白,所以加多瞭一個判斷:"\uFEFF\xA0".trim() == ""

\uFEFF是utf8的字節序標記, “\xA0”是全角空格 如果以上條件成立瞭,那就直接用原生的trim函數就好瞭

7. $.proxy

用法:

返回一個新函數,並且這個函數始終保持瞭特定的作用域。
當有事件處理函數要附加到元素上,但他們的作用域實際是指向另一個對象時,這個方法最有用瞭。
此外,最妙的是,jQuery能夠確保即便你綁定的函數是經過jQuery.proxy()處理過的函數,你依然可以傳遞原先的函數來準確無誤地取消綁定。
這個函數還有另一種用法,jQuery.proxy( scope, name )。第一個參數是要設定的作用域對象。第二個參數是將要設置作用域的函數名(必須是第一個作用域對象的一個屬性)。

var obj = {
  name: "John",
  test: function() {
    alert( this.name );
    $("#test").unbind("click", obj.test);
  }
};

$("#test").click( jQuery.proxy( obj, "test" ) ); //彈出John

// 以下代碼跟上面那句是等價的:
$("#test").click( jQuery.proxy( obj.test, obj ) );

// 可以與單獨執行下面這句做個比較。
$("#test").click( obj.test ); // 彈出$("#test")的name

guid????

8. $.type

原生 typeof 和 toString:

typeof 1 == 'number'
typeof {} == 'object'
typeof [] == 'object'
(1).toString() == "1"
({}).toString() == "[object Object]"
//再針對一些邊界的測試,
typeof null == "object"
typeof undefined == "undefined"
(null).toString()//非法
(undefined).toString()//非法

//再看看很詭異的幾個:
([]).toString() == ""
(new Error()).toString() == "Error"
//出現以上兩個的結果的原因是,Array跟Error類重寫瞭toSting方法
//如果用Object的toString方法的話,就是以下結果
Object.prototype.toString.call([]) == "[object Array]"
Object.prototype.toString.call(new Error()) == "[object Error]"

所以在判斷變量類型時,用Object.prototype.toString.call()更靠譜些
【ps:直接用{}.toString.call() 會報錯,但是var obj = {}; obj.toString.call() 就可以,不懂為什麼?????】

9. isXXX系列

isWindow

isWindow: function( obj ) {
  return obj != null && obj == obj.window;
},

為什麼要先判斷不為空??????

isNumeric

isNumeric: function( obj ) {
  return !isNaN( parseFloat(obj) ) && isFinite( obj );
},

parseFloat:如果在解析過程中遇到瞭正負號(+或-),數字(0-9),小數點,或者科學記數法中的指數(e或E)以外的字符,則它會忽略該字符以及之後的所有字符,返回當前已經解析到的浮點數.同時參數字符串首位的空白符會被忽略.

如果參數字符串的第一個字符不能被解析成為數字,則parseFloat返回NaN.

parseFloat 也可轉換和返回Infinity值.

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
  if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
    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 );
},

to be continued。。。

不懂的地方,請指教

1. $.makeArray

問題見註釋中的attention:

makeArray: function( arr, results ) {
  var ret = results || [];//不由得不贊js這個技巧
  //等同於:var ret = (!results) ? [] : results;

  if ( arr != null ) {

    //---------------------attention---------------------------
    //此處不太懂,如果arr是一個字符串的話,為什麼不直接走else push到結果中?而在此處大費周折呢?
    if ( isArraylike( Object(arr) ) ) {
      //如果arr是一個類數組對象,調用merge合到返回值
      jQuery.merge( ret,
        typeof arr === "string" ?
        [ arr ] : arr
      );
    //---------------------attention---------------------------

    } else {//如果不是數組,則將其放到返回數組末尾
      //等同於ret.push(arr);
      core_push.call( ret, arr );
    }
  }

  return ret;
},

原文錯誤指正

2.10 $.nodeName
源碼問題:

nodeName: function( elem, name ) {
  //IE下,DOM節點的nodeName是大寫的,例如DIV
  //所以統一轉成小寫再判斷
  //這裡不return elem.nodeName.toLowerCase();
  //我認為原因是為瞭保持瀏覽器自身的對外的規則,避免所有引用nodeName都要做轉換的動作
  return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
  //可以看到這裡的&&使用技巧,以上代碼等同於:
  //if (elem.nodeName) return elem.nodeName.toLowerCase() === name.toLowerCase();
  //else return elem.nodeName;
  //如此之簡潔
},

這樣如果elem.nodeName不為空,就會與name進行比較;為空,則返回空字符串,是無法獲取節點名稱的。

2.1 $.trim

var core_trim = String.prototype.trim; if (core_trim && !core_trim.call("\uFEFF\xA0")) 相當於: if (String.prototype.trim && "\uFEFF\xA0".trim() !== "")
應該是"\uFEFF\xA0".trim() == ""

發佈留言