成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

深入剖析 JavaScript 的深復(fù)制

gclove / 2699人閱讀

摘要:的不能算作深復(fù)制,但它至少比直接賦值來(lái)得深一些,它創(chuàng)建了一個(gè)新的對(duì)象。它們的主要用途是對(duì)存在環(huán)的對(duì)象進(jìn)行深復(fù)制。比如源對(duì)象中的子對(duì)象在深復(fù)制以后,對(duì)應(yīng)于。希望這篇文章對(duì)你們有幫助深復(fù)制方法所謂擁抱未來(lái)的深復(fù)制實(shí)現(xiàn)參考資料

  

本文最初發(fā)布于我的個(gè)人博客:咀嚼之味

一年前我曾寫(xiě)過(guò)一篇 Javascript 中的一種深復(fù)制實(shí)現(xiàn),當(dāng)時(shí)寫(xiě)這篇文章的時(shí)候還比較稚嫩,有很多地方?jīng)]有考慮仔細(xì)。為了不誤人子弟,我決定結(jié)合 Underscore、lodash 和 jQuery 這些主流的第三方庫(kù)來(lái)重新談一談這個(gè)問(wèn)題。

第三方庫(kù)的實(shí)現(xiàn)

講一句唯心主義的話,放之四海而皆準(zhǔn)的方法是不存在的,不同的深復(fù)制實(shí)現(xiàn)方法和實(shí)現(xiàn)粒度有各自的優(yōu)劣以及各自適合的應(yīng)用場(chǎng)景,所以本文并不是在教大家改如何實(shí)現(xiàn)深復(fù)制,而是將一些在 JavaScript 中實(shí)現(xiàn)深復(fù)制所需要考慮的問(wèn)題呈獻(xiàn)給大家。我們首先從較為簡(jiǎn)單的 Underscore 開(kāi)始:

Underscore —— _.clone()

在 Underscore 中有這樣一個(gè)方法:_.clone(),這個(gè)方法實(shí)際上是一種淺復(fù)制 (shallow-copy),所有嵌套的對(duì)象和數(shù)組都是直接復(fù)制引用而并沒(méi)有進(jìn)行深復(fù)制。來(lái)看一下例子應(yīng)該會(huì)更加直觀:

var x = {
    a: 1,
    b: { z: 0 }
};

var y = _.clone(x);

y === x       // false
y.b === x.b   // true

x.b.z = 100;
y.b.z         // 100

讓我們來(lái)看一下 Underscore 的源碼:

// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
  if (!_.isObject(obj)) return obj;
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

如果目標(biāo)對(duì)象是一個(gè)數(shù)組,則直接調(diào)用數(shù)組的slice()方法,否則就是用_.extend()方法。想必大家對(duì)extend()方法不會(huì)陌生,它的作用主要是將從第二個(gè)參數(shù)開(kāi)始的所有對(duì)象,按鍵值逐個(gè)賦給第一個(gè)對(duì)象。而在 jQuery 中也有類(lèi)似的方法。關(guān)于 Underscore 中的 _.extend() 方法的實(shí)現(xiàn)可以參考 underscore.js #L1006。

Underscore 的 clone() 不能算作深復(fù)制,但它至少比直接賦值來(lái)得“深”一些,它創(chuàng)建了一個(gè)新的對(duì)象。另外,你也可以通過(guò)以下比較 tricky 的方法來(lái)完成單層嵌套的深復(fù)制:

var _ = require("underscore");
var a = [{f: 1}, {f:5}, {f:10}];
var b = _.map(a, _.clone);       // <----
b[1].f = 55;
console.log(JSON.stringify(a));  // [{"f":1},{"f":5},{"f":10}]
jQuery —— $.clone() / $.extend()

在 jQuery 中也有這么一個(gè)叫 $.clone() 的方法,可是它并不是用于一般的 JS 對(duì)象的深復(fù)制,而是用于 DOM 對(duì)象。這不是這篇文章的重點(diǎn),所以感興趣的同學(xué)可以參考jQuery的文檔。與 Underscore 類(lèi)似,我們也是可以通過(guò) $.extend() 方法來(lái)完成深復(fù)制。值得慶幸的是,我們?cè)?jQuery 中可以通過(guò)添加一個(gè)參數(shù)來(lái)實(shí)現(xiàn)遞歸extend。調(diào)用$.extend(true, {}, ...)就可以實(shí)現(xiàn)深復(fù)制啦,參考下面的例子:

var x = {
    a: 1,
    b: { f: { g: 1 } },
    c: [ 1, 2, 3 ]
};

var y = $.extend({}, x),          //shallow copy
    z = $.extend(true, {}, x);    //deep copy

y.b.f === x.b.f       // true
z.b.f === x.b.f       // false

在 jQuery的源碼 - src/core.js #L121 文件中我們可以找到$.extend()的實(shí)現(xiàn),也是實(shí)現(xiàn)得比較簡(jiǎn)潔,而且不太依賴于 jQuery 的內(nèi)置函數(shù),稍作修改就能拿出來(lái)多帶帶使用。

lodash —— _.clone() / _.cloneDeep()

在lodash中關(guān)于復(fù)制的方法有兩個(gè),分別是_.clone()_.cloneDeep()。其中_.clone(obj, true)等價(jià)于_.cloneDeep(obj)。使用上,lodash和前兩者并沒(méi)有太大的區(qū)別,但看了源碼會(huì)發(fā)現(xiàn),Underscore 的實(shí)現(xiàn)只有30行左右,而 jQuery 也不過(guò)60多行???lodash 中與深復(fù)制相關(guān)的代碼卻有上百行,這是什么道理呢?

var $ = require("jquery"),
    _ = require("lodash");

var arr = new Int16Array(5),
    obj = { a: arr },
    obj2;
arr[0] = 5;
arr[1] = 6;

// 1. jQuery
obj2 = $.extend(true, {}, obj);
console.log(obj2.a);                            // [5, 6, 0, 0, 0]
Object.prototype.toString.call(obj2);           // [object Int16Array]
obj2.a[0] = 100;
console.log(obj);                               // [100, 6, 0, 0, 0]

//此處jQuery不能正確處理Int16Array的深復(fù)制?。。?
// 2. lodash
obj2 = _.cloneDeep(obj);                       
console.log(obj2.a);                            // [5, 6, 0, 0, 0]
Object.prototype.toString.call(arr2);           // [object Int16Array]
obj2.a[0] = 100;
console.log(obj);                               // [5, 6, 0, 0, 0]

通過(guò)上面這個(gè)例子可以初見(jiàn)端倪,jQuery 無(wú)法正確深復(fù)制 JSON 對(duì)象以外的對(duì)象,而我們可以從下面這段代碼片段可以看出 lodash 花了大量的代碼來(lái)實(shí)現(xiàn) ES6 引入的大量新的標(biāo)準(zhǔn)對(duì)象。更厲害的是,lodash 針對(duì)存在環(huán)的對(duì)象的處理也是非常出色的。因此相較而言,lodash 在深復(fù)制上的行為反饋比前兩個(gè)庫(kù)好很多,是更擁抱未來(lái)的一個(gè)第三方庫(kù)。

/** `Object#toString` result references. */
var argsTag = "[object Arguments]",
    arrayTag = "[object Array]",
    boolTag = "[object Boolean]",
    dateTag = "[object Date]",
    errorTag = "[object Error]",
    funcTag = "[object Function]",
    mapTag = "[object Map]",
    numberTag = "[object Number]",
    objectTag = "[object Object]",
    regexpTag = "[object RegExp]",
    setTag = "[object Set]",
    stringTag = "[object String]",
    weakMapTag = "[object WeakMap]";

var arrayBufferTag = "[object ArrayBuffer]",
    float32Tag = "[object Float32Array]",
    float64Tag = "[object Float64Array]",
    int8Tag = "[object Int8Array]",
    int16Tag = "[object Int16Array]",
    int32Tag = "[object Int32Array]",
    uint8Tag = "[object Uint8Array]",
    uint8ClampedTag = "[object Uint8ClampedArray]",
    uint16Tag = "[object Uint16Array]",
    uint32Tag = "[object Uint32Array]";
借助 JSON 全局對(duì)象

相比于上面介紹的三個(gè)庫(kù)的做法,針對(duì)純 JSON 數(shù)據(jù)對(duì)象的深復(fù)制,使用 JSON 全局對(duì)象的 parsestringify 方法來(lái)實(shí)現(xiàn)深復(fù)制也算是一個(gè)簡(jiǎn)單討巧的方法。然而使用這種方法會(huì)有一些隱藏的坑,它能正確處理的對(duì)象只有 Number, String, Boolean, Array, 扁平對(duì)象,即那些能夠被 json 直接表示的數(shù)據(jù)結(jié)構(gòu)。

function jsonClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
var clone = jsonClone({ a:1 });
擁抱未來(lái)的深復(fù)制方法

我自己實(shí)現(xiàn)了一個(gè)深復(fù)制的方法,因?yàn)橛玫搅?b>Object.create、Object.isPrototypeOf等比較新的方法,所以基本只能在 IE9+ 中使用。而且,我的實(shí)現(xiàn)是直接定義在 prototype 上的,很有可能引起大多數(shù)的前端同行們的不適。(關(guān)于這個(gè)我還曾在知乎上提問(wèn)過(guò):為什么不要直接在Object.prototype上定義方法?)只是實(shí)驗(yàn)性質(zhì)的,大家參考一下就好,改成非 prototype 版本也是很容易的,不過(guò)就是要不斷地去判斷對(duì)象的類(lèi)型了。~

這個(gè)實(shí)現(xiàn)方法具體可以看我寫(xiě)的一個(gè)小玩意兒——Cherry.js,使用方法大概是這樣的:

function X() {
    this.x = 5;
    this.arr = [1,2,3];
}
var obj = { d: new Date(), r: /abc/ig, x: new X(), arr: [1,2,3] },
    obj2,
    clone;

obj.x.xx = new X();
obj.arr.testProp = "test";
clone = obj.$clone();                  //<----

首先定義一個(gè)輔助函數(shù),用于在預(yù)定義對(duì)象的 Prototype 上定義方法:

function defineMethods(protoArray, nameToFunc) {
    protoArray.forEach(function(proto) {
        var names = Object.keys(nameToFunc),
            i = 0;

        for (; i < names.length; i++) {
            Object.defineProperty(proto, names[i], {
                enumerable: false,
                configurable: true,
                writable: true,
                value: nameToFunc[names[i]]
            });
        }
    });
}

為了避免和源生方法沖突,我在方法名前加了一個(gè) $ 符號(hào)。而這個(gè)方法的具體實(shí)現(xiàn)很簡(jiǎn)單,就是遞歸深復(fù)制。其中我需要解釋一下兩個(gè)參數(shù):srcStackdstStack。它們的主要用途是對(duì)存在環(huán)的對(duì)象進(jìn)行深復(fù)制。比如源對(duì)象中的子對(duì)象srcStack[7]在深復(fù)制以后,對(duì)應(yīng)于dstStack[7]。該實(shí)現(xiàn)方法參考了 lodash 的實(shí)現(xiàn)。關(guān)于遞歸最重要的就是 Object 和 Array 對(duì)象:

/*=====================================*
 * Object.prototype
 * - $clone()
*=====================================*/

defineMethods([ Object.prototype ], {
    "$clone": function (srcStack, dstStack) {
        var obj = Object.create(Object.getPrototypeOf(this)),
            keys = Object.keys(this),
            index,
            prop;

        srcStack = srcStack || [];
        dstStack = dstStack || [];
        srcStack.push(this);
        dstStack.push(obj);

        for (var i = 0; i < keys.length; i++) {
            prop = this[keys[i]];
            if (prop === null || prop === undefined) {
                obj[keys[i]] = prop;
            }
            else if (!prop.$isFunction()) {
                if (prop.$isPlainObject()) {
                    index = srcStack.lastIndexOf(prop);
                    if (index > 0) {
                        obj[keys[i]] = dstStack[index];
                        continue;
                    }
                }
                obj[keys[i]] = prop.$clone(srcStack, dstStack);
            }
        }
        return obj;
    }
});

/*=====================================*
 * Array.prototype
 * - $clone()
*=====================================*/

defineMethods([ Array.prototype ], {
    "$clone": function (srcStack, dstStack) {
        var thisArr = this.valueOf(),
            newArr = [],
            keys = Object.keys(thisArr),
            index,
            element;

        srcStack = srcStack || [];
        dstStack = dstStack || [];
        srcStack.push(this);
        dstStack.push(newArr);

        for (var i = 0; i < keys.length; i++) {
            element = thisArr[keys[i]];
            if (element === undefined || element === null) {
                newArr[keys[i]] = element;
            } else if (!element.$isFunction()) {
                if (element.$isPlainObject()) {
                    index = srcStack.lastIndexOf(element);
                    if (index > 0) {
                        newArr[keys[i]] = dstStack[index];
                        continue;
                    }
                }
            }
            newArr[keys[i]] = element.$clone(srcStack, dstStack);
        }
        return newArr;
    }
});

接下來(lái)要針對(duì) Date 和 RegExp 對(duì)象的深復(fù)制進(jìn)行一些特殊處理:

/*=====================================*
 * Date.prototype
 * - $clone
 *=====================================*/

defineMethods([ Date.prototype ], {
    "$clone": function() { return new Date(this.valueOf()); }
});

/*=====================================*
 * RegExp.prototype
 * - $clone
 *=====================================*/

defineMethods([ RegExp.prototype ], {
    "$clone": function () {
        var pattern = this.valueOf();
        var flags = "";
        flags += pattern.global ? "g" : "";
        flags += pattern.ignoreCase ? "i" : "";
        flags += pattern.multiline ? "m" : "";
        return new RegExp(pattern.source, flags);
    }
});

接下來(lái)就是 Number, Boolean 和 String 的 $clone 方法,雖然很簡(jiǎn)單,但這也是必不可少的。這樣就能防止像單個(gè)字符串這樣的對(duì)象錯(cuò)誤地去調(diào)用 Object.prototype.$clone

/*=====================================*
 * Number / Boolean / String.prototype
 * - $clone()
 *=====================================*/

defineMethods([
    Number.prototype,
    Boolean.prototype,
    String.prototype
], {
    "$clone": function() { return this.valueOf(); }
});
比較各個(gè)深復(fù)制方法
特性 jQuery lodash JSON.parse 所謂“擁抱未來(lái)的深復(fù)制實(shí)現(xiàn)”
瀏覽器兼容性 IE6+ (1.x) & IE9+ (2.x) IE6+ IE8+ IE9+
能夠深復(fù)制存在環(huán)的對(duì)象 拋出異常 RangeError: Maximum call stack size exceeded 支持 拋出異常 TypeError: Converting circular structure to JSON 支持
對(duì) Date, RegExp 的深復(fù)制支持 × 支持 × 支持
對(duì) ES6 新引入的標(biāo)準(zhǔn)對(duì)象的深復(fù)制支持 × 支持 × ×
復(fù)制數(shù)組的屬性 × 僅支持RegExp#exec返回的數(shù)組結(jié)果 × 支持
是否保留非源生對(duì)象的類(lèi)型 × × × 支持
復(fù)制不可枚舉元素 × × × ×
復(fù)制函數(shù) × × × ×
執(zhí)行效率

為了測(cè)試各種深復(fù)制方法的執(zhí)行效率,我使用了如下的測(cè)試用例:

var x = {};
for (var i = 0; i < 1000; i++) {
    x[i] = {};
    for (var j = 0; j < 1000; j++) {
        x[i][j] = Math.random();
    }
}

var start = Date.now();
var y = clone(x);
console.log(Date.now() - start);

下面來(lái)看看各個(gè)實(shí)現(xiàn)方法的具體效率如何,我所使用的瀏覽器是 Mac 上的 Chrome 43.0.2357.81 (64-bit) 版本,可以看出來(lái)在3次的實(shí)驗(yàn)中,我所實(shí)現(xiàn)的方法比 lodash 稍遜一籌,但比jQuery的效率也會(huì)高一些。希望這篇文章對(duì)你們有幫助~

深復(fù)制方法 jQuery lodash JSON.parse 所謂“擁抱未來(lái)的深復(fù)制實(shí)現(xiàn)”
Test 1 475 341 630 320
Test 2 505 270 690 345
Test 3 456 268 650 332
Average 478.7 293 656.7 332.3
參考資料

Underscore - clone

Stackoverflow - How do you clone an array of objects using underscore?

jQuery API

lodash docs #clone

MDN - JSON.stringify

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/92333.html

相關(guān)文章

  • 在js中的深復(fù)制實(shí)現(xiàn)方法

    摘要:針對(duì)本話題,我在年月發(fā)布了新的文章深入剖析的深復(fù)制要實(shí)現(xiàn)深復(fù)制有很多辦法,比如最簡(jiǎn)單的辦法有上面這種方法好處是非常簡(jiǎn)單易用,但是壞處也顯而易見(jiàn),這會(huì)拋棄對(duì)象的,也就是深復(fù)制之后,無(wú)論這個(gè)對(duì)象原本的構(gòu)造函數(shù)是什么,在深復(fù)制之后都會(huì)變成。 針對(duì)本話題,我在2015年5月發(fā)布了新的文章:深入剖析 JavaScript 的深復(fù)制 要實(shí)現(xiàn)深復(fù)制有很多辦法,比如最簡(jiǎn)單的辦法有: var...

    Alliot 評(píng)論0 收藏0
  • js深淺復(fù)制

    摘要:總結(jié)綜上所述,數(shù)組的深拷貝比較簡(jiǎn)單,方法沒(méi)有什么爭(zhēng)議,對(duì)象的深拷貝,比較好的方法是用的方法實(shí)現(xiàn),或者遞歸實(shí)現(xiàn),比較簡(jiǎn)單的深復(fù)制可以使用實(shí)現(xiàn)參考資料知乎中的深拷貝和淺拷貝深入剖析的深復(fù)制 深淺復(fù)制對(duì)比 因?yàn)镴avaScript存儲(chǔ)對(duì)象都是存地址的,所以淺復(fù)制會(huì)導(dǎo)致 obj 和obj1 指向同一塊內(nèi)存地址。我的理解是,這有點(diǎn)類(lèi)似數(shù)據(jù)雙向綁定,改變了其中一方的內(nèi)容,都是在原來(lái)的內(nèi)存基礎(chǔ)上做...

    Apollo 評(píng)論0 收藏0
  • js技術(shù) - 收藏集 - 掘金

    摘要:還記得剛開(kāi)始學(xué)習(xí)的時(shí)候,內(nèi)存管理前端掘金作為一門(mén)高級(jí)語(yǔ)言,并不像低級(jí)語(yǔ)言那樣擁有對(duì)內(nèi)存的完全掌控。第三方庫(kù)的行代碼內(nèi)實(shí)現(xiàn)一個(gè)前端掘金前言本文會(huì)教你如何在行代碼內(nèi),不依賴任何第三方的庫(kù),用純實(shí)現(xiàn)一個(gè)。 (譯) 如何使用 JavaScript 構(gòu)建響應(yīng)式引擎 —— Part 1:可觀察的對(duì)象 - 掘金原文地址:How to build a reactive engine in JavaSc...

    Guakin_Huang 評(píng)論0 收藏0
  • js技術(shù) - 收藏集 - 掘金

    摘要:還記得剛開(kāi)始學(xué)習(xí)的時(shí)候,內(nèi)存管理前端掘金作為一門(mén)高級(jí)語(yǔ)言,并不像低級(jí)語(yǔ)言那樣擁有對(duì)內(nèi)存的完全掌控。第三方庫(kù)的行代碼內(nèi)實(shí)現(xiàn)一個(gè)前端掘金前言本文會(huì)教你如何在行代碼內(nèi),不依賴任何第三方的庫(kù),用純實(shí)現(xiàn)一個(gè)。 (譯) 如何使用 JavaScript 構(gòu)建響應(yīng)式引擎 —— Part 1:可觀察的對(duì)象 - 掘金原文地址:How to build a reactive engine in JavaSc...

    zhou_you 評(píng)論0 收藏0
  • javascript中的數(shù)據(jù)類(lèi)型

    摘要:中具有兩種數(shù)據(jù)類(lèi)型的值,分別是基本類(lèi)型值和引用類(lèi)型值。在中,基本類(lèi)型值指的是簡(jiǎn)單的數(shù)據(jù)段,引用類(lèi)型值指那些可能由多個(gè)值構(gòu)成的對(duì)象?;緮?shù)據(jù)類(lèi)型基本數(shù)據(jù)類(lèi)型未定義的值的默認(rèn)值尚未存在的對(duì)象數(shù)字字符串。 整理以及總結(jié)一下,回溯下基礎(chǔ)。 ECMAScript中具有兩種數(shù)據(jù)類(lèi)型的值,分別是 基本類(lèi)型值和引用類(lèi)型值。 在ECMAScript中,基本類(lèi)型值指的是簡(jiǎn)單的數(shù)據(jù)段,引用類(lèi)型值指那些可能由...

    2450184176 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<