jQuery源代码自我理解(一)
自己看的源代码,可能理解的不够透彻,但是自己看过一遍感觉还是比较不一样的。希望各路大神多多指教!下面是自己写的其他源码理解文章:jQuery源代码自我理解(二)前面的if判断是否存在commonJs运行环境,若没有,直接执行else的内容。if ( typeof module === "object" && typeof module.exports === "object" ) {
自己看的源代码,可能理解的不够透彻,但是自己看过一遍感觉还是比较不一样的。希望各路大神多多指教!
下面是自己写的其他源码理解文章:
jQuery源代码自我理解(二)
前面的if判断是否存在commonJs运行环境,若没有,直接执行else的内容。
if ( typeof module === "object" && typeof module.exports === "object" ) {
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery.
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window);
// See ticket #14549 for more info.
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
} else {
factory( global );
}
jQuery选择页面元素的源码,之后学习再深入。
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init( selector, context );
}
pushStack()函数用来指定节点链表中该节点的上一个元素,方便元素的查找。
pushStack: function( elems ) {
// Build a new jQuery matched element set
var ret = jQuery.merge( this.constructor(), elems );
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
// Return the newly-formed element set
return ret;
}
其中涉及到的merge()函数是将第二个参数的值复制到第一个参数,然后返回第一个参数:
merge: function( first, second ) {
var len = +second.length,
j = 0,
i = first.length;
for ( ; j < len; j++ ) {
first[ i++ ] = second[ j ];
}
first.length = i;
return first;
}
each()函数对数组或对象进行遍历,执行回调函数,如果有一个元素返回false则跳出循环,直到都返回true。
each: function( obj, callback ) {
var length, i = 0;
if ( isArrayLike( obj ) ) {
length = obj.length;
for ( ; i < length; i++ ) {
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
break;
}
}
} else {
for ( i in obj ) {
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
break;
}
}
}
return obj;
}
each()函数调用了isArrayLike()来判断一个对象是不是类数组类型,其中调用了jQuery.type()函数来判断对象类型。
函数中先取得obj的长度,如果是对象,则”length” in obj
返回的是false。
type()函数返回obj的类型。
之后
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && ( length - 1 ) in obj;
如果obj的type为array或长度等于0或者(长度类型为number且长度大于0且长度减一属于obj)则返回true。
function isArrayLike( obj ) {
// Support: real iOS 8.2 only (not reproducible in simulator)
// `in` check used to prevent JIT error (gh-2145)
// hasOwn isn't used here due to false negatives
// regarding Nodelist length in IE
var length = !!obj && "length" in obj && obj.length,
type = jQuery.type( obj );
if ( type === "function" || jQuery.isWindow( obj ) ) {
return false;
}
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}
上一个函数使用到的type方法返回对象的类型,其中调用class2type对象的值。
其中一个较长逻辑为判断obj是否为对象或者函数,是的话则调用class2type对象的属性值,否则直接返回obj的类型。
toString.call( obj )返回obj的类型字符串,包括:"Boolean Number String Function Array Date RegExp Object Error Symbol"
等类型。
class2type[ toString.call( obj ) ]
返回对象中的对应属性值。
type: function( obj ) {
if ( obj == null ) {
return obj + "";
}
// Support: Android <=2.3 only (functionish RegExp)
return typeof obj === "object" || typeof obj === "function" ?
class2type[ toString.call( obj ) ] || "object" :
typeof obj;
}
定义class2type对象的属性值,值为一些常见的数据类型,例如布尔类型,数值类型,字符串类型等。
先用split()函数将字符串分割成数组,然后在jQuery.each遍历函数里将数组的值添加到class2type对象里。
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
function( i, name ) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );
Map()函数是对数组进行处理并且返回一个新数组。方法源码类似于each()函数,区别在于map()函数返回一个新数组。
map: function( elems, callback, arg ) {
var length, value,
i = 0,
ret = [];
// Go through the array, translating each of the items to their new values
if ( isArrayLike( elems ) ) {
length = elems.length;
for ( ; i < length; i++ ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
// Go through every key on the object,
} else {
for ( i in elems ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
}
// Flatten any nested arrays
return concat.apply( [], ret );
}
代码最后一行对数组进行扁平化处理,但是对于嵌套数组而言,map()并不会对其进行判断处理。比如:
var a=[2,[1,3],4,[3,[4,5,32],3],2];
var b=jQuery.map(a,function(v,i){
return v++;
});
console.log(b);
输出的为: [2, NaN, 4, NaN, 2]
此外,concat.apply([],ret);
也只会处理一次数组,例如:
var a=[2,[1,3],4,[3,[4,5,32],3],2];
console.log(Array.prototype.concat.apply( [], a ));
输出的结果为: [2, 1, 3, 4, 3, Array[3], 3, 2]
slice()函数用来取数组的元素,代码中slice.apply( this, arguments )
冒充js原生的slice()函数,然后保留链式关系。
slice: function() {
return this.pushStack( slice.apply( this, arguments ) );
}
eq()函数选择数组中的第i个元素。其中
j = +i + ( i < 0 ? len : 0 );
表示若参数为负数,则返回(长度+i),否则返回i。
另外,代码j >= 0 && j < len ? [ this[ j ] ] : []
表示在j>=0且j不超过最大长度时表达式等于数组中下标为j的元素,否则表达式等于空数组。
之后再调用pushStack()函数,确定链式关系。
eq: function( i ) {
var len = this.length,
j = +i + ( i < 0 ? len : 0 );
return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
}
end()函数返回元素的上一个节点,或返回该对象的构造函数。
end: function() {
return this.prevObject || this.constructor();
}
jQuery.extend()和jQuery.fn.extend()是实现对象拓展的函数,函数可以传递多个参数,但一般默认为jQuery.extend([deep], target, object1, [objectN])
,
其中deep为一个Boolean值,表示是否深度复制对象;
target表示函数调用后接收拓展内容的对象;
object1和object2表示要拓展到target的对象。
函数开始先对一些变量赋值,然后判断传进来的第一个参数是否为布尔值,若是则将变量deep赋值为true,表示深度拓展对象。然后将target变量迁移到下一个参数。
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[ 0 ] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
// Skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
判断第二个参数,若它既不是对象也不是函数,则令target赋值为空对象。
如果i===length,则表示传递的参数只有一个,此时target则赋值为调用该方法的对象,然后将i自减一。
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
target = {};
}
// Extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
}
之后进行if代码片段(若只有一个参数,前面的代码段执行后i自减一,此时i
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( ( options = arguments[ i ] ) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
如果是深层克隆且copy属性存在且(copy为普通对象或copy为数组)则执行下面的代码,否则直接将copy赋值给target对象。
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray( src ) ? src : [];
}
copy为数组时执行这段代码,其中src && jQuery.isArray( src ) ? src : [];
表示src若存在且是数组时,表达式返回src的值,否则重新定义一个新数组。
else {
clone = src && jQuery.isPlainObject( src ) ? src : {};
}
copy若是对象执行这段代码,其中src && jQuery.isPlainObject( src ) ? src : {};
与上面类似,若src若存在且是对象时,表达式直接返回src的值,否则重新定义一个新对象。
// Recurse if we're merging plain objects or arrays
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 : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
该方法会覆盖原对象的一些属性和方法。
上面用到的isPlainObject()方法是判断对象是否为普通对象。
isPlainObject: function( obj ) {
var proto, Ctor;
// Detect obvious negatives
// Use toString instead of jQuery.type to catch host objects
if ( !obj || toString.call( obj ) !== "[object Object]" ) {
return false;
}
proto = getProto( obj );
// Objects with no prototype (e.g., `Object.create( null )`) are plain
if ( !proto ) {
return true;
}
// Objects with prototype are plain iff they were constructed by a global Object function
Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
}
isEmptyObject()是判断对象是否为空,对于数组一样适用
isEmptyObject: function( obj ) {
var name;
for ( name in obj ) {
return false;
}
return true;
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)