jquery 的 extend 是 jQuery 中应用非常多的一个函数,今天我们一边看 jQuery 的 extend 的特性,一边实现一个 extend!
先来看看 extend 的功能,引用 jQuery 官网:
Merge the contents of two or more objects together into the first object.
翻译过来就是,合并两个或者更多的对象的内容到第一个对象中。
让我们看看 extend 的用法:
jQuery.extend( target [, object1 ] [, objectN ] )
第一个参数 target,表示要拓展的目标,我们就称它为目标对象吧。
后面的参数,都传入对象,内容都会复制到目标对象中,我们就称它们为待复制对象吧。
举个例子:
var obj1 = {
a: 1,
b: { b1: 1, b2: 2 }
};
var obj2 = {
b: { b1: 3, b3: 4 },
c: 3
};
var obj3 = {
d: 4
}
console.log($.extend(obj1, obj2, obj3));
// {
// a: 1,
// b: { b1: 3, b3: 4 },
// c: 3,
// d: 4
// }
当两个对象出现相同字段的时候,后者会覆盖前者,而不会进行深层次的覆盖。
结合着上篇写得 《JavaScript专题之深浅拷贝》,我们尝试着自己写一个 extend 函数:
// 第一版
function extend() {
var name, options, copy;
var length = arguments.length;
var i = 1;
var target = arguments[0];
for (; i < length; i++) {
options = arguments[i];
if (options != null) {
for (name in options) {
copy = options[name];
if (copy !== undefined){
target[name] = copy;
}
}
}
}
return target;
};
那如何进行深层次的复制呢?jQuery v1.1.4 加入了一个新的用法:
jQuery.extend( [deep], target, object1 [, objectN ] )
也就是说,函数的第一个参数可以传一个布尔值,如果为 true,我们就会进行深拷贝,false 依然当做浅拷贝,这个时候,target 就往后移动到第二个参数。
还是举这个例子:
var obj1 = {
a: 1,
b: { b1: 1, b2: 2 }
};
var obj2 = {
b: { b1: 3, b3: 4 },
c: 3
};
var obj3 = {
d: 4
}
console.log($.extend(true, obj1, obj2, obj3));
// {
// a: 1,
// b: { b1: 3, b2: 2, b3: 4 },
// c: 3,
// d: 4
// }
因为采用了深拷贝,会遍历到更深的层次进行添加和覆盖。
我们来实现深拷贝的功能,值得注意的是:
// 第二版
function extend() {
// 默认不进行深拷贝
var deep = false;
var name, options, src, copy;
var length = arguments.length;
// 记录要复制的对象的下标
var i = 1;
// 第一个参数不传布尔值的情况下,target默认是第一个参数
var target = arguments[0] || {};
// 如果第一个参数是布尔值,第二个参数是才是target
if (typeof target == 'boolean') {
deep = target;
target = arguments[i] || {};
i++;
}
// 如果target不是对象,我们是无法进行复制的,所以设为{}
if (typeof target !== 'object') {
target = {}
}
// 循环遍历要复制的对象们
for (; i < length; i++) {
// 获取当前对象
options = arguments[i];
// 要求不能为空 避免extend(a,,b)这种情况
if (options != null) {
for (name in options) {
// 目标属性值
src = target[name];
// 要复制的对象的属性值
copy = options[name];
if (deep && copy && typeof copy == 'object') {
// 递归调用
target[name] = extend(deep, src, copy);
}
else if (copy !== undefined){
target[name] = copy;
}
}
}
}
return target;
};
在实现上,核心的部分还是跟上篇实现的深浅拷贝函数一致,如果要复制的对象的属性值是一个对象,就递归调用 extend。不过 extend 的实现中,多了很多细节上的判断,比如第一个参数是否是布尔值,target 是否是一个对象,不传参数时的默认值等。
接下来,我们看几个 jQuery 的 extend 使用效果:
在我们的实现中,typeof target 必须等于 object,我们才会在这个 target 基础上进行拓展,然而我们用 typeof 判断一个函数时,会返回function,也就是说,我们无法在一个函数上进行拓展!
什么,我们还能在一个函数上进行拓展!!
当然啦,毕竟函数也是一种对象嘛,让我们看个例子:
function a() {}
a.target = 'b';
console.log(a.target); // b
实际上,在 underscore 的实现中,underscore 的各种方法便是挂在了函数上!
所以在这里我们还要判断是不是函数,这时候我们便可以使用《JavaScript专题之类型判断(上)》中写得 isFunction 函数
我们这样修改:
if (typeof target !== "object" && !isFunction(target)) {
target = {};
}
其实我们实现的方法有个小 bug ,不信我们写个 demo:
var obj1 = {
a: 1,
b: {
c: 2
}
}
var obj2 = {
b: {
c: [5],
}
}
var d = extend(true, obj1, obj2)
console.log(d);
我们预期会返回这样一个对象:
{
a: 1,
b: {
c: [5]
}
}
然而返回了这样一个对象:
{
a: 1,
b: {
c: {
0: 5
}
}
}
让我们细细分析为什么会导致这种情况:
首先我们在函数的开始写一个 console 函数比如:console.log(1),然后以上面这个 demo 为例,执行一下,我们会发现 1 打印了三次,这就是说 extend 函数执行了三遍,让我们捋一捋这三遍传入的参数:
第一遍执行到递归调用时:
var src = { c: 2 };
var copy = { c: [5]};
target[name] = extend(true, src, copy);
第二遍执行到递归调用时:
var src = 2;
var copy = [5];
target[name] = extend(true, src, copy);
第三遍进行最终的赋值,因为 src 是一个基本类型,我们默认使用一个空对象作为目标值,所以最终的结果就变成了对象的属性!
为了解决这个问题,我们需要对目标属性值和待复制对象的属性值进行判断:
判断目标属性值跟要复制的对象的属性值类型是否一致:
如果待复制对象属性值类型为数组,目标属性值类型不为数组的话,目标属性值就设为 []
如果待复制对象属性值类型为对象,目标属性值类型不为对象的话,目标属性值就设为 {}
结合着《JavaScript专题之类型判断(下)》中的 isPlainObject 函数,我们可以对类型进行更细致的划分:
var clone, copyIsArray;
...
if (deep && copy && (isPlainObject(copy) ||
(copyIsArray = Array.isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && Array.isArray(src) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
}
target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
实际上,我们还可能遇到一个循环引用的问题,举个例子:
var a = {name : b};
var b = {name : a}
var c = extend(a, b);
console.log(c);
我们会得到一个可以无限展开的对象,类似于这样:
为了避免这个问题,我们需要判断要复制的对象属性是否等于 target,如果等于,我们就跳过:
...
src = target[name];
copy = options[name];
if (target === copy) {
continue;
}
...
如果加上这句,结果就会是:
{name: undefined}
// isPlainObject 函数来自于 [JavaScript专题之类型判断(下) ](https://github.com/mqyqingfeng/Blog/issues/30)
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
function isPlainObject(obj) {
var proto, Ctor;
if (!obj || toString.call(obj) !== "[object Object]") {
return false;
}
proto = Object.getPrototypeOf(obj);
if (!proto) {
return true;
}
Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
return typeof Ctor === "function" && hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object);
}
function extend() {
// 默认不进行深拷贝
var deep = false;
var name, options, src, copy, clone, copyIsArray;
var length = arguments.length;
// 记录要复制的对象的下标
var i = 1;
// 第一个参数不传布尔值的情况下,target 默认是第一个参数
var target = arguments[0] || {};
// 如果第一个参数是布尔值,第二个参数是 target
if (typeof target == 'boolean') {
deep = target;
target = arguments[i] || {};
i++;
}
// 如果target不是对象,我们是无法进行复制的,所以设为 {}
if (typeof target !== "object" && !isFunction(target)) {
target = {};
}
// 循环遍历要复制的对象们
for (; i < length; i++) {
// 获取当前对象
options = arguments[i];
// 要求不能为空 避免 extend(a,,b) 这种情况
if (options != null) {
for (name in options) {
// 目标属性值
src = target[name];
// 要复制的对象的属性值
copy = options[name];
// 解决循环引用
if (target === copy) {
continue;
}
// 要递归的对象必须是 plainObject 或者数组
if (deep && copy && (isPlainObject(copy) ||
(copyIsArray = Array.isArray(copy)))) {
// 要复制的对象属性值类型需要与目标属性值相同
if (copyIsArray) {
copyIsArray = false;
clone = src && Array.isArray(src) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
}
target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}
return target;
};
如果觉得看明白了上面的代码,想想下面两个 demo 的结果:
var a = extend(true, [4, 5, 6, 7, 8, 9], [1, 2, 3]);
console.log(a) // ???
var obj1 = {
value: {
3: 1
}
}
var obj2 = {
value: [5, 6, 7],
}
var b = extend(true, obj1, obj2) // ???
var c = extend(true, obj2, obj1) // ???
原文来自:https://github.com/mqyqingfeng/Blog/issues/33
在网络上也时不时会看到,“是时候和jQuery说拜拜了”,最著名的莫过于在2013年的这篇文章You Might Not Need jQuery。
15个jQuery小技巧:返回顶部按钮,预加载图像,检查图像是否加载,自动修复破坏的图像,悬停切换类,禁用输入字段,停止加载链接,淡入/滑动切换,简单的手风琴...
jquery插件是用来扩展jquery对象的一种方法,它的使用方法是通过jquery对象$来调用。其中Jquery插件开发一共有三种方式:$.extend(),$.fn,$.widget()
在JQuery中,可以使用trigger()方法完成模拟操作,trigger()方法不仅能触发浏览器支持的具有相同名称的事件,也可以触发自定义名称的事件。rigger(type[,data])方法有两个参数
在高版本的jquery引入prop方法后,什么时候该用prop?什么时候用attr?对于HTML元素本身就带有的固有属性,在处理时,使用prop方法。对于HTML元素我们自己自定义的DOM属性,在处理时,使用attr方法。
jquery是对js语言的封装、扩展,实现了对浏览器的兼容,使用jquery能让操作更方便简洁,这篇文章主要讲解原生js中Dom对象和jquery对象的相互转换。
在使用jquery.pagination.js插件的时候,会出现pagination is not a function的错误,这是什么原因导致的呢?这里为大家整理一下,请对比自己的代码参考!
整理一些简单技巧的集合,帮你提升 jQuery 技能,你可以直接拿来使用,下面内容包括:禁止右键点击、隐藏搜索文本框文字、隐藏搜索文本框文字、在新窗口中打开链接、检测浏览器...
jQuery提供了JS未能提供的动画效果,利用jQuery的动画效果,可以极大的简化JS动画部分的逻辑,包括:滑入滑出动画、淡入淡出动画、显示隐藏动画、停止动画、自定义动画
在网页的实际应用中,需要根据不同的条件来改变元素的CSS样式,通过动态的给元素添加删除一个CSS类可以实现此功能,下面通过实例来介绍一下如何实现此种功能。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!