`
裴小星
  • 浏览: 264990 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
8ccf5db2-0d60-335f-a337-3c30d2feabdb
Java NIO翻译
浏览量:27804
F3e939f0-dc16-3d6e-8c0b-3315c810fb91
PureJS开发过程详解
浏览量:74004
07a6d496-dc19-3c71-92cf-92edb5203cef
MongoDB Java ...
浏览量:62903
社区版块
存档分类
最新评论

jQuery.extend()的实现方式

阅读更多
jQuery.extend()的实现方式

jQuery中的extend()

  extend()函数是jQuery的基础函数之一,作用是扩展现有的对象。例如下面的代码:
<script type="text/javascript" src="jquery-1.5.2.js"></script>
<script>
obj1 = { a : 'a', b : 'b' };
obj2 = {  x : { xxx : 'xxx', yyy : 'yyy' },  y : 'y' };

$.extend(true, obj1, obj2);

alert(obj1.x.xxx);  // 得到"xxx"

obj2.x.xxx = 'zzz';
alert(obj2.x.xxx);  // 得到"zzz"
alert(obj1.x.xxx);  // 得带"xxx"
</script>

  $.extend(true, obj1, obj2)表示以obj2中的属性扩展对象obj1,第一个参数设为true表示深复制。
  虽然obj1中原来没有"x"属性,但经过扩展后,obj1不但具有了"x"属性,而且对obj2中的"x"属性的修改也不会影响到obj1中"x"属性的值,这就是所谓的“深复制”了。

浅复制的实现

  如果仅仅需要实现浅复制,可以采用类似下面的写法:
$ = {
     extend : function(target, options) {
        for (name in options) {
            target[name] = options[name];
        }
        return target;
    }
};

  也就是简单地将options中的属性复制到target中。我们仍然可以用类似的代码进行测试,但得到的结果有所不同(假设我们的js命名为“jquery-extend.js”):
<script type="text/javascript" src="jquery-extend.js"></script>
<script>
obj1 = { a : 'a', b : 'b' };
obj2 = {  x : { xxx : 'xxx', yyy : 'yyy' },  y : 'y' };

$.extend(obj1, obj2);

alert(obj1.x.xxx);  // 得到"xxx"

obj2.x.xxx = 'zzz';
alert(obj2.x.xxx);  // 得到"zzz"
alert(obj1.x.xxx);  // 得带"zzz"
</script>

  obj1中具有了"x"属性,但这个属性是一个对象,对obj2中的"x"的修改也会影响到obj1,这可能会带来难以发现的错误。

深复制的实现

  如果我们希望实现“深复制”,当所复制的对象是数组或者对象时,就应该递归调用extend。如下代码是“深复制”的简单实现:
$ = {
	extend : function(deep, target, options) {
		for (name in options) {
			copy = options[name];
			if (deep && copy instanceof Array) {
                target[name] = $.extend(deep, [], copy);
            } else if (deep && copy instanceof Object) {
                target[name] = $.extend(deep, {}, copy);
			} else {
				target[name] = options[name];
			}
		}
		return target;
	}
};

  具体分为三种情况:
  1. 属性是数组时,则将target[name]初始化为空数组,然后递归调用extend;
  2. 属性是对象时,则将target[name]初始化为空对象,然后递归调用extend;
  3. 否则,直接复制属性。

  测试代码如下:
<script type="text/javascript" src="jquery-extend.js"></script>
<script>
obj1 = { a : 'a', b : 'b' };
obj2 = {  x : { xxx : 'xxx', yyy : 'yyy' },  y : 'y' };
$.extend(true, obj1, obj2);
alert(obj1.x.xxx);  // 得到"xxx"
obj2.x.xxx = 'zzz';
alert(obj2.x.xxx); // 得到"zzz"
alert(obj1.x.xxx); // 得到"xxx"
</script>

  现在如果指定为深复制的话,对obj2的修改将不会对obj1产生影响了;不过这个代码还存在一些问题,比如“instanceof Array”在IE5中可能存在不兼容的情况。jQuery中的实现实际上会更复杂一些。

更完整的实现

  下面的实现与jQuery中的extend()会更接近一些:
$ = function() {
    var copyIsArray,
        toString = Object.prototype.toString,
        hasOwn = Object.prototype.hasOwnProperty;

    class2type = {
        '[object Boolean]' : 'boolean',
        '[object Number]' : 'number',
        '[object String]' : 'string',
        '[object Function]' : 'function',
        '[object Array]' : 'array',
        '[object Date]' : 'date',
        '[object RegExp]' : 'regExp',
        '[object Object]' : 'object'
    },

    type = function(obj) {
        return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";
    },

    isWindow = function(obj) {
        return obj && typeof obj === "object" && "setInterval" in obj;
    },

    isArray = Array.isArray || function(obj) {
        return type(obj) === "array";
    },

    isPlainObject = function(obj) {
        if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
            return false;
        }

        if (obj.constructor && !hasOwn.call(obj, "constructor")
                && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
            return false;
        }

        var key;
        for (key in obj) {
        }

        return key === undefined || hasOwn.call(obj, key);
    },

    extend = function(deep, target, options) {
        for (name in options) {
            src = target[name];
            copy = options[name];

            if (target === copy) { continue; }

            if (deep && copy
                    && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
                if (copyIsArray) {
                    copyIsArray = false;
                    clone = src && isArray(src) ? src : [];

                } else {
                    clone = src && isPlainObject(src) ? src : {};
                }

                target[name] = extend(deep, clone, copy);
            } else if (copy !== undefined) {
                target[name] = copy;
            }
        }

        return target;
    };

    return { extend : extend };
}();

  首先是 $ =  function(){...}();这种写法,可以理解为与下面的写法类似:
func = function(){...};
$ =  func();
  也就是立即执行函数,并将结果赋给$。这种写法可以利用function来管理作用域,避免局部变量或局部函数影响全局域。另外,我们只希望使用者调用$.extend(),而将内部实现的函数隐藏,因此最终返回的对象中只包含extend:
    return { extend : extend };

  接下来,我们看看extend函数与之前的区别,首先是多了这句话:
  if (target === copy) { continue; }
  这是为了避免无限循环,要复制的属性copy与target相同的话,也就是将“自己”复制为“自己的属性”,可能导致不可预料的循环。

  然后是判断对象是否为数组的方式:
   type = function(obj) {
        return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";
   },
   isArray = Array.isArray || function(obj) {
        return type(obj) === "array";
    }
  如果浏览器有内置的Array.isArray 实现,就使用浏览器自身的实现方式,否则将对象转为String,看是否为"[object Array]"。

   最后逐句地看看isPlainObject的实现:
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
    return false;
}
  如果定义了obj.nodeType,表示这是一个DOM元素;这句代码表示以下四种情况不进行深复制:
  1. 对象为undefined;
  2. 转为String时不是"[object Object]";
  3. obj是一个DOM元素;
  4. obj是window。
  之所以不对DOM元素和window进行深复制,可能是因为它们包含的属性太多了;尤其是window对象,所有在全局域声明的变量都会是其属性,更不用说内置的属性了。

  接下来是与构造函数相关的测试:
  if (obj.constructor && !hasOwn.call(obj, "constructor")
                && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
        return false;
    }
  如果对象具有构造函数,但却不是自身的属性,说明这个构造函数是通过prototye继承来的,这种情况也不进行深复制。这一点可以结合下面的代码结合进行理解:
        var key;
        for (key in obj) {
        }

        return key === undefined || hasOwn.call(obj, key);
   这几句代码是用于检查对象的属性是否都是自身的,因为遍历对象属性时,会先从自身的属性开始遍历,所以只需要检查最后的属性是否是自身的就可以了。

  这说明如果对象是通过prototype方式继承了构造函数或者属性,则不对该对象进行深复制;这可能也是考虑到这类对象可能比较复杂,为了避免引入不确定的因素或者为复制大量属性而花费大量时间而进行的处理,从函数名也可以看出来,进行深复制的只有"PlainObject"。
  如果我们用如下代码进行测试:
<script type="text/javascript" src="jquery-1.5.2.js"></script>
<script>
function O() {
	this.yyy = 'yyy';
}

function X() {
	this.xxx = 'xxx';
}

X.prototype = new O();

x = new X();

obj1 = { a : 'a', b : 'b' };
obj2 = { x : x };
$.extend(true, obj1, obj2);

alert(obj1.x.yyy);  // 得到"xxx"
obj2.x.yyy = 'zzz';
alert(obj1.x.yyy);  // 得到"zzz"
</script>
  可以看到,这种情况是不进行深复制的。

  总之,jQuery中的extend()的实现方式,考虑了兼容浏览器的兼容,避免性能过低,和避免引入不可预料的错误等因素。
5
4
分享到:
评论
1 楼 踏月流星 2012-09-25  
第一行的运行结果不对,应该是'yyy'

相关推荐

    原生js实现jquery $.extend方法

    原生js实现jquery $.extend方法 通过遍历对象属性来实现

    jQuery:jQuery.extend函数详解

    可以通过向`jQuery.extend`添加一个布尔值参数来实现深度拷贝: ```javascript jQuery.extend(boolean, dest, src1, src2, src3, ...); ``` 其中,`boolean`参数为`true`时,将执行深度拷贝。例如: ```...

    jQuery.extend

    **jQuery.extend** jQuery.extend是jQuery库中的一个非常重要的方法,用于合并两个或更多的对象属性到一个目标对象中。这个功能在开发过程中非常实用,尤其是在处理配置选项、扩展插件功能或者进行对象拷贝时。本篇...

    jQuery.js中继承的实现.pdf

    jQuery.js库提供了一种优雅的解决方案,通过`jQuery.extend()`方法实现了对象的继承功能。这个方法在jQuery中扮演着至关重要的角色,它不仅用于合并对象,还用于实现类之间的继承。 `jQuery.extend()`方法的基本...

    jquery $.fn.extend

    通过这种方式,我们能够构建自己的事件系统,使其与jQuery的事件处理机制无缝集成。 ### 总结 `$.fn.extend`是jQuery的核心扩展机制,它使得开发者可以轻松地为jQuery添加新的功能,提高代码复用性和可维护性。...

    jquery.media.js 插件实现在线预览PDF文件

    `jquery.media.js`通过集成Adobe Flash或HTML5的`&lt;object&gt;`和`&lt;embed&gt;`元素来实现这一功能,根据用户的浏览器支持情况选择最合适的预览方式。 3. **HTML5 `&lt;embed&gt;` 标签**:在HTML5中,`&lt;embed&gt;`标签用于嵌入外部...

    Jquery实现$.fn.extend和$.extend函数_.docx

    实现方式与`$.fn.extend`类似,但`this`的上下文不同: ```javascript $.extend = function (obj) { if (isObj(obj)) { for (var i in obj) { this[i] = obj[i]; } } } ``` 在`$.extend`中,`this`代表`$`...

    Jquery.json.js

    **jQuery与JSON简介** jQuery是一个高效、简洁且易用的JavaScript库,它极大地简化了JavaScript编程,使得DOM操作、...同时,理解jQuery与JSON的交互方式对于Web开发人员来说至关重要,能够提升开发效率和代码质量。

    jQuery.extend()、jQuery.fn.extend()扩展方法示例详解

    这两个方法允许开发者在不修改原有jQuery代码的情况下,向jQuery类和jQuery对象中添加自定义的方法和属性,实现功能的扩展与定制。以下是对这两个方法的详细解释以及它们的具体应用。 ### jQuery.extend() jQuery....

    开发插件的两个方法jquery.fn.extend与jquery.extend

    在开发jQuery插件时,我们通常需要借助jQuery提供的方法来扩展其功能。具体来说,有两类重要的方法可以用来开发插件:jQuery....这样,只要通过jQuery选择器选中对应的元素并调用新添加的方法,就能实现预期的效果。

    jquery.validate.js表单验证.pdf

    综上所述,jquery.validate.js提供了一个非常便捷的方式来实现表单验证,通过引入相应的JS文件,并设置相应的验证规则和自定义信息,就可以轻松完成表单验证的任务。它不仅提高了开发效率,也增强了用户填写表单时的...

    jQuery.validate.js

    jQuery.validate.js是jQuery库的一个插件,专为实现表单验证提供了强大的支持。这个插件以其易用性、灵活性和丰富的功能,深受开发者喜爱。 一、jQuery.validate.js基础 jQuery.validate.js的核心是通过添加自定义...

    jquery.treegrid.extension.js

    扩展jquery.treegrid.extension.js,实现jquery-treegrid的懒加载,结合jquery.cookie.js来实现缓存,减缓页面大量数据加载

    jQuery.js执行过程分析

    通过这种方式,所有由jQuery构造的实例都可以访问这些方法。 4. **静态方法扩展**: `jQuery.extend`是一个静态方法,用于扩展jQuery自身的功能。这使得开发人员可以在不修改源代码的情况下,为jQuery添加自定义...

    jQuery学习笔记之jQuery.extend(),jQuery.fn.extend()分析

    在实现中,`jQuery.extend()` 和 `jQuery.fn.extend()` 的内部工作原理类似,它们都会处理不定数量的参数,并进行属性的合并。在处理过程中,有一些关键细节需要注意,比如: - 如果第一个参数是布尔值,那么它将被...

    jquery.metadata.js和jquery.validate.js

    4. 可以自定义错误消息和显示方式,以提供更好的用户体验: ```javascript $.extend($.validator.messages, { required: "这是必填字段。", email: "请输入有效的电子邮件地址。", }); ``` 这两个插件的结合...

    浅谈jquery.fn.extend与jquery.extend区别

    jQuery库中,有两个扩展方法:`$.extend()` 和 `$.fn.extend()`,这两个方法在用途和实现上有明显的区别。理解它们之间的不同,对于深入使用jQuery编写高质量代码至关重要。 `$.extend()` 方法用于扩展jQuery库本身...

Global site tag (gtag.js) - Google Analytics