对Object的extend是一个常用的功能。举一个例子,由于javascript 没有重载(overload),而且函数的参数类型是没有定义的,所以很多时候我们都传入一个对象来作为参数已方便控制。通常在函数里面给了参数对象的默认值,这个时候就需要通过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])
返回值--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本身就是一个对象,tel里面的homeTel和officeTel没有继承过去。
我的目标就是实现这种对子对象的子属性也一起复制(继承)的功能,不管他嵌套有多深。
首先我们看看这个方法的参数,有三个参数,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; //如果第二个参数的类型为未定义或者为布尔值 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.
为了安全起见,我们还需要判断一下,如果souce满足了上面的条件,但它不是Object对象,或者它是一个Function对象(这涉及到一些其他的问题),我们也没办法对其进行复制的。这个时候我们把souce设为空的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=Object.inherit = 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).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; };
Object.extend = function(destination, source) { // 一个静态方法表示继承, 目标对象将拥有源对象的所有属性和方法for (var property in source) { destination[property] = source[property]; // 利用动态语言的特性, 通过赋值动态添加属性与方法 } return destination; // 返回扩展后的对象 } Object.extend(Object, { inspect: function(object) { // 一个静态方法, 传入一个对象, 返回对象的字符串表示 try { if (object == undefined) return 'undefined'; // 处理undefined情况 if (object == null) return 'null'; // 处理null情况 // 如果对象定义了inspect方法, 则调用该方法返回, 否则返回对象的toString()值 return object.inspect ? object.inspect() : object.toString(); } catch (e) { if (e instanceof RangeError) return '...'; // 处理异常情况 throw e; } }, keys: function(object) { // 一个静态方法, 传入一个对象, 返回该对象中所有的属性, 构成数组返回 var keys = []; for (var property in object) keys.push(property); // 将每个属性压入到一个数组中 return keys; }, values: function(object) { // 一个静态方法, 传入一个对象, 返回该对象中所有属性所对应的值, 构成数组返回 var values = []; for (var property in object) values.push(object[property]); // 将每个属性的值压入到一个数组中 return values; }, clone: function(object) { // 一个静态方法, 传入一个对象, 克隆一个新对象并返回 return Object.extend({}, object); } });
var source = {id:1, name:'Jack Source'}, target = {name:'Jack Target', gender:1,tel:{homeTel:"158255",officeTel:"02112585"}}; var newObj1 = Object.extend(target, source);