您的位置:首页 > Web前端 > JQuery

深入学习理解jQuery中的extend方法以及JavaScript中对象的复制

2015-07-02 09:39 561 查看

深入学习理解jQuery中的extend方法以及JavaScript中对象的复制

本文参见的jQuery版本号是:jquery-1.11.1.js

1. jQuery extend方法介绍

查看jQuery的API Document可以发现,extend方法被挂载在了jQuery和jQuery.fn两个不同对象上,但查看jQuery内部代码实现两者确是相同的,只是功能却不太一样;再查看官方的注释:
jQuery.extend(): Merge the contents of two or moreobjects together into the first object.
----把两个或者更多的对象合并到第一个对象当中

jQuery.fn.extend():Merge the contents of an object ontothe jQuery prototype to provide new jQuery instance methods.
----把对象挂载到jQuery的prototype对象上,用以扩展一个新的jQuery实例方法

简单理解两者区别:
jQuery.extend(object):用于将一个或多个对象的内容合并到目标对象
该函数可以将一个或多个对象的成员属性和方法复制到指定的对象上。
该函数属于全局jQuery对象。

jQuery.fn.extend(object): 函数用于为jQuery扩展一个或多个实例属性和方法(主要用于扩展方法)。
jQuery.fn是jQuery的原型对象,其extend()方法用于为jQuery的原型添加新的属性和方法。这些方法可以在jQuery实例对象上调用。
该函数属于jQuery的原型对象(jQuery.fn)。

2. 关于使用上的区别

静态函数jQuery.extend()有以下两种用法

用法一:jQuery 1.0 新增该用法。
jQuery.extend(target [, object1 ] [, objectN... ] )

用法二:jQuery 1.1.4 新增该用法。
jQuery.extend([ deep ], target , object1 [, objectN... ] )

用法二是用法一的变体,参数deep用于指示是否深度递归合并。
注意事项:

1、该函数复制的对象属性包括方法在内。此外,还会复制对象继承自原型中的属性(JS内置的对象除外)。

2、参数deep的默认值为false,可以明确指定该参数为true值,但不能明确指定为false值。简而言之,第一个参数不能为false值。

3、如果参数为null或undefined,则该参数将被忽略。

4、如果只为$.extend()指定了一个参数,则意味着参数target被省略。此时,target就是jQuery对象本身。通过这种方式,我们可以为全局对象jQuery添加新 的函数。

5、如果多个对象具有相同的属性,则后者会覆盖前者的属性值。
6、改函数的返回值为参数target代表的对象。

插播深度复制的一些知识:
jQuery中深度复制,是将除null,undefined,window对象,dom对象,通过继承创建的对象外的其它对象克隆后保存到target中;之所以排除部分对象,一是考虑性能,二是考虑复杂度(如dom、window对象,如果克隆复制,消耗过大,而通过继承实现的对象,复杂程度不可预知,因此也不进行深度复制);

深度与非深度复制区别是,深度复制的对象中如果有复杂属性值(如数组、函数、json对象等),那将会递归属性值的复制,合并后的对象修改属性值不影响原对象

静态函数jQuery.fn.extend( object )的用法:jQuery 1.0 新增该静态函数。
jQuery.fn.extend()函数的返回值是Object类型,返回jQuery的原型对象(即jQuery.fn)。

$.extend深度复制的使用示例:
extend(boolean,dest,src1,src2,src3...)
第一个参数boolean代表是否进行深度拷贝,其余参数和前面介绍的一致,什么叫深层拷贝,我们看一个例子:
var result=$.extend( true,  {},  { name: "John", location: {city: "Boston",county:"USA"} },  { last: "Resig", location: {state: "MA",county:"China"} } );///<span style="font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333339691162px; line-height: 21.6666679382324px; white-space: pre-wrap; background-color: rgb(255, 255, 255);">我们可以看到参数src1中嵌套有子对象location:{city:"Boston"}, src2中也嵌套了同名的子对象location:{state:"MA"},然后我们设置了第一个深度拷贝参数的值为true,那么合并后的结果就是: </span>
<span style="font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13.3333339691162px; line-height: 21.6666679382324px; background-color: rgb(255, 255, 255);"></span><pre name="code" class="javascript">result={name:"John",last:"Resig",
location:{city:"Boston",state:"MA",county:"China"}}
// 也就是说它会将src中的嵌套子对象也进行合并,而如果第一个参数boolean为false,我们看看合并的结果是什么,如下:


