深度探討-js深克隆

js深克隆一直是一個很奇妙的問題。也是面試中常問的一個問題。但是可能大多數(shù)人的解決方案都是有問題的。但是并不是解決方案本身的問題,是這個問題本身的問題。從一個角度來看,這個問題其實(shí)是無解的。但是換一個角度,這個問題已然有了最優(yōu)解。下面就來說一下這個問題的各種** 解決方案 **。

1.使用JSON

JSON.parse(JSON.stringify(obj))

看一個例子

obj={a:1,b:{b:1}}
JSON.parse(JSON.stringify(obj))//obj={a:1,b:{b:1}}

深克隆完畢。。是不是覺得很簡單,很驚艷,從某種程度來說,沒錯,這種方法是很好,而且如果是簡單結(jié)構(gòu)的話比jquery的extend效率要高10%-20%。但是問題出在哪里呢,JSON的深克隆不會克隆NAN,undefined這些,更別說function,Data了。所以又有了遞歸遍歷這種操作。

2.遞歸

//返回傳遞給他的任意對象的類
    function isClass(o) {
        return Object.prototype.toString.call(o).slice(8, -1);
    }

    //深度克隆
    function deepClone(obj) {
        var result, oClass = isClass(obj);
        //確定result的類型
        if (oClass === "Object") {
            result = {};
        } else if (oClass === "Array") {
            result = [];
        } else {
            return obj;
        }
        for (key in obj) {
            if (obj.hasOwnProperty(key)) { //原型鏈屬性不克隆
                var copy = obj[key];
                if (isClass(copy) === "Object") {
                    result[key] = arguments.callee(copy);//遞歸調(diào)用
                } else if (isClass(copy) === "Array") {
                    result[key] = arguments.callee(copy);
                } else {
                    result[key] = obj[key];
                }
            }
        }
        return result;
    }

突然就感覺解決了所有問題吧。數(shù)組,和對象的情況都考了到了呢。而且原型鏈上的繁雜東西也沒有帶出來。是不是覺得又完美了。
然而,打擊總是一直存在的,在已知數(shù)據(jù)結(jié)構(gòu)的情況下你可以這樣使用,但是,如果不知道就不行。比如。如果有環(huán)?然后就有了死循環(huán),無法避免的棧溢出報(bào)錯。。。
而且,原型鏈也沒有復(fù)制,從一定程度來說也不算深克隆。。那如果已知數(shù)據(jù)結(jié)構(gòu),還不如直接用for in循環(huán)來的快一些。。

所以說真正的深克隆需要考慮的問題有這些

1.JSON 克隆不支持函數(shù)、引用、undefined、Date、RegExp 等
2.遞歸克隆要考慮環(huán)
3.要考慮 等特殊對象的克隆方式
4.要不要克隆 proto,如果要克隆,就非常浪費(fèi)內(nèi)存;如果不克隆,就不是深克隆。

3.JQuery中較為完美的實(shí)現(xiàn)

仔細(xì)想想這些問題可能會爆炸。。。??纯磈query的那些大佬怎么實(shí)現(xiàn)的吧。。代碼挺長的,這里有一個相近的實(shí)現(xiàn)。 下面的代碼可以先跳過。

