extend() 是 jQuery 中一个重要的函数,主要用于实现对象的扩展和合并,在 jQuery 插件开发中也经常会用到。jQuery 内部同样大量使用了它来扩展属性和方法,例如上一篇文章中讲到的 noConflict 方法,就是通过 extend 方法扩展到 jQuery 对象上的。
在 jQuery 的 API 手册中可以看到,extend 实际上挂载在 jQuery 和 jQuery.fn 上,是两个不同的方法。尽管在 jQuery 内部,jQuery.extend() 和 jQuery.fn.extend() 使用的是同一套代码实现,但它们扩展的对象不同,因此用途也不一样。
官方 API 对它们的解释分别是:
jQuery.extend():Merge the contents of two or more objects together into the first object.
把两个或多个对象的内容合并到第一个对象中。
jQuery.fn.extend():Merge the contents of an object onto the jQuery prototype to provide new jQuery instance methods.
把对象的内容扩展到 jQuery 原型上,从而增加新的 jQuery 实例方法。
我们知道,jQuery 有静态方法和实例方法之分,那么 jQuery.extend() 和 jQuery.fn.extend() 最主要的区别,就是一个用来扩展静态方法,一个用来扩展实例方法。
用法如下:
jQuery.extend({
sayhello:function(){
console.log("Hello, This is jQuery Library");
}
});
$.sayhello(); // Hello, This is jQuery Library
jQuery.fn.extend({
check: function() {
return this.each(function() {
this.checked = true;
});
},
uncheck: function() {
return this.each(function() {
this.checked = false;
});
}
});
$("input[type='checkbox']").check();注意两种调用方式的区别:一种是直接使用 $ 调用,另外一种是使用 $() 创建 jQuery 对象后调用。
另外,jQuery.extend() 可以接收多个对象作为参数。如果只有一个参数,则会把这个对象的属性和方法附加到 jQuery 自身;如果有多个参数,则会把后面的对象合并到第一个对象中。
jQuery 中 extend 的实现源码如下:
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
i = 2;
}
// Handle case when target is a string or something
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
if ( (options = arguments[i]) != null ) {
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 : {};
}
target[name] = jQuery.extend(deep, clone, copy);
} else if ( copy !== undefined ) {
target[name] = copy;
}
}
}
}
return target;
};代码看起来比较多,乍一看可能不太容易理解。实际上其中大部分代码,主要是用来处理多个对象合并以及深拷贝的问题。如果去掉对象合并、深拷贝等功能,只保留扩展静态方法和实例方法最核心的功能,那么代码实际上可以简化成这样:
jQuery.extend = jQuery.fn.extend = function(obj){
// obj 是传递过来并扩展到 this 上的对象
var target = this;
for (var name in obj){
// name 为对象属性名
// copy 为属性值
var copy = obj[name];
// 防止循环引用
if (target === copy) continue;
// 防止附加未定义值
if (typeof copy === 'undefined') continue;
// 赋值
target[name] = copy;
}
return target;
}从这个简化版本可以比较容易看出 extend 的核心原理:遍历传入对象的属性,然后把这些属性复制到目标对象上。
那么为什么同样的代码,可以分别实现 jQuery.extend() 和 jQuery.fn.extend() 呢?关键就在于调用时的 this 不同。
jQuery.extend(...)
调用时,this 指向 jQuery,因此扩展的是 jQuery 本身,也就是静态方法。
jQuery.fn.extend(...)
调用时,this 指向 jQuery.fn,也就是 jQuery.prototype,因此扩展的是所有 jQuery 对象都可以使用的实例方法。
下面再对原来的 extend 方法进行简单注释:
jQuery.extend = jQuery.fn.extend = function() {
// 定义变量
// target 为目标对象
// options 为需要合并的对象
// i 为参数起始位置
// deep 默认为浅拷贝
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// 当第一个参数为布尔值时,
// 这个参数用于定义是否进行深拷贝
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
i = 2;
}
// 如果目标不是对象或函数,则重新定义为空对象
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// 当只传入一个参数时,
// 被扩展的对象就是当前调用者 this
if ( length === i ) {
target = this;
--i;
}
// 遍历需要扩展的对象
for ( ; i < length; i++ ) {
// 只处理非 null 和 undefined 的对象
if ( (options = arguments[i]) != null ) {
// 遍历对象中的属性
for ( name in options ) {
src = target[name];
copy = options[name];
// 防止循环引用
if ( target === copy ) {
continue;
}
// 如果开启深拷贝,并且当前属性是普通对象或数组
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 : {};
}
// 递归调用 extend 进行深度合并
target[name] = jQuery.extend(deep, clone, copy);
// 不处理 undefined
} else if ( copy !== undefined ) {
// 给目标对象增加属性或方法
target[name] = copy;
}
}
}
}
// 返回扩展后的对象
return target;
};弄懂 extend 的基本原理之后,再来看 jQuery 的插件开发、静态方法扩展和实例方法扩展,就会容易理解很多。
简单来说,jQuery.extend() 和 jQuery.fn.extend() 的核心代码是一样的,区别主要在于调用时 this 的指向不同。一个扩展 jQuery 本身,另一个扩展 jQuery 的原型对象,而对象合并和深拷贝,则是在这个基础上增加的功能。
评论0
欢迎分享你的看法,也欢迎补充不同的实践经验。