Ext 源码笔记 Ext.apply Ext.Object.merge Ext.clone

 Ext.apply  &  Ext.Object.merge & Ext.clone

 前两天写Ext的时候,碰到对象引用的问题,本想Ext有自己的拷贝对象的方法,Ext.apply(),那就用呗~~ 然,问题依然存在啊。于是,猜想:Ext.apply不能拷贝深层对象,深层对象依然是引用。

先看源码:

  /**
     * 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  这是什么呢? 看源码:

   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方法。

 

下面我们再看下Ext.Object.merge

   /** @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(),

 /**
         * 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(),这个方法只拷贝源对象中存在,目标对象中不存在的属性。

       /**
         * 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复制对象的过程已然明了。花花再也不用担心对象引用的问题啦!

 

posted on 2013-09-16 15:10  花森  阅读(2486)  评论(0编辑  收藏  举报