javascript

位置:IT落伍者 >> javascript >> 浏览文章

jQuery.extend()的实现方式详解及实例


发布日期:2021年03月08日
 
jQuery.extend()的实现方式详解及实例
extend()函数是jQuery的基础函数之一作用是扩展现有的对象复制代码 代码如下:
<script type="text/javascript" src="jqueryjs"></script>
<script>
obj = { a : a b : b };
obj = {  x : { xxx : xxx yyy : yyy }  y : y };

$extend(true obj obj);

alert(objxxxx);  // 得到"xxx"

objxxxx = zzz;
alert(objxxxx);  // 得到"zzz"
alert(objxxxx);  // 得带"xxx"
</script>


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

浅复制的实现

如果仅仅需要实现浅复制可以采用类似下面的写法

复制代码 代码如下:
$ = {
extend : function(target options) {
for (name in options) {
target[name] = options[name];
}
return target;
}
};


也就是简单地将options中的属性复制到target中我们仍然可以用类似的代码进行测试但得到的结果有所不同(假设我们的js命名为“jqueryextendjs”)

复制代码 代码如下:
<script type="text/javascript" src="jqueryextendjs"></script>
<script>
obj = { a : a b : b };
obj = {  x : { xxx : xxx yyy : yyy }  y : y };
$extend(obj obj);
alert(objxxxx);  // 得到"xxx"
objxxxx = zzz;
alert(objxxxx);  // 得到"zzz"
alert(objxxxx);  // 得带"zzz"
</script>


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

深复制的实现

如果我们希望实现“深复制”当所复制的对象是数组或者对象时就应该递归调用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;
}
};


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

测试代码如下

复制代码 代码如下:
<script type="text/javascript" src="jqueryextendjs"></script>
<script>
obj = { a : a b : b };
obj = {  x : { xxx : xxx yyy : yyy }  y : y };
$extend(true obj obj);
alert(objxxxx);  // 得到"xxx"
objxxxx = zzz;
alert(objxxxx); // 得到"zzz"
alert(objxxxx); // 得到"xxx"
</script>


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

更完整的实现

下面的实现与jQuery中的extend()会更接近一些

复制代码 代码如下:
$ = function() {
var copyIsArray
toString = ObjectprototypetoString
hasOwn = ObjectprototypehasOwnProperty;

classtype = {
[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) : classtype[toStringcall(obj)] || "object";
}

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

isArray = ArrayisArray || function(obj) {
return type(obj) === "array";
}

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

if (objconstructor && !hasOwncall(obj "constructor")
&& !hasOwncall(objconstructorprototype "isPrototypeOf")) {
return false;
}

var key;
for (key in obj) {
}

return key === undefined || hasOwncall(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) : classtype[toStringcall(obj)] || "object";
}
isArray = ArrayisArray || function(obj) {
return type(obj) === "array";
}


如果浏览器有内置的ArrayisArray 实现就使用浏览器自身的实现方式否则将对象转为String看是否为"[object Array]"

最后逐句地看看isPlainObject的实现

复制代码 代码如下:
if (!obj || type(obj) !== "object" || objnodeType || isWindow(obj)) {
return false;
}


如果定义了objnodeType表示这是一个DOM元素这句代码表示以下四种情况不进行深复制
对象为undefined
转为String时不是"[object Object]"
obj是一个DOM元素
obj是window
之所以不对DOM元素和window进行深复制可能是因为它们包含的属性太多了尤其是window对象所有在全局域声明的变量都会是其属性更不用说内置的属性了

接下来是与构造函数相关的测试

复制代码 代码如下:
if (objconstructor && !hasOwncall(obj "constructor")
&& !hasOwncall(objconstructorprototype "isPrototypeOf")) {
return false;
}


如果对象具有构造函数但却不是自身的属性说明这个构造函数是通过prototye继承来的这种情况也不进行深复制这一点可以结合下面的代码结合进行理解

复制代码 代码如下:
var key;
for (key in obj) {
}
return key === undefined || hasOwncall(obj key);


这几句代码是用于检查对象的属性是否都是自身的因为遍历对象属性时会先从自身的属性开始遍历所以只需要检查最后的属性是否是自身的就可以了
这 说明如果对象是通过prototype方式继承了构造函数或者属性则不对该对象进行深复制这可能也是考虑到这类对象可能比较复杂为了避免引入不确定 的因素或者为复制大量属性而花费大量时间而进行的处理从函数名也可以看出来进行深复制的只有"PlainObject"
如果我们用如下代码进行测试

复制代码 代码如下:
<script type="text/javascript" src="jqueryjs"></script>
<script>
function O() {
thisyyy = yyy;
}

function X() {
thisxxx = xxx;
}

Xprototype = new O();

x = new X();

obj = { a : a b : b };
obj = { x : x };
$extend(true obj obj);

alert(objxyyy);  // 得到"xxx"
objxyyy = zzz;
alert(objxyyy);  // 得到"zzz"
</script>


可以看到这种情况是不进行深复制的
总之jQuery中的extend()的实现方式考虑了兼容浏览器的兼容避免性能过低和避免引入不可预料的错误等因素

               

上一篇:jquery中通过父级查找进行定位示例

下一篇:js操作iframe兼容各种主流浏览器示例代码