$ = function() {  
    var copyIsArray,  
        toString = Object.prototype.toString,  
        hasOwn = Object.prototype.hasOwnProperty;  
  
    class2type = {  
        '[object Boolean]' : 'boolean',  
        '[object Number]' : 'number',  
        '[object String]' : 'string',  
        '[object Function]' : 'function',  
        '[object Array]' : 'array',  
        '[object Date]' : 'date',  
        '[object RegExp]' : 'regExp',  
        '[object Object]' : 'object'  
    },  
  
    type = function(obj) {  
        return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";  
    },  
  
    isWindow = function(obj) {  
        return obj && typeof obj === "object" && "setInterval" in obj;  
    },  
  
    isArray = Array.isArray || function(obj) {  
        return type(obj) === "array";  
    },  
  
    isPlainObject = function(obj) {  
        if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {  
            return false;  
        }  
  
        if (obj.constructor && !hasOwn.call(obj, "constructor")  
                && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {  
            return false;  
        }  
  
        var key;  
        for (key in obj) {  
        }  
  
        return key === undefined || hasOwn.call(obj, key);  
    },  
  
    extend = function(deep, target, options) {  
        for (name in options) {  
            src = target[name];  
            copy = options[name];  
  
            if (target === copy) { continue; }  
  
            if (deep && copy  
                    && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {  
                if (copyIsArray) {  
                    copyIsArray = false;  
                    clone = src && isArray(src) ? src : [];  
  
                } else {  
                    clone = src && isPlainObject(src) ? src : {};  
                }  
  
                target[name] = extend(deep, clone, copy);  
            } else if (copy !== undefined) {  
                target[name] = copy;  
            }  
        }  
  
        return target;  
    };  
  
    return { extend : extend };  
}();  

與之前相比。多了這些有趣的代碼

if (target === copy) { continue; }  //避免了死循環(huán)

在isPlainObject函數(shù)中

if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {  
    return false;  
} 
//以下情況不復(fù)制
  1. 對象為undefined; 
  2. 轉(zhuǎn)為String時(shí)不是"[object Object]"; 
  3. obj是一個DOM元素; 
  4. obj是window。

之所以不對DOM元素和window進(jìn)行深復(fù)制,可能是因?yàn)樗鼈儼膶傩蕴嗔?;尤其是window對象,所有在全局域聲明的變量都會是其屬性,更不用說內(nèi)置的屬性了。

if (obj.constructor && !hasOwn.call(obj, "constructor")  
              && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {  
      return false;  
  }  
//如果對象具有構(gòu)造函數(shù),但卻不是自身的屬性,說明這個構(gòu)造函數(shù)是通過prototye繼承來的,這種情況也不進(jìn)行深復(fù)制。
var key;  
for (key in obj) {  
}  
return key === undefined || hasOwn.call(obj, key);  
// 這幾句代碼是用于檢查對象的屬性是否都是自身的,因?yàn)楸闅v對象屬性時(shí),
會先從自身的屬性開始遍歷,所以只需要檢查最后的屬性是否是自身的就可以了。

上面的代碼和和jQuery的實(shí)現(xiàn)就很相似。做到了,各種類型的判斷,死循環(huán)的避免,而對于復(fù)雜的原型拷貝問題,它做了部分淺復(fù)制(關(guān)于原型做深復(fù)制很不必要,而且浪費(fèi)大量時(shí)間和內(nèi)存)。大部分情況下是深拷貝,也是一種較為完美的解決方案了。

4.優(yōu)雅實(shí)現(xiàn)-immutable.js

深復(fù)制已經(jīng)解決到這個層面了,可以說在這個角度沒有更優(yōu)的解決方案了吧?;貧w到問題本身。我們?yōu)槭裁匆顝?fù)制這個對象。如果要求一樣,直接使用原來的對象不行嗎?真的不一樣的話,為什么不新建一個對象,為什么要用這么浪費(fèi)性能和內(nèi)存的深復(fù)制?可能你復(fù)制之后的對象的大部分屬性還是沒有用的。這就牽扯到了一個js庫。。。

在React出來的同時(shí)出了一個js庫-immutable.js,怎么說呢,它真的是一個很厲害的庫,也是一個很厲害的想法,但是react優(yōu)點(diǎn)遮擋了他的光輝吧。這也是一個react的官方推薦庫。一般它會和react一起使用,但是它單獨(dú)也可以使用,它是一種結(jié)構(gòu)復(fù)用的思想。也就是數(shù)據(jù)不可變,什么意思呢,就是用immutable建立的對象不會被改變,會產(chǎn)生一個新分支來共用它的結(jié)構(gòu),下圖這樣。

immutable.gif

具體了解的話來看一下這兩篇文章。
Immutable 詳解及 React 中實(shí)踐
官方文檔

const { Map } = require('immutable')
const map1 = Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1.get('b') // 2
map2.get('b') // 50

這種結(jié)構(gòu)復(fù)用的解決方案基本上已經(jīng)脫離了深復(fù)制的層面,不是從解決深復(fù)制,而是單純的不讓深復(fù)制產(chǎn)生。從根源上解決了這個問題,實(shí)現(xiàn)了時(shí)間復(fù)雜度和空間復(fù)雜度的雙優(yōu)化。但是對于已有的含復(fù)雜原型鏈的對象,可能它也沒有太好的解決方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容