从extend函数看JavaScript的深度复制

  Javascript Pattern的Code Reuse Patterns中有一个小节叫做Inheritance by Copying Properties.仔细研究后发现其实这里提到的Copying Properties就是JS中的深度复制。

  先看一下Javascript Pattern中关于深度复制的实现:

function extendDeep(parent, child) {
    var i,
        toStr = Object.prototype.toString,
        astr = "[object Array]";
    child = child || {};
    for (i in parent) {
        if (parent.hasOwnProperty(i)) {
            if (typeof parent[i] === "object") {
                child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
                extendDeep(parent[i], child[i]);
            } else {
                child[i] = parent[i];
            }
        }
    }
    return child;
}

 

看上面的代码遇到的第一个问题就是关于Object.prototype.toString的应用,在ES5中关于Object.prototype.toString有下面的描述:

When the toString method is called, the following steps are taken:

  1. If the this value is undefined, return "[object Undefined]".
  2. If the this value is null, return "[object Null]".
  3. Let O be the result of calling ToObject passing the this value as the argument.
  4. Let class be the value of the [[Class]] internal property of O.
  5. Return the String value that is the result of concatenating the three Strings "[object "class, and "]"

通过上面的描述,我们不难发现,toString()完成的工作,除去this是undefined和null的情况,将首先调用ToObject方法,这个方法的返回值就是,this对应的Primitive object或者是this本身。接着又是[[Class]]的理解,在Javascript中所有的本地对象都有一个内部属性[[Class]],这个属性包含了一个ES定义的关于对象分类的字符值,这个字符值可能的值有以下几种:

  • "Object"
  • "Array"
  • "Function"
  • "Date"
  • "RegExp"
  • "String"
  • "Number"
  • "Boolean"
  • "Error" for error objects such as instances of ReferenceErrorTypeErrorSyntaxError,Error, etc
  • "Math" for the global Math object
  • "JSON" for the global JSON object defined on the ECMAScript 5th Ed. spec.
  • "Arguments" for the arguments object (also introduced on the ES5 spec.)
  • "null" (introduced just a couple of days ago in the ES5 errata)
  • "undefined"

所以通过上面的描述,不难得出结论:Object.prototype.toString.call(temptValue),返回的是一个字符串,这个字符串的值是

"[object "+this.[[Class]]+"]"

至于为什么不用instanceof 和constructor 检测array,具体详见http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/

接着说深度复制,因为在JavaScript中对象是被当做引用来进行传递的,所以如果仅仅通过复制的方式将一个对象覆盖另外一个对象,这样就会造成当child对象的一个属性改变时,父对象的对应属性也会发生改变。

理解了这一思路后,上面关于extendDeep的实现就很好理解了,它的最终的思想就是赋值的都不是对象,所以会有递归的调用和判断,直到不是数组或者是对象。

if (typeof parent[i] === "object") {
                child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
                extendDeep(parent[i], child[i]);
            } else {
                child[i] = parent[i];
            }

下面是jQuery中extend函数的实现

// Recurse if we're merging plain objects or arrays
                if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray(src) ? src : [];

                    } else {
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }

                    // Never move original objects, clone them
                    target[ name ] = jQuery.extend( deep, clone, copy );

                // Don't bring in undefined values
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }

 

posted @ 2012-08-04 15:16  shawnXiao  Views(4333)  Comments(0Edit  收藏  举报