从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:
- If the this value is undefined, return
"[object Undefined]"
. - If the this value is null, return
"[object Null]"
. - Let O be the result of calling ToObject passing the this value as the argument.
- Let class be the value of the [[Class]] internal property of O.
- 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 ofReferenceError
,TypeError
,SyntaxError
,Error
, etc"Math"
for the globalMath
object"JSON"
for the global JSON object defined on the ECMAScript 5th Ed. spec."Arguments"
for thearguments
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; }