var result=$.extend( false, {},
{ name: "John", location:{city: "Boston",county:"USA"} },
{ last: "Resig", location: {state: "MA",county:"China"} }
);


那么合并后的结果就是:

result={name:"John",last:"Resig",location:{state:"MA",county:"China"}}


3.jQuery源码实现

先来看看jQuery中源代码的实现:
jQuery.extend = jQuery.fn.extend = function() {
var src, copyIsArray, copy, name, options, 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++;
}

// 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--;
}

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;
}

// 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;
};


关于上面代码的注释版本:
jQuery.extend = jQuery.fn.extend = function() {
// i 此处初始值为 1 ,主要是为了便于区分深度复制时,确保target对象的正确性
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;

// Handle a deep copy situation
if ( typeof target === "boolean" ) { // 第一个参数如果是Boolean类型则进行赋值深度的选择
deep = target;

// skip the boolean and the target
// 如果没有传入target对象并且第一个参数是boolean类型是,新创建一个对象,用于赋值
target = arguments[ i ] || {};
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对象为jQuery自身
target = this;
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;
}

// 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增加属性或方法
target[ name ] = copy;
}
}
}
}

// Return the modified object
return target;
};


代码的大部分都是用来实现jQuery.extend()中有多个参数时的对象合并,深度拷贝问题,如果去掉这些功能,让extend只有扩展静态和实例方法的功能,那么代码如下:
jQuery.extend = jQuery.fn.extend = function(object){

//object是传递过来扩展到this上的对象

var target = this;

for (var name in object){

//name为对象属性

//copy为属性值

copy = obj[name];

//防止循环调用

if(target === copy) continue;

//防止附加未定义值

if(typeof copy === 'undefined') continue;

//赋值

target[name]=copy;

}

return target;
}


4.关于复制深浅的学习与讨论:

先利用jQuery中的extend()来窥探一下深浅复制的区别,先来一个整体感知:

<!DOCTYPE html>
<html>
<head>
<script src="jquery-1.11.1.js"></script>
<script>
var
obj1 = { a : 'a', b : 'b' },

obj2 = {},

obj3 = {  x : { xxx : 'oldValue', yyy : 'yyy' },  y : 'y' };

$.extend(true, obj1, obj3);   // 第一个参数为true表示执行深度复制

$.extend(obj2, obj3);   // deep的默认值为false,表示前复制

console.log(obj1.x.xxx);  // 输出"oldValue"

obj3.x.xxx = 'newVlaue';

console.log(obj3.x.xxx);  // 输出"newValue"

console.log(obj1.x.xxx);  // 输出"oldValue" 因为是深度复制,所以obj1中的属性值的改变不会再obj2中得到体现

console.log(obj2.x.xxx);  // 输出"newValue" 因为是浅赋值,仅仅是简单的给对象多添加了一个引用而已
</script>
</head>
<body>
</body>
</html>


在感知有了一个整体的感知后,我们开始来探讨关于深浅复制的基本实现问题:

(1):浅复制的实现:(基于源码进行精简,更过具体实现可以去参考一下《JavaScript高级程序设计》,我记得里面有很多讨论的地方!)

$ = {
extend : function(target, options) {
for (name in options) {
target[name] = options[name];
}
return target;
}
};


(2):深赋值的实现:(还是基于jQuery源码进行精简的,并没有考虑DOM、Window对象这些的过滤,也没有考虑IE的兼容,听见IE不再更新的时候,心情那叫一个酸爽~~~)
$ = {
extend : function(deep, target, options) {
for (name in options) {
copy = options[name];
// 赋值过程中对属性的具体类型进行区别处理,这里只是简单的处理了一下
if (deep && copy instanceof Array) {
target[name] = $.extend(deep, [], copy);
} else if (deep && copy instanceof Object) {
target[name] = $.extend(deep, {}, copy);
} else {
target[name] = options[name];
}
}
return target;
}
};


今天先写到这里,后续再跟进!写一篇文章太耗时了,完成了一天的工作,抽点时间慢慢的记录一下~~希望自己能够不断成长!!!加油!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: