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; }