四(补充)、jQuery中的继承方法extend(庖丁解牛版)
深入学习jQuery源码后,才晓得了jQuery设计的优美和巧妙。我在前面虽然已经写过了jQuery.extend
方法源码的相关文章,勉强还是能看的,但不是太令人满意,所以我在本篇中对其进行补充说明,虽然这将消耗我大量的时间(写博客真的很费时间)。
jQuery.extend
方式可以是jQuery的静态工具方法,也可以是jQuery对象的实例方法,简单的说,就是既可以通过$.extend()
的形式调用,也可以通过$().extend()
的形式调用。之所以这么说,是因为其源码中的第一行代码。
jQuery.extend = jQuery.fn.extend = function() { ... }
一、extend方法的使用场景
1、jQuery中扩展插件
当给extend方法传递一个参数(对象字面量)的时候,我们就可以给jQuery扩展插件了。
$.extend({
aaa: function(){
alert('aaa')
},
bbb:function(){
alert('bbb')
}
})
$.fn.extend({
ccc:function(){
alert('ccc')
},
ddd:function(){
alert('eee')
}
})
// 将依次弹窗 aaa、bbb、ccc、ddd
$.aaa()
$.bbb()
$().ccc()
$().ddd()
这两种方式定义的方式差别在于,调用它们的this对象不一致。
$.extend() -> this -> $ ->this.aaa -> $.aaa() // this对象是jQuery/$(jQuery构造函数的静态方法)
$.fn.extend() -> this -> this.ccc -> $().ccc() // this对象是JQ对象
2、合并多个对象
当给extend传递多个参数(对象字面量)的时候,第2个参数到最后一个参数的属性全都会扩展到第一个参数上面。
var a = {}
$.extend( a, { name: 'hello'}, { age: 30})
console.log(a) //{name: "hello", age: 30}
3、实现深拷贝和浅拷贝
对于深拷贝和浅拷贝的概念,相信大家都比较熟悉了,在jQuery中,我们可以通过给extend的第一个参数传递一个boolean
来控制实现是深拷贝还是浅拷贝。
// 浅拷贝
var a = {}
var b = { name: { age: 18 }}
$.extend( true, a, b)
a.name.age = 20
console.log(b.name.age) //20
// 深拷贝
var a = {}
var b = { name: { age: 18 }}
$.extend( true, a, b)
a.name.age = 20
console.log(b.name.age) //18
二、extend方法源码分析
extend方法的源码不长,处理逻辑也不是很复杂,下面,我们现将其源码的逻辑结构进行简单梳理,然后按照具体的逻辑逐一进行说明。
jQuery.extend = jQuery.fn.extend = function(){
// 定义一些变量
if(){} 看是不是深拷贝的情况
if(){} 看参数正不正确
if(){} 看是不是插件情况
for(){ 可能有多个对象的情况
if(){} 防止循环引用
if(){} 深拷贝
else if(){} 钱拷贝
}
}
1、定义了一些初始变量值
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
对于这些定义的变量,我们要关心他们的初始值。比如target
的值。
2、处理深度复制的情形
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
默认情况下进行的是浅拷贝,也就是说第一个参数可传也可以不传。如果要传,必须是boolean类型的值,这时,我们就需要矫正target的值了,因为我们默认的target值是第一个参数,顺便将下面要在for循环中使用的i
的修正为2.
3、验证参数的合法性
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
要确保target为对象或者函数。
4、处理只传入一个参数的情况
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
变量i
递减问0,不再进行下面的for循环,也就是处理插件的情况。
5、处理传入多个参数的情况
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;
}
}
}
}
代码也对复杂,我们继续分解。👉
5.1 防止循环引用
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
假如注释掉这段代码,执行下面的代码,会有怎样的效果呢。看到了吧,无限循环引用,具体原因您需要脑补一下。
var a = {};
var b = { name: a }
console.log($.extend(a, b))
执行结果如下:

5.2 实现深拷贝
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 );
}
深拷贝用到了递归,这里我们纠正一个错误,extend不仅可以拷贝对象字面量,也可以拷贝数组。不信,你可以尝试一下。
5.3 实现浅拷贝
else if ( copy !== undefined ) {
target[ name ] = copy;
}
浅拷贝没有什么特别的,直接拷贝即可。
至此,extend方法已经被我庖丁解牛般的解析完了,整体还是比较容易的。从中可以看出,jquery中的继承我们采用了拷贝继承,并没有使用我们经典的继承方式,当然,你可以通过查阅《红宝书》来学习Javascript中经典的继承方式。
最后,感谢您的阅读,祝你学习进步!