JavaScript 實現深拷貝
寫該系列的起因是在網上遇到一套比較不錯的應試題目,涉及到了前端的各個角落,以及模組化,HTTP,後端等。題目在此 前端題目
第一題,就來討論一下關於 JS 實現深拷貝的問題。
- var obj = { a: 1 };
- var obj2 = obj;
- obj2.a = 3;
- obj.a; // 3
這是簡單的對象引用,這僅僅是將對象的引用地址簡單的複製了一份給予變數 obj2,而並不是將真正的對象複製了一份,原對象依舊是只有一個。影響則為,當我修改 obj2 內部的屬性或者添加新屬性時會影響 obj ,因為兩個變數的值為同一個對象的地址引用,即兩者指向的是同一個對象。
這就是所謂深拷貝的意義所在,不是拷貝引用地址,而是實實在在的複製一份新對象。而目前標準中還未提供一個類似的原生方法(主要是由於需求不大,JS 本身已經可以處理的很好了)。所以對於深拷貝,主要遇到的問題就是對於數組,對象以及方法的複製。
對象:一般來說不考慮原型鏈上對象的屬性,均是使用 for in 遍曆並且使用,由於瀏覽器的五花八門,還需要使用 hasOwnPrototype 來進行過濾下。當然也需要考慮某屬性值為 對象,數組,函數等情況。
數組:對於數組來說,也可以如對象一樣 for in 迴圈遍曆,不過通常情況是使用長度進行迴圈,對空數組進行不斷的 push。
函數:對於一般庫來說,一些深拷貝函數基本都不進行處理,實在需要處理的可以調用函數的 toString,再使用 eval 或者 Function 進行處理,得到新的函數。
然後上個執行個體吧,是自己正在寫的庫中的簡單的一個拷貝方法 mix,由於庫的原因擴充了一些功能以及對於方法不做複製處理,使用外部的一個方法 type,是用來判斷區分類型,如 array object function。
- /** 對象擴充 mix
- *
- * @method mix 不擴充原型屬性
- * @param {obj} receiver 可選 擴充的目標對象 如果無 則擴充到外圍對象
- * @param {obj} obj 必選 要擴充到目標對象的對象資料
- * @param {boolean} ride 可選 主要是標識是否覆蓋原有對象屬性 預設為true
- * @param {boolean} deep 可選 主要是標識是否需要簡單的深度拷貝 預設為false
- *
- * @return {Object} 返回目標對象
- *
- */
- varhasOwn = Object.prototype.hasOwnProperty;
- function mix(receiver, obj){
- var args = [].slice.call(arguments), key, i = 1,deep, ride, value, valueType;
- if( typeof args[args.length-2] === "boolean" ){
- deep = args.pop();
- ride = args.pop();
- }else{
- ride = (typeof args[args.length-1] === "boolean")?args.pop():true;
- deep = false;
- if(args.length < 2){
- receiver = ( this !== global ) ? this : {};
- if( args.length === 0 ){
- return receiver;
- }
- }
- }
- while( obj = args[ i++ ] ){
- for( key in obj ){
- if( hasOwn.call(obj, key) ){
- if( ride ||!(key in receiver) ){
- value = obj[key];
- valueType = type(value);
- if( deep && ( valueType==="object")){
- receiver[key]={};
- mix(receiver[key], value, ride, deep);
- }else if( deep && ( valueType==="array" )){
- receiver[key]=[];
- mix(receiver[key], value, ride, deep);
- }else{
- receiver[key] = obj[key];
- }
- }
- }
- }
- }
- return receiver;
- }
對於 type 函數,原始碼如下
- // 類型判定對象
- var class2type = {
- "[objectHTMLDocument]" : "document",
- "[objectHTMLCollection]" : "nodeList",
- "[objectStaticNodeList]" : "nodeList",
- "[objectIXMLDOMNodeList]" : "nodeList",
- "null" : "null",
- "NaN" : "NaN",
- "undefined" : "undefined"
- };
- "Boolean, Number, String, Function, Array, Date, RegExp, Document, Arguments, NodeList"
- .replace(/[^, ]+/g, function( type ){
- class2type["[object " + type + "]"] = type.toLowerCase();
- } );
- // 類型判定
- function type( obj, isType ){
- var key = ((obj == null || obj !== obj ) ? obj + "" : Object.prototype.toString.call( obj )),
- result;
- if( typeof(result = class2type[ key ]) !== "string" ){
- if( obj.nodeType === 9 ){
- result = class2type["Document"];
- }else if( obj.item && typeof obj.length === "number" ){
- result = class2type["NodeList"];
- }else{
- result = key.slice(8, -1);
- }
- }
- if( isType ){
- return result === isType.toLowerCase;
- }
- return result;
- }
這就是簡單的對於 JS 實現深拷貝的例子,有什麼錯誤,歡迎指出。