用法
jQuery.extend([deep], target, object1, [objectN])
概述
用一个或多个其他对象来扩展一个对象,返回被扩展的对象。
1、如果不指定target,则给jQuery本身进行扩展。这有助于插件作者为jQuery增加新方法和属性。
2、如果第一个参数deep设置为true,则jQuery返回一个深层次的副本,递归地复制找到的任何对象,及把target
之后的对象参数深拷贝到target
中。
3、如果第一个参数deep设置为false,则jQuery返回一个副本会与原对象共享结构,把target
之后的对象参数浅拷贝到target
中。
4、未定义的属性将不会被复制,然而从对象的原型继承的属性将会被复制。
代码解读
下面我们将通过源码解析,学习一下优秀的框架的写法。下面的代码是jquery-3.4.1版本。把extend方法单独拿出来学习一下
function extend() {
//源对象指的是把自己的值赋给别人的对象;目标对象指的是被源对象赋值的对象
var options, //指向源对象
name, //表示某个源对象的某个属性名
src, //表示目标对象的某个属性的原始值
copy, //表示某个源对象的某个属性的值
copyIsArray, //指示变量 copy 是否是数组
clone, //表示深度复制时原始值的修正值
target = arguments[0] || {}, //指向目标对象,申明时先临时用第一个参数值
i = 1, //表示源对象的起始下标,申明时先临时用第二个参数值
length = arguments.length, //表示参数的个数,用于修正变量 target
deep = false; //指示是否执行深度复制,默认为 false
console.log("target:", target);
// 处理深拷贝的情况
if (typeof target === "boolean") {
deep = target; //设置deep变量,确定是深拷贝还是浅拷贝
//获取深拷贝标记之后的参数(及目标对象)
target = arguments[i] || {};
console.log("deep:", target);
i++; //源对象的起始下标设为2(即从第三个参数开始为源对象)
}
//到此,可以总结出:extend三个参数:[深拷贝标记,目标对象,源对象]
// 处理目标源对象是字符串或者其他类型的情况(可能是深拷贝)
if (typeof target !== "object" && !isFunction(target)) {
target = {};
}
// 如果只传递一个不是boolean类型的参数,则扩展jQuery本身
if (i === length) {
target = this;
i--;
}
// 拷贝的核心部分代码,递归深拷贝
for (; i < length; i++) {
// 只处理非null和非未定义的值
if ((options = arguments[i]) != null) {
//options就是源对象
//遍历源对象的属性名
for (name in options) {
//获取源对象上,属性名对应的属性值
copy = options[name];
//防止对象原型的污染
//为了避免深度遍历时死循环,因此不会覆盖目标对象的同名属性。防止无限循环
if (name === "__proto__" || target === copy) {
continue;
}
// 递归合并普通对象或数组
if (
deep &&
copy &&
(isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))
) {
//获取目标对象上相同属性的值
src = target[name];
if (copyIsArray && !Array.isArray(src)) {
// 源对象的属性值是数组,目标对象上相同属性的值不是数组,则创建数组作为该属性的目标数组
clone = [];
} else if (!copyIsArray && !isPlainObject(src)) {
// 源对象的属性值是对象,目标对象上相同属性的值不是对象,则创建对象作为该属性的目标对象
clone = {};
} else {
//源对象的属性值和目标对象上相同属性的值的类型相同(及都是数组或者都是对象)
clone = src;
}
copyIsArray = false;
//递归地拷贝
target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) {
//属性是原始类型,直接把源对象的属性,赋给目标对象
target[name] = copy;
}
}
}
}
// Return the modified object
return target;
}
源码里面都加了注释,阅读起来几乎没有难度,先花几分钟好好阅读一下,基本上面的概述中的情况都能理解了。
一些校验的方法这里就不累赘了,自己找了一源码看一下,无非是判断是对象还是函数的。虽然简单,但是JQuery写的还是很不错的,值的参考
现在我们就结合源码看看概述中,我们设置的参数不同,产生的不同结果吧。
情况一:不指定target
var settings = { mTestValidate: false, mTestLimit: 5, mTestName: "foo" };
extend(settings);
console.log(window.mTestValidate, window.mTestLimit, window.mTestName); //false 5 "foo"
源码中,target
赋值为第一个参数deep
,然后进行了类型校验,最后判断i=1
和参数个数进行比较,发现只有一个参数,所以target
赋值为this
。(如果在JQuery.js中,this
为JQuery)。由于deep =false
,所以是浅拷贝。
上面的实例由于是把extend
方法独立出来,在window
的词法环境下调用,所以this
为window
,所以settings
被扩展到了window
上。
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;
// 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" && !isFunction( target ) ) {
target = {};
}
// Extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
}
情况二:deep设置为true
var settings = { mTestValidate: false, mTestLimit: 5, mTestName: "foo" };
var options = { mTestValidate: true, mTestName: "bar" };
extend(true, options, settings);
console.log(options);//{mTestValidate: false, mTestName: "foo", mTestLimit: 5}
源码中,target
赋值为第一个参数deep
,然后进行了类型校验,为boolean
类型,则 deep = target=true
。所以进入递归拷贝对象属性
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
( copyIsArray = Array.isArray( copy ) ) ) ) {
src = target[ name ];
// Ensure proper type for the source value
if ( copyIsArray && !Array.isArray( src ) ) {
clone = [];
} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
clone = {};
} else {
clone = src;
}
copyIsArray = false;
// 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;
}
修改被扩展对象的属性,不影响原来对象(深拷贝)
var settings = {
mTestValidate: false,
mTestLimit: 5,
mTestName: "foo",
ages: [1, 2, 3]
};
var options = { mTestValidate: true, mTestName: "bar" };
options = extend(true, options, settings);
options.ages[3] = 4;
console.log(settings);
console.log(options);

情况三:deep设置为false
var settings = {
mTestValidate: false,
mTestLimit: 5,
mTestName: "foo",
ages: [1, 2, 3]
};
var options = { mTestValidate: true, mTestName: "bar" };
options = extend(false, options, settings);
console.log(settings);
console.log(options);
源码中,target
赋值为第一个参数deep
,然后进行了类型校验,为boolean
类型,则 deep =false;target = {}
。所以进直接把源对象的属性,赋给目标对象,进行浅拷贝。

修改属性,都改变,浅拷贝。(不理解浅拷贝和深拷贝的自己找资料吧,以后有空写个文章介绍一下)
options.ages[3] = 4;
console.log(settings);
console.log(options);

网友评论