jQuery 源码学习之路三(jQuery.extend)

jQuery插件的开发分两种:静态方法 jQuery.extend() 和实例方法 jQuery.fn.extend();
想研究源码,起码你要知道这两个方法的用法。这样才能更容易去探索源码的奥秘。

一. jQuery.extend()

  1. jQuery.extend( target, object1 , objectN )
  2. jQuery.extend( deep , target, object1 , objectN )
    target,object1,objectN都是指对象,target 指目标对象, deep 是布尔值,表示是否深拷贝。
    如果只有一个参数提供给$.extend(),这意味着目标参数被省略。在这种情况下,jQuery对象本身被默认为
    目标对象。这样,我们可以在jQuery的命名空间下添加新的功能。

jQuery.extend( target, object1 , objectN ) 使用例子(例子测试时,不要忘记引入jquery文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var object1 = {
apple: 0,
banana: { weight: 52, price: 100 },
cherry: 97
};
var object2 = {
banana: { price: 200 },
durian: 100
};
var object3 = {
apple : 3,
peach : {price : 150}
}
$.extend( object1, object2, object3);//把object2和object3的对象的所有属性添加到object1这个对象上。
console.log( JSON.stringify( object1 ) ); //{"apple":3,"banana":{"price":200},"cherry":97,"durian":100,"peach":{"price":150}}

由打印结果可以看出来,jQuery.extend( target, object1 , objectN ),后面对象的属性方法会添加给第一个目标对象,但是注意,前面对象所存在的属性或方法会被后面对象所存在相同的属性或方法所覆盖。

还是上面的例子,我们瞧瞧 jQuery.extend( deep , target, object1 , objectN )是什么效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var object1 = {
apple: 0,
banana: { weight: 52, price: 100 },
cherry: 97
};
var object2 = {
banana: { price: 200 },
durian: 100
};
var object3 = {
apple : 3,
peach : {price : 150}
}
//第一个值传 true
$.extend(true, object1, object2, object3);//把object2和object3的对象的所有属性添加到object1这个对象上。
console.log( JSON.stringify( object1 ) );
//打印结果:{"apple":3,"banana":{"weight":52,"price":200},"cherry":97,"durian":100,"peach":{"price":150}}

传值 true 时是深拷贝,在对象上进行了递归的合并。这里要注意一点。api中有警告:不支持第一个参数传递 false 。

二. jQuery.fn.extend( object )

这个方法是:一个对象的内容合并到jQuery的原型,以提供新的jQuery实例方法。jQuery.fn.extend()方法继承了jQuery原型($.fn)对象,以提供jQuery原型新的方法,可以链式调用jQuery()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>jQuery.fn.extend demo</title>
<script src="js/jquery-3.1.1.js"></script>
</head>
<body>
<ul>
<li>aa</li>
<li>bb</li>
<li>cc</li>
</ul>
<script>
jQuery.fn.extend({
listStyleNone : function(){
return this.each(function(){
this.style.listStyle = "none"; //去掉li的默认样式
})
}
});
$("li").listStyleNone();
</script>
</body>
</html>

源码中jQuery.extend = jQuery.fn.extend = function() {} ,两个方法是指向同一个函数的,那么他是怎样实现不同的功能呢?下面这段是jq源码的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
//常见用法 jQuery.extend( obj1, obj2 ),此时,target为arguments[0]
target = arguments[ 0 ] || {}, //没有明确实参,而是以 arguments 来判断,这样实现处理起来更灵活
i = 1,
length = arguments.length, //形参的数量
deep = false; //deep默认值设置为 false
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
//判断第一个参数是否为布尔值,是就把deep重新赋值。常见用法jQuery.extend( true, obj1, obj2 );
deep = target; //是布尔值,deep重新赋值
// Skip the boolean and the target
target = arguments[ i ] || {}; // 是布尔值,把target对象也重新赋值
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
target = {}; // 处理目标对象的类型是非 object 或者不是一个函数的情况
}
// Extend jQuery itself if only one argument is passed
if ( i === length ) {
// 如果传值只有一个对象参数,jQuery.extend(obj),或 jQuery.fn.extend( obj )
target = this; // jQuery.extend时,this指的是jQuery;jQuery.fn.extend时,this指的是jQuery.fn
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
// 深拷贝的条件:
//1. deep 的值为 true
//2. 被拷贝对象当前属性(name)的值存在
//3. 被拷贝对象的当前属性是"object"或是数组
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
( copyIsArray = jQuery.isArray( copy ) ) ) ) {
// clone存放了target[name]的值
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 ) {
// 浅拷贝且被拷贝的对象属性值不为"undefined";
target[ name ] = copy;
}
}
}
}
// Return the modified object
// 返回修改后的对象
return target;
};

jQuery.fn.extend();方法中只能传入一个对象,这样这个对象合并到 jQuery 的原型。否则是跟$.extend的功能是一样的。在(二)的例子中:

1
2
3
4
5
6
7
8
9
10
11
12
jQuery.fn.extend({
listStyleNone : function(){
return this.each(function(){
this.style.listStyle = "none"; //去掉li的默认样式
})
}
},
{
price:200
});
$("li").listStyleNone(); //控制台报错:listStyleNone is not defined

因为没有 this 的指向,就不能通过实例对象调用。

2017-11-14