前兩天寫Ext的時候,碰到對象引用的問題,本想Ext有本身的拷貝對象的方法,Ext.apply(),那就用唄~~ 然,問題依然存在啊。因而,猜測:Ext.apply不能拷貝深層對象,深層對象依然是引用。node
先看源碼:chrome
/** * Copies all the properties of config to the specified object. * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use * {@link Ext.Object#merge} instead. * @param {Object} object The receiver of the properties * @param {Object} config The source of the properties * @param {Object} [defaults] A different object that will also be applied for default values * @return {Object} returns obj */ Ext.apply = function(object, config, defaults) { if (defaults) { Ext.apply(object, defaults); } if (object && config && typeof config === 'object') { var i, j, k; for (i in config) { object[i] = config[i]; // 複製對象,這裏顯然是淺複製。 } if (enumerables) { for (j = enumerables.length; j--;) { k = enumerables[j]; if (config.hasOwnProperty(k)) { object[k] = config[k]; } } } } return object; };
嗯,果真,人家已經寫的很清楚了:若是不想引用原對象或數組,可使用 Ext.Object.merge方法(仍是看源碼好啊!!),一下就找到問題的根源了。數組
源碼裏面還有個新鮮的玩意兒:enumerables 這是什麼呢? 看源碼:app
enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor']; /** * An array containing extra enumerables for old browsers * @property {String[]} */ Ext.enumerables = enumerables;
'old browsers' 很明顯嘛,兼容ie6用的。可是若是還不明白爲何要兼容ie6,那就先用ie6測試一下吧,函數
var a = { years: [20013, 20012], toString: 'this year is 2013' } var b = {}; Ext.apply(b, a); if(b.hasOwnProperty('toString')){ alert(1); }else{ alert(2); }
// 在ie6下,彈出 2
// ie7,ie8,ie9 及 chrome firefox彈出的都是 1
// 即 在ie6下,對象包含的像 toString 這樣的原生屬性不會被拷貝,也不能被 for in 遍歷到
在ie6下,複製對象後,不會將enumerables列舉的這些屬性複製到目標對象中,所以這裏須要手動添加!(萬惡的ie6啊)。測試
題外話,hasOwnProperty() 這個方法只會檢查該對象自己的屬性,不會檢查該對象的原型鏈中是否具備該屬性。若是須要檢查一個對象的原型鏈上是否包含了另外一個對象,可使用isPrototypeOf方法。this
下面咱們再看下Ext.Object.mergespa
/** @param {Object} destination The object into which all subsequent objects are merged. * @param {Object...} object Any number of objects to merge into the destination. * @return {Object} merged The destination object with all passed objects merged in. */ merge: function(destination) { var i = 1, ln = arguments.length, mergeFn = ExtObject.merge, // ExtObject = Ext.Object 這裏指代 this cloneFn = Ext.clone, // 真正幹活的函數 object, key, value, sourceKey; for (; i < ln; i++) { object = arguments[i]; for (key in object) { value = object[key]; if (value && value.constructor === Object) { // 判斷是不是深層對象 sourceKey = destination[key];
// 判斷是複製仍是合併 if (sourceKey && sourceKey.constructor === Object) { mergeFn(sourceKey, value); // 合併 遞歸 } else { destination[key] = cloneFn(value); // 複製 } } else { destination[key] = value; } } } return destination; }
其實,看到這裏,才發現原來真正幹活的是Ext.clone(),firefox
/** * Clone simple variables including array, {}-like objects, DOM nodes and Date without keeping the old reference. * A reference for the object itself is returned if it's not a direct decendant of Object. For model cloning, * see {@link Ext.data.Model#copy Model.copy}. * * @param {Object} item The variable to clone * @return {Object} clone */ clone: function(item) { var type, i, j, k, clone, key; if (item === null || item === undefined) { return item; } // DOM nodes // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing // recursively if (item.nodeType && item.cloneNode) { return item.cloneNode(true); } type = toString.call(item); // 這裏 toString = Object.prototype.toString
// 這裏是一個細節的地方
// String.prototype.toString 和 Array.prototype.toString 都重寫了 Object.prototype.toString 的方法
// So, 這兩個實際上是不同的方法.
// String.prototype.toString.call('str') -> 'str'
// Array.prototype.toString.call('str') -> '[object String]'
// Array.prototype.toString.call([1,2]) -> '1,2'
// Object.prototype.toString.call('str') -> '[object String]'
// So 這個方法實際上是在變相的判斷數據類型 !!! 又是一個小技巧 MARK
// Date if (type === '[object Date]') { return new Date(item.getTime()); } // Array if (type === '[object Array]') { i = item.length; clone = []; while (i--) { clone[i] = Ext.clone(item[i]); } } // Object else if (type === '[object Object]' && item.constructor === Object) { clone = {}; for (key in item) { clone[key] = Ext.clone(item[key]); } if (enumerables) { for (j = enumerables.length; j--;) { k = enumerables[j]; clone[k] = item[k]; } } } return clone || item; }
Ext爲複製對象還提供了一個十分實用的函數 Ext.applyIf(),這個方法只拷貝源對象中存在,目標對象中不存在的屬性。prototype
/** * Copies all the properties of config to object if they don't already exist. * @param {Object} object The receiver of the properties * @param {Object} config The source of the properties * @return {Object} returns obj */ applyIf: function(object, config) { var property; if (object) { for (property in config) { if (object[property] === undefined) { //目標對象中不存在該屬性 object[property] = config[property]; } } } return object; }
嗯,到這裏,整個Ext複製對象的過程已然明瞭。花花不再用擔憂對象引用的問題啦!