JavaScript Object.extend 实现深层对象合并

作者:小括号

时间:2026-10-25

浏览:160

本文分析jQuery内置extend方法在处理嵌套对象时的局限性,并提供一个自定义的Object.extend函数实现。通过判断参数类型与递归调用,该函数支持对任意深度的子对象进行属性复制,解决了浅层合并导致子对象丢失的问题,适用于需要完整继承复杂对象结构的场景。

文章目录

jQuery extend的局限性

在函数开发中,通常需要为参数对象设置默认值,并通过扩展机制将传入的参数覆盖到默认参数中。例如:


代码示例如下:


代码如下:

 
giant.ui.imageshow = function(options) {
this.opts = $.extend({}, giant.ui.imageshow.defaults, options);
} 
giant.ui.imageshow.defaults = {
id:"imageshow",
isAuto:true,
speed:3000
}; 

jQuery框架提供了一个名为extend的工具函数:


jQuery.extend(target,obj1,[objN])


该函数用于将一个或多个源对象扩展到一个目标对象,并返回被修改的目标对象。


它常用于简化继承操作。


Extend one object with one or more others, returning the original, modified, object.


This is a great utility for simple inheritance.


返回值类型为Object。


参数说明:


target (Object):目标对象,即被修改的对象。


object1 (Object):源对象,其属性将被合并到目标对象中。


objectN (Object):可选,其他待合并到目标对象的源对象。


然而,框架内置的extend方法存在明显缺陷,即无法继承对象内部的嵌套对象。举例说明:


代码示例如下:


代码如下:

 
var obj1 = {},
var obj2={name:"karry",email:"karry@a.com",tel:{homeTel:"158255",officeTel:"02112585"}}
obj1 = $.extend({},obj1 ,obj2 );

执行后,obj1仅包含name和email属性。由于tel本身是一个对象,其内部的homeTel和officeTel属性未能被继承。


我的目标是实现一种能够对子对象的子属性进行完整复制(继承)的功能,无论嵌套层级有多深。


自定义Object.extend实现

首先分析该方法参数,包含三个参数:target(目标对象)、source(源对象)和deep(布尔值,指示是否递归复制嵌套对象)。若deep为true,则继承所有层级;若为false,则与jQuery默认行为一致,不继承子对象。


代码实现如下:


代码如下:

 
Object.extend = function(target, /*optional*/source, /*optional*/deep) {}

我将第一个参数target设为必选,而source和deep设为可选。这带来一个问题:当只传入两个参数时,如何区分第二个参数是source还是deep?因此需要判断第二个参数的类型。


代码示例如下:


代码如下:

 
target = target || {}; //target默认为空对象
var sType = typeof source;
//如果第二个参数的类型为undefined或boolean
if( sType === 'undefined' || sType === 'boolean' ) {
deep = sType === 'boolean' ? source : false;
source = target; //将target赋值给source
target = this; //这里的this指向Object
} 

部分读者可能对最后两行代码存疑。我的处理逻辑是:如果target和source都存在且source不是布尔值,则将source的内容复制到target;否则,将target复制到Object对象。此时deep默认为false。


为确保安全,还需判断source是否满足条件且不是Object类型,或者它是Function对象(涉及其他问题),此时无法进行复制。因此,将source设为空Object,即不进行复制操作。


代码示例如下:


代码如下:

 
if( typeof source !== 'object' && Object.prototype.toString.call(source) !== '[object Function]' )
source = {};

注:Function对象在执行typeof操作时返回“object”,但无法在此方法中正确复制,因此必须排除。


接下来进行循环复制,利用递归实现。


代码示例如下:


代码如下:

 
var i=1,option;
//外层循环依次修改options,先设为target,后设为source
while(i <= 2) {
options = i === 1 ? target : source;
if( options != null ) {
//内层循环复制对应属性
for( var name in options ) {
var src = target[name], copy = options[name];
if(target === copy)
continue;
//如果deep设为true,且该属性是对象
if(deep && copy && typeof copy === 'object' && !copy.nodeType)
//递归调用
target[name] = this.extend(src ||(copy.length != null ? [] : {}), copy, deep);
else if(copy !== undefined)
target[name] = copy;
} 
} 
i++;
} 

此处利用递归方式依次复制对象内的嵌套对象,功能完成。完整代码如下:


代码示例如下:


代码如下:

 
/* 
* @param {Object} target 目标对象。
* @param {Object} source 源对象。
* @param {boolean} deep 是否复制(继承)对象中的对象。
* @returns {Object} 返回继承了source对象属性的新对象。
*/ 
Object.extend = function(target, /*optional*/source, /*optional*/deep) {
target = target || {};
var sType = typeof source, i = 1, options;
if( sType === 'undefined' || sType === 'boolean' ) {
deep = sType === 'boolean' ? source : false;
source = target;
target = this;
} 
if( typeof source !== 'object' && Object.prototype.toString.call(source) !== '[object Function]' )
source = {};
while(i <= 2) {
options = i === 1 ? target : source;
if( options != null ) {
for( var name in options ) {
var src = target[name], copy = options[name];
if(target === copy)
continue;
if(deep && copy && typeof copy === 'object' && !copy.nodeType)
target[name] = this.extend(src ||
(copy.length != null ? [] : {}), copy, deep);
else if(copy !== undefined)
target[name] = copy;
} 
} 
i++;
} 
return target;
}; 

使用示例

使用示例:


代码示例如下:


代码如下:

 
var source = {id:1, name:'Jack Source'}, target = {name:'Jack Target', gender:1,tel:{homeTel:"158255",officeTel:"02112585"}};
var newObj1 = Object.extend(target, source);

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章