今天复习了下jQuery的事件绑定机制,记录一下
jQuery源码剖析(一)
(function( window, undefined ) {
//用一个函数域包起来,就是所谓的沙箱
//在这里边var定义的变量,属于这个函数域内的局部变量,避免污染全局
//把当前沙箱需要的外部变量通过函数参数引入进来
//只要保证参数对内提供的接口的一致性,你还可以随意替换传进来的这个参数
"use strict";
window.jQuery = window.$ = jQuery;
})( window );
(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);
在ECMAScript5之前undefined都是可写的,也就是undefined可以赋值的。jQuery作者这么做的目的还有防止2B程序员对undefined进行赋值后使得代码出现了不可预料的bug。、
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。
调用实例arr的方法concat时,首先需要辨别当前实例arr的类型是Array,在内存空间中寻找Array的concat内存入口,把当前对象arr的指针和其他参数压入栈,跳转到concat地址开始执行。 当保存了concat方法的入口core_concat时,完全就可以省去前面两个步骤,从而提升一些性能。
var obj = {}; 此时调用obj.concat是非法的,但是如果jQuery采用上边方式二或者三的话,能够解决这个问题。 也即是让类数组也能用到数组的方法(这就是call跟apply带来的另一种用法),尤其在jQuery里边引用一些DOM对象时,也能完美的用这个方法去解决
$.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
$.grep
callback(value, key)
,注意与 $.each
的 callback(key, value)
相区别
$.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]]
$.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函数就好了
$.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
$.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()
就可以,不懂为什么?????】
isWindow: function( obj ) {
return obj != null && obj == obj.window;
},
为什么要先判断不为空??????
isNumeric: function( obj ) {
return !isNaN( parseFloat(obj) ) && isFinite( obj );
},
parseFloat:如果在解析过程中遇到了正负号(+或-),数字(0-9),小数点,或者科学记数法中的指数(e或E)以外的字符,则它会忽略该字符以及之后的所有字符,返回当前已经解析到的浮点数.同时参数字符串首位的空白符会被忽略.
如果参数字符串的第一个字符不能被解析成为数字,则parseFloat返回NaN.
parseFloat 也可转换和返回Infinity值.
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 );
},
$.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;
},
$.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进行比较;为空,则返回空字符串,是无法获取节点名称的。
var core_trim = String.prototype.trim; if (core_trim && !core_trim.call("\uFEFF\xA0"))
相当于:if (String.prototype.trim && "\uFEFF\xA0".trim() !== "")
应该是"\uFEFF\xA0".trim() == ""