首页 > 安全资讯 >

jQuery源码剖析学习笔记

17-08-21

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() == ""

相关文章
最新文章
热点推荐