苏州网站建设带你一起玩转你不知道的jQuery
发布日期:2018-01-13   浏览量:5164 次

方法 jQuery.extend() 和 jQuery.fn.extend() 执行的关键步骤如下所示: 1)修正参数 deep、target、源对象的起始下标。 2)逐个遍历源对象:

a. 遍历源对象的属性。

b. 覆盖目标对象的同名属性;如果是深度合并,则先递归调用 jQuery.extend()。 下面分析方法 jQuery.extend() 和 jQuery.fn.extend() 的源码。

1. 定义 jQuery.extend() 和 jQuery.fn.extend()

相关代码如下所示:

324 jQuery.extend = jQuery.fn.extend = function() {

第 324 行:因为参数的个数是不确定的,可以有任意多个,所以没有列出可接受的参数。

2. 定义局部变量

相关代码如下所示:

325 var options, name, src, copy, copyIsArray,

326 clone, target = arguments[0] || {},

327 i = 1,

328 length = arguments.length,

329 deep = false;

第 325 ~ 329 行:定义一组局部变量,它们的含义和用途如下: ‰

变量 options:指向某个源对象。
‰变量 name:表示某个源对象的某个属性名。
‰变量 src:表示目标对象的某个属性的原始值。
‰变量 copy:表示某个源对象的某个属性的值。 ‰
变量 copyIsArray:指示变量 copy 是否是数组。 ‰
变量 clone:表示深度复制时原始值的修正值。 ‰
变量 target:指向目标对象。
变量 i:表示源对象的起始下标。
‰变量 length:表示参数的个数,用于修正变量 target。 ‰
变量 deep:指示是否执行深度复制,默认为 false。

3. 修正目标对象 target、源对象起始下标 i
相关代码如下所示:

331 // Handle a deep copy situation

332 if ( typeof target === "boolean" ) {

333 deep = target;

334 target = arguments[1] || {};

335 // skip the boolean and the target

336 i = 2;

337 }

338 // Handle case when target is a string or something (possible in deep copy)

339 if ( typeof target !== "object" && !jQuery.isFunction(target) ) {

340 target = {};

341 // extend jQuery itself if only one argument is passed

342 if ( length === i ) {

343 target = this;

344 --i;

345 }

第 331~337 行:如果第一个参数是布尔值,则修正第一个参数为 deep,修正第二个参数 为目标对象 target,并且期望源对象从第三个元素开始。

变量 i 的初始值为 1,表示期望源对象从第 2 个元素开始;当第一个参数为布尔型时, 变量 i 变为 2,表示期望源对象从第 3 个元素开始。

第 339 ~ 342 行:如果目标对象 target 不是对象、不是函数,而是一个字符串或其他的 基本类型,则统一替换为空对象 {},因为在基本类型上设置非原生属性是无效的。例如:

var s = 'hi';

s.test = 'hello';

console.log( s.test ); // 打印 undefined

第 344 ~ 348 行:变量 i 表示源对象开始的下标,变量 length 表示参数个数,如果二者相等,表示期望的源对象没有传入,则把 jQuery 或 jQuery.fn 作为目标对象,并且把源对象 的开始下标减一,从而使得传入的对象被当作源对象。变量 length 等于 i 可能有两种情况:

‰ extend( object ),只传入了一个参数。

‰ extend( deep, object ),传入了两个参数,第一个参数是布尔值。

4. 逐个遍历源对象

相关代码如下所示:

350 for ( ; i < length; i++ ) {

第 350 行:循环变量 i 表示源对象开始的下标,很巧妙的用法。

下面的代码用于遍历源对象的属性:

351 // Only deal with non-null/undefined values

352 if ( (options = arguments[ i ]) != null ) {

353 // Extend the base object

354 for ( name in options ) {

第 352 行:arguments 是一个类似数组的对象,包含了传入的参数,可以通过整型下标 访问指定位置的参数。这行代码把获取源对象和对源对象的判断合并为一条语句,只有源对 象不是 null、undefined 时才会继续执行。

第 353 行:开始遍历单个源对象的属性。

相关代码如下所示:

355 src = target[ name ];

356 copy = options[ name ];

357 // Prevent never-ending loop

358 if(target===copy){

359 continue;

360 }

361 // Recurse if we're merging plain objects or arrays

362 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {

363 if ( copyIsArray ) {

364 copyIsArray = false;

365 clone = src && jQuery.isArray(src) ? src : [];

366 }else{

367 clone = src && jQuery.isPlainObject(src) ? src : {}; }

368 // Never move original objects, clone them

369 target[ name ] = jQuery.extend( deep, clone, copy );

370 // Don't bring in undefined values } else if ( copy !== undefined ) {

371 target[ name ] = copy;

第 355 ~ 361 行:变量 src 是原始值,变量 copy 是复制值。如果复制值 copy 与目标对 象 target 相等,为了避免深度遍历时死循环,因此不会覆盖目标对象的同名属性。如果注释 掉第 360 行,下面的代码会抛出堆栈溢出异常:

var o = {};

o.n1 = o;

$.extend( true, o, { n2: o } );

// 抛出异常:

// Uncaught RangeError: Maximum call stack size exceeded

注意,判断 target === copy 时使用的是“ ===”,强制不做类型转换;如果使用“ ==”, 则可能因自动类型转换而导致错误 。

第 364 ~ 374 行:如果是深度合并,且复制值 copy 是普通 JavaScript 对象或数组,则递 归合并。

第 365 ~ 371 行:复制值 copy 是数组时,如果原始值 src 不是数组,则修正为空数组; 复制值 copy 是普通 JavaScript 对象时,如果原始值 src 不是普通 JavaScript 对象,则修正为 空对象 {}。把原始值 src 或修正后的值赋值给原始值副本 clone。

通过调用方法 jQuery.isPlainObject( copy ) 判断复制值 copy 是否是“纯粹”的 JavaScript 对象,只有通过对象直接量 {} 或 new Object() 创建的对象,才会返回 true。例如:

jQuery.isPlainObject( { hello: 'world' } ); // true

jQuery.isPlainObject( new Object() ); // true

jQuery.isPlainObject( new Object(1) ); // true

.selector、.jquery、.length、.size()

属性 selector 用于记录 jQuery 查找和过滤 DOM 元素时的选择器表达式,但不一定是可执行的选择器表达式,该属性更多的是为了方便调试。下面是一些示例:

$('div').find('p').selector // "div p"

$('div p').selector // "div p"

$('div').first().selector // "div.slice(0,1)"

属性 jquery 表示正在使用的 jQuery 版本号。

属性 .length 表示当前 jQuery 对象中元素的个数。

方法 .size() 返回当前 jQuery 对象中元素的个数。方法 .size() 在功能上等价于属性 .length,

但应该优先使用属性 .length,因为它没有函数调用开销。

属性 .selector、.jquery、.length、.size() 的相关代码如下所示:

209 // Start with an empty selector

210 selector: "",

212 // The current version of jQuery being used

213 jquery: "1.7.1",

215 // The default length of a jQuery object is 0

216 length: 0,

218 // The number of elements contained in the matched element set

219 size: function() {

220 return this.length;

221 },

.toArray()、.get( [index] )

1. .toArray()

方法 .toArray() 将当前 jQuery 对象转换为真正的数组,转换后的数组包含了所有元素。 方法 .toArray() 的实现巧妙地借用了数组的方法 slice(),相关的代码如下所示:

86 // Save a reference to some core methods

87 toString = Object.prototype.toString,

88 hasOwn = Object.prototype.hasOwnProperty,

89 push = Array.prototype.push,

90 slice = Array.prototype.slice,

91 trim = String.prototype.trim,

92 indexOf = Array.prototype.indexOf,

223 toArray: function() {

224 return slice.call( this, 0 );

225 }

第 86 ~ 92 行: 连 同 slice() 一 起 声 明 的 还 有 toString()、hasOwn()、trim()、indexOf(), 这里通过声明对这些核心方法的引用,使得在 jQuery 代码中可以借用这些核心方法的功能, 执行时可通过方法 call() 和 apply() 指定方法执行的环境,即关键字 this 所引用的对象。这种“借鸡下蛋”的技巧非常值得借鉴。

数组方法 slice() 返回数组的一部分,语法如下

array.slice(start, end)

// 参数 start 表示数组片段开始处的下标。如果是负数,它声明从数组末尾开始算起的位置

// 参数end 表示数组片段结束处的后一个元素的下标。如果没有指定这个参数,切分的数组包含从 start

开始到数组结束的所有元素。如果这个参数是负数,它声明的是从数组尾部开始算起的元素

2. .get( [index] )

方法 .get( [index] ) 返回当前 jQuery 对象中指定位置的元素或包含了全部元素的数组。 如果没有传入参数,则调用 .toArray() 返回包含了所有元素的数组;如果指定了参数 index, 则返回一个单独的元素;参数 index 从 0 开始计算,并且支持负数,负数表示从元素集合末尾开始计算。相关代码如下所示:

227 // Get the Nth element in the matched element set OR

228 // Get the whole matched element set as a clean array

229 get: function( num ) {

230 return num == null ?

231 // Return a 'clean' array

232 this.toArray() :

233 // Return just the object

234 ( num < 0 ? this[ this.length + num ] : this[ num ] ); },

第 236 行:先判断 num 是否小于 0,如果小于 0,则用 length + num 重新计算下标,然 后使用数组访问操作符([])获取指定位置的元素,这是支持下标为负数的一个小技巧;如果num 大于等于 0,则直接返回指定位置的元素。

.each( function(index, Element) )、jQuery.each( collection, callback (indexInArray, valueOfElement) )

方法 .each() 遍历当前 jQuery 对象,并在每个元素上执行回调函数。每当回调函数执行时,会 传递当前循环次数作为参数,循环次数从 0 开始计数;更重要的是,回调函数是在当前元素为上 下文的语境中触发的,即关键字 this 总是指向当前元素;在回调函数中返回 false 可以终止遍历。

方法 .each() 内部通过简单的调用静态方法 jQuery.each() 实现,相关代码如下所示:

267 // Execute a callback for every element in the matched set.

268 // (You can seed the arguments with an array of args, but this is

269 // only used internally.)

270 each: function( callback, args ) {

271 return jQuery.each( this, callback, args );

272 }

2. jQuery.each( collection, callback(indexInArray, valueOfElement) )

静态方法 jQuery.each() 是一个通用的遍历迭代方法,用于无缝地遍历对象和数组。对于 数组和含有 length 属性的类数组对象(如函数参数对象 arguments),该方法通过下标遍历, 从 0 到 length-1 ;对于其他对象则通过属性名遍历(for-in)。在遍历过程中,如果回调函数 返回 false,则结束遍历。

定义方法 jQuery.each( object, callback, args ),它接受 3 个参数。

分享:
上一篇:到底怎么回事?为什么电脑存储盘没有A、B盘,而是从C盘开始划分的?
下一篇:跳一跳只是个开胃菜,微信小程序是要准备开足马力爆发吗?

 
首页
咨询