所排除的对象,一是考虑性能,二是考虑复杂度(例如
dom
及
window
对象,如果克隆复制,消耗过大,而通过继承实现的对象,复杂程度不可预知,因此也不进行深度复制);
深度与非深度复制区别是,深度复制的对象中如果有复杂属性值(如数组、函数、
json
对象等),那将会递归属性值的复制,合并后的对象修改属性值不影响原对象,如下面例子:
1.
obj1 = { a : 'a', b : 'b' };
2.
obj2 = { x : { xxx : 'xxx',
yyy
: '
yyy
' }, y : 'y' };
3.
$.extend(true, obj1, obj2);
4.
alert(obj1.x.xxx); //
得到
"xxx"
5.
obj2.x.xxx = '
zzz
';
//
修改
obj2
对象属性的内联值,不影响合并
后对象
obj1
6.
alert(obj2.x.xxx); //
得到
"
zzz
"
7.
alert(obj1.x.xxx); //
得到
"xxx" //
值保持;如果不加
true
,则得到
“
zzz
”
后面分析源码时,可以看到具体为什么……
2
、
jQuery.
fn.extend
(
object);
jQuery.fn
=
jQuery.prototype
即指向
jQuery
对象的原型链,对其它进行的扩展,作用在
jQuery
对象上面;一般用此方法来扩展
jQuery
的对象插件
1.
//
将
hello
方法合并到
jquery
的实例对象中。
2.
$.
fn.extend
({
3.
hello:function
(){alert('hello');}
4.
});
6.
//
在
jquery
全局对象中扩展一个
net
命名空间。
7.
$.extend($.net,{
8.
hello:function
(){alert('hello');}
9.
});
//
使用
jQuery.net.hello
();
二、
jQuery
extend
实现原理
extend()
函数是
jQuery
的基础函数之一,作用是扩展现有的对象。例如下面的代码:
1.
<script type="text/
javascript
"
src
="jquery-1.5.2.js"></script>
2.
<script>
3.
obj1 = { a : 'a', b : 'b' };
4.
obj2 = {
x : { xxx : 'xxx',
yyy
: '
yyy
' },
y : 'y' };
6.
$.extend(true, obj1, obj2);
8.
alert(obj1.x.xxx);
//
得到
"xxx"
10.
obj2.x.xxx = '
zzz
';
11.
alert(obj2.x.xxx);
//
得到
"
zzz
"
12.
alert(obj1.x.xxx);
//
得带
"xxx"
13.
</script>
$.extend(true, obj1, obj2)
表示以
obj2
中的属性扩展对象
obj1
,第一个参数设为
true
表示深复制。
虽然
obj1
中原来没有
"x"
属性,但经过扩展后,
obj1
不但具有了
"x"
属性,而且对
obj2
中的
"x"
属性的修改也不会影响到
obj1
中
"x"
属性的值,这就是所谓的“深复制”了。
1
、浅复制的实现
如果仅仅需要实现浅复制,可以采用类似下面的写法:
1.
$ = {
2.
extend : function(target, options) {
3.
for (name in options) {
4.
target[name] = options[name];
5.
}
6.
return target;
7.
}
8.
};
也就是简单地将
options
中的属性复制到
target
中。我们仍然可以用类似的代码进行测试,但得到的结果有所不同
(
假设我们的
js
命名为“
jquery-extend.js
”
)
:
1.
<script type="text/
javascript
"
src
="jquery-extend.js"></script>
2.
<script>
3.
obj1 = { a : 'a', b : 'b' };
4.
obj2 = {
x : { xxx : 'xxx',
yyy
: '
yyy
' },
y : 'y' };
6.
$.extend(obj1, obj2);
8.
alert(obj1.x.xxx);
//
得到
"xxx"
10.
obj2.x.xxx = '
zzz
';
11.
alert(obj2.x.xxx);
//
得到
"
zzz
"
12.
alert(obj1.x.xxx);
//
得带
"
zzz
"
13.
</script>
obj1
中具有了
"x"
属性,但这个属性是一个对象,对
obj2
中的
"x"
的修改也会影响到
obj1
,这可能会带来难以发现的错误。
2
、深复制的实现
如果我们希望实现“深复制”,当所复制的对象是数组或者对象时,就应该递归调用
extend
。如下代码是“深复制”的简单实现:
1.
$ = {
2.
extend : function(deep, target, options) {
3.
for (name in options) {
4.
copy = options[name];
5.
if (deep && copy
instanceof
Array) {
6.
target[name] = $.extend(deep, [], copy);
7.
} else if (deep && copy
instanceof
Object) {
8.
target[name] = $.extend(deep, {}, copy);
9.
} else {
10.
target[name] = options[name];
11.
}
12.
}
13.
return target;
14.
}
15.
};
具体分为三种情况:
1.
属性是数组时,则将
target[name]
初始化为空数组,然后递归调用
extend
;
2.
属性是对象时,则将
target[name]
初始化为空对象,然后递归调用
extend
;
3.
否则,直接复制属性。
测试代码如下:
1.
<script type="text/
javascript
"
src
="jquery-extend.js"></script>
2.
<script>
3.
obj1 = { a : 'a', b : 'b' };
4.
obj2 = {
x : { xxx : 'xxx',
yyy
: '
yyy
' },
y : 'y' };
5.
$.extend(true, obj1, obj2);
6.
alert(obj1.x.xxx);
//
得到
"xxx"
7.
obj2.x.xxx = '
zzz
';
8.
alert(obj2.x.xxx); //
得到
"
zzz
"
9.
alert(obj1.x.xxx); //
得到
"xxx"
10.
</script>
现在如果指定为深复制的话,对
obj2
的修改将不会对
obj1
产生影响了;不过这个代码还存在一些问题,比如“
instanceof
Array
”在
IE5
中可能存在不兼容的情况。
jQuery
中的实现实际上会更复杂一些。
3
、更完整的实现
下面的实现与
jQuery
中的
extend()
会更接近一些:
11.
$ =
function(
) {
12.
var
copyIsArray
,
13.
toString
=
Object.prototype.toString
,
14.
hasOwn
=
Object.prototype.hasOwnProperty
;
16.
class2type = {
17.
'[
object
Boolean]' : '
boolean
',
18.
'[
object
Number]' : 'number',
19.
'[
object
String]' : 'string',
20.
'[
object
Function]' : 'function',
21.
'[
object
Array]' : 'array',
22.
'[
object
Date]' : 'date',
23.
'[
object
RegExp
]' : '
regExp
',
24.
'[
object
Object]' : 'object'
25.
},
27.
type
= function(
obj
) {
28.
return
obj
== null ?
String(
obj
) : class2type[
toString.call
(
obj
)] || "object";
29.
},
31.
isWindow
= function(
obj
) {
32.
return
obj
&&
typeof
obj
=== "object" && "
setInterval
" in
obj
;
33.
},
35.
isArray
=
Array.isArray
|| function(
obj
) {
36.
return
type(
obj
) === "array";
37.
},
39.
isPlainObject
= function(
obj
) {
40.
if
(!
obj
|| type(
obj
) !== "object" ||
obj.nodeType
||
isWindow
(
obj
)) {
41.
return
false;
42.
}
44.
if
(
obj.constructor
&& !
hasOwn.call
(
obj
, "constructor")
45.
&
& !
hasOwn.call
(
obj.constructor.prototype
, "
isPrototypeOf
")) {
46.
return
false;
47.
}
49.
var
key;
50.
for
(key in
obj
) {
51.
}
53.
return
key === undefined ||
hasOwn.call
(
obj
, key);
54.
},
56.
extend
= function(deep, target, options) {
57.
for
(name in options) {
58.
src
= target[name];
59.
copy
= options[name];
61.
if
(target === copy) { continue; }
63.
if
(deep && copy
64.
&& (
isPlainObject
(
copy) || (
copyIsArray
=
isArray
(copy)))) {
65.
if
(
copyIsArray
) {
66.
copyIsArray
= false;
67.
clone
=
src
&&
isArray
(
src
) ?
src
:
[];
69.
} else {
70.
clone
=
src
&&
isPlainObject
(
src
) ?
src
:
{};
71.
}
73.
target[
name] = extend(deep, clone, copy);
74.
} else if (
copy !
== undefined) {
75.
target[
name] = copy;
76.
}
77.
}
79.
return
target;
80.
};
82.
return
{ extend : extend };
83.
}();
首先是
$ =
function(){...}();
这种写法,可以理解为与下面的写法类似:
1.
func
= function(){...};
2.
$ =
func
();
也就是立即执行函数,并将结果赋给
$
。这种写法可以利用
function
来管理作用域,避免局部变量或局部函数影响全局域。另外,我们只希望使用者调用
$.extend()
,而将内部实现的函数隐藏,因此最终返回的对象中只包含
extend:
1.
return { extend : extend };
接下来,我们看看
extend
函数与之前的区别,首先是多了这句话:
1.
if (target === copy) { continue; }
这是为了避免无限循环,要复制的属性
copy
与
target
相同的话,也就是将“自己”复制为“自己的属性”,可能导致不可预料的循环。
然后是判断对象是否为数组的方式:
1.
type = function(
obj
) {
2.
return
obj
== null ?
String(
obj
) : class2type[
toString.call
(
obj
)] || "object";
3.
},
4.
isArray
=
Array.isArray
|| function(
obj
) {
5.
return
type(
obj
) === "array";
6.
}
如果浏览器有内置的
Array.isArray
实现,就使用浏览器自身的实现方式,否则将对象转为
String
,看是否为
"[object Array]"
。
最后逐句地看看
isPlainObject
的实现:
1.
if (!
obj
|| type(
obj
) !== "object" ||
obj.nodeType
||
isWindow
(
obj
)) {
2.
return false;
3.
}
如果定义了
obj.nodeType
,表示这是一个
DOM
元素;这句代码表示以下四种情况不进行深复制:
1.
对象为
undefined
;
2.
转为
String
时不是
"[object Object]"
;
3.
obj
是一个
DOM
元素;
4.
obj
是
window
。
之所以不对
DOM
元素和
window
进行深复制,可能是因为它们包含的属性太多了;尤其是
window
对象,所有在全局域声明的变量都会是其属性,更不用说内置的属性了。
接下来是与构造函数相关的测试:
1.
if (
obj.constructor
&& !
hasOwn.call
(
obj
, "constructor")
2.
&& !
hasOwn.call
(
obj.constructor.prototype
, "
isPrototypeOf
")) {
3.
return
false;
4.
}
如果对象具有构造函数,但却不是自身的属性,说明这个构造函数是通过
prototye
继承来的,这种情况也不进行深复制。这一点可以结合下面的代码结合进行理解:
1.
var
key;
2.
for (key in
obj
) {
3.
}
5.
return key === undefined ||
hasOwn.call
(
obj
, key);
这几句代码是用于检查对象的属性是否都是自身的,因为遍历对象属性时,会先从自身的属性开始遍历,所以只需要检查最后的属性是否是自身的就可以了。
这说明如果对象是通过
prototype
方式继承了构造函数或者属性,则不对该对象进行深复制;这可能也是考虑到这类对象可能比较复杂,为了避免引入不确定的因素或者为复制大量属性而花费大量时间而进行的处理,从函数名也可以看出来,进行深复制的只有
"
PlainObject
"
。
如果我们用如下代码进行测试:
1.
<script type="text/
javascript
"
src
="jquery-1.5.2.js"></script>
2.
<script>
3.
function O() {
4.
this.yyy
= '
yyy
';
5.
}
7.
function X() {
8.
this.xxx
= 'xxx';
9.
}
11.
X.prototype
= new O();
13.
x = new X();
15.
obj1 = { a : 'a', b : 'b' };
16.
obj2 = { x : x };
17.
$.extend(true, obj1, obj2);
19.
alert(obj1.x.yyy);
//
得到
"xxx"
20.
obj2.x.yyy = '
zzz
';
21.
alert(obj1.x.yyy);
//
得到
"
zzz
"
22.
</script>
可以看到,这种情况是不进行深复制的。
总之,
jQuery
中的
extend()
的实现方式,考虑了兼容浏览器的兼容,避免性能过低,和避免引入不可预料的错误等因素。
三、
jQuery
源码实现
还是先加一个例子,区别
jQuery.extend
及
jQuery.fn.extend
:
1.
jQuery.extend
({
2.
sayhello:function
(){
3.
console.log(
"
Hello,This
is
jQuery
Library");
4.
}
5.
})
6.
$.
sayhello
(
);
//Hello, This is
jQuery
Library
8.
jQuery.fn.extend
({
9.
check
: function() {
10.
return
this.each
(function() {
11.
this.checked
= true;
12.
});
13.
},
14.
uncheck
: function() {
15.
return
this.each
(function() {
16.
this.checked
= false;
17.
});
18.
}
19.
})
20.
$( "input[type='checkbox']" ).check(); //
所有的
checkbox
都会被选择
1
、
extend
无注释的源码
1.
jQuery.extend
=
jQuery.fn.extend
= function() {
2.
var
options, name,
src
, copy,
copyIsArray
, clone,
3.
target
= arguments[0] || {},
4.
i
= 1,
5.
length
=
arguments.length
,
6.
deep
= false;
8.
// Handle a deep copy situation
9.
if
(
typeof
target === "
boolean
" ) {
10.
deep
= target;
11.
target
= arguments[1] || {};
12.
// skip the
boolean
and the target
13.
i
= 2;
14.
}
16.
// Handle case when target is a string or something (possible in deep copy)
17.
if
(
typeof
target !== "object" && !
jQuery.isFunction
(target) ) {
18.
target
= {};
19.
}
21.
// extend
jQuery
itself if only one argument is passed
22.
if
( length ===
i
) {
23.
target
= this;
24.
--
i
;
25.
}
27.
for
( ;
i
< length;
i
++ ) {
28.
//
Only
deal with non-null/undefined values
29.
if
( (options = arguments[
i
]) != null ) {
30.
//
Extend
the base object
31.
for
( name in options ) {
32.
src
= target[ name ];
33.
copy
= options[ name ];
35.
//
Prevent
never-ending loop
36.
if
( target === copy ) {
37.
continue
;
38.
}
40.
//
Recurse
if we're merging plain objects or arrays
41.
if
( deep && copy && (
jQuery.isPlainObject
(copy) || (
copyIsArray
=
jQuery.isArray
(copy)) ) ) {
42.
if
(
copyIsArray
) {
43.
copyIsArray
= false;
44.
clone
=
src
&&
jQuery.isArray
(
src
) ?
src
:
[];
46.
} else {
47.
clone
=
src
&&
jQuery.isPlainObject
(
src
) ?
src
:
{};
48.
}
50.
//
Never
move original objects, clone them
51.
target[
name ] =
jQuery.extend
( deep, clone, copy );
53.
//
Don't
bring in undefined values
54.
} else if
( copy
!== undefined ) {
55.
target[
name ] = copy;
56.
}
57.
}
58.
}
59.
}
61.
// Return the modified object
62.
return
target;
63.
};
代码的大部分都是用来实现
jQuery.extend
()
中有多个参数时的对象合并,深度拷贝问题,如果去掉这些功能,让
extend
只有扩展静态和实例方法的功能,那么代码如下
:
1.
jQuery.extend
=
jQuery.fn.extend
= function(
obj
){
2.
//
obj
是传递过来扩展到
this
上的对象
3.
var
target=this;
4.
for
(
var
name in
obj
){
5.
//name
为对象属性
6.
//copy
为属性值
7.
copy=
obj
[name];
8.
//
防止循环调用
9.
if(
target === copy) continue;
10.
//
防止附加未定义值
11.
if(
typeof
copy === 'undefined') continue;
12.
//
赋值
13.
target[
name]=copy;
14.
}
15.
return
target;
16.
}
2
、
extend
方法进行注释解释
:
1.
jQuery.extend
=
jQuery.fn.extend
= function() {
2.
//
定义默认参数和变量
3.
//
对象分为扩展对象和被扩展的对象
4.
//options
代表扩展的对象中的方法
5.
//name
代表扩展对象的方法名
6.
//
i
为扩展对象参数起始值
7.
//deep
默认为浅复制
8.
var
options, name,
src
, copy,
copyIsArray
, clone,
9.
target = arguments[0] || {},
10.
i
= 1,
11.
length =
arguments.length
,
12.
deep = false;
14.
//
当第一个参数为布尔类型是,次参数定义是否为深拷贝
15.
//
对接下来的参数进行处理
16.
if (
typeof
target === "
boolean
" ) {
17.
deep = target;
18.
target = arguments[1] || {};
19.
//
当定义
是否深拷贝
时,参数往后移动一位
20.
i
= 2;
21.
}
23.
//
如果要扩展的不是对象或者函数,则定义要扩展的对象为空
24.
if (
typeof
target !== "object" && !
jQuery.isFunction
(target) ) {
25.
target = {};
26.
}
28.
//
当只含有一个参数时,被扩展的对象是
jQuery
或
jQuery.fn
29.
if ( length ===
i
) {
30.
target = this;
31.
--
i
;
32.
}
34.
//
对从
i
开始的多个参数进行遍历
35.
for ( ;
i
< length;
i
++ ) {
36.
//
只处理有定义的值
37.
if ( (options = arguments[
i
]) != null ) {
38.
//
展开扩展对象
39.
for ( name in options ) {
40.
src
= target[ name ];
41.
copy = options[ name ];
43.
//
防止循环引用
44.
if ( target === copy ) {
45.
continue;
46.
}
48.
//
递归处理深拷贝
49.
if ( deep && copy && (
jQuery.isPlainObject
(copy) || (
copyIsArray
=
jQuery.isArray
(copy)) ) ) {
50.
if (
copyIsArray
) {
51.
copyIsArray
= false;
52.
clone
=
src
&&
jQuery.isArray
(
src
) ?
src
: [];
54.
} else {
55.
clone
=
src
&&
jQuery.isPlainObject
(
src
) ?
src
: {};
56.
}
58.
target[ name ] =
jQuery.extend
( deep, clone, copy );
60.
//
不处理未定义值
61.
} else if ( copy !== undefined ) {
62.
//
给
target
增加属性或方法
63.
target[ name ] = copy;
64.
}
65.
}
66.
}
67.
}
69.
//
返回
70.
return target;
71.
};
部分内容借鉴网上博客资源,记不太清了,先谢谢了……