EcmaScript對象克隆之謎

先談談深拷貝

如何在js中獲得一個克隆對象,可以說是喜聞樂見的話題了。相信大家都了解引用類型與基本類型,也都知道有種叫做深拷貝的東西,傳說深拷貝可以獲得一個克隆對象!那么像我這樣的萌新自然就去學習了一波,我們能找到的代碼基本都是這樣的:

低配版深拷貝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var deepClone = function(currobj){if(typeof currobj !== 'object'){return currobj;}if(currobj instanceof Array){var newobj = [];}else{var newobj = {}}for(var key in currobj){if(typeof currobj[key] !== 'object'){newobj[key] = currobj[key];}else{newobj[key] = deepClone(currobj[key])    }}return newobj
}

嘖嘖真是很精巧啊!對于Array和普通Object都做了區分。但是顯然,借助遞歸實現的深拷貝如果要克隆層級很多的復雜對象,容易造成內存溢出,咱可以做出一個小小改進:

看起來酷一點的深拷貝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var deepClone = function(currobj){if(typeof currobj !== 'object'){return currobj;}if(currobj instanceof Array){var newobj = [];}else{var newobj = {}}var currQue = [currobj], newQue = [newobj]; //關鍵在這里while(currQue.length){var obj1 = currQue.shift(),obj2 = newQue.shift();for(var key in obj1){if(typeof obj1[key] !== 'object'){obj2[key] = obj1[key];}else{if(obj1[key] instanceof Array ){obj2[key] = [];}else{obj2[key] = {}};// 妙啊currQue.push(obj1[key]);newQue.push(obj2[key]);}}}return newobj;
};

這里利用了兩個隊列,還算優雅的避免了遞歸的弊端。

JSON序列化

還有一種方法是利用JSON的內置方法,即所謂的JSON序列化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var deepClone = function(obj){var str, newobj = obj.constructor === Array ? [] : {};if(typeof obj !== 'object'){return;} else if(window.JSON){str = JSON.stringify(obj), //系列化對象newobj = JSON.parse(str); //還原} else {for(var i in obj){newobj[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]; }}return newobj;
};

不過不打緊,它與上面方法的效果基本相同。

上面幾種深拷貝的局限

拜托,大家都很懂對象,上面的方法有幾個很大的問題:

  • 遇到對象內部的循環引用直接gg
  • 無法拷貝函數(typeof 函數 得到的是 ‘function’),函數仍是引用類型
  • 無法正確保留實例對象的原型

于是,我們就要開始改造上面的深拷貝方法來進行完美的克隆了!………….么?

等下,你到底要啥

克隆克隆,我們平常把它掛在嘴上,但面對一個對象,我們真正想克隆的是什么?我想在99%的情況下,我們想克隆的是對象的數據,而保留它的原型引用方法引用,因此上面提到的局限中的第二點,基本可以不考慮。現在咱再來看看怎么解決剩下兩點。

解決循環引用

首先搞清什么是循環引用,常見的循環引用有兩種:

自身循環引用

1
2
var a = {};
a._self = a;

這種循環引用可以說很是常見。

多個對象互相引用

1
2
3
4
var a = {};
var b = {};
a.brother = b;
b.brother = a;

也不是沒見過,不過這是典型導致對象內存無法被回收的寫法,本身就不推薦。

解決之道

目前只找到了針對第一種引用的解決方法,來自于Jquery源碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
jQuery.extend = jQuery.fn.extend = function() {// options是一個緩存變量,用來緩存arguments[i]// name是用來接收將要被擴展對象的key// src改變之前target對象上每個key對應的value// copy傳入對象上每個key對應的valu// copyIsArray判定copy是否為一個數組// clone深拷貝中用來臨時存對象或數組的srcvar options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},i = 1,length = arguments.length,deep = false;// 處理深拷貝的情況if (typeof target === "boolean") {deep = target;target = arguments[1] || {};//跳過布爾值和目標 i++;}// 控制當target不是object或者function的情況if (typeof target !== "object" && !jQuery.isFunction(target)) {target = {};}// 當參數列表長度等于i的時候,擴展jQuery對象自身if (length === i) {target = this; --i;}for (; i < length; i++) {if ((options = arguments[i]) != null) {// 擴展基礎對象for (name in options) {src = target[name];	copy = options[name];// 防止永無止境的循環,這里舉個例子,如var i = {};i.a = i;$.extend(true,{},i);如果沒有這個判斷變成死循環了if (target === copy) {continue;}if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {if (copyIsArray) {copyIsArray = false;clone = src && jQuery.isArray(src) ? src: []; // 如果src存在且是數組的話就讓clone副本等于src否則等于空數組。} else {clone = src && jQuery.isPlainObject(src) ? src: {}; // 如果src存在且是對象的話就讓clone副本等于src否則等于空數組。}// 遞歸拷貝target[name] = jQuery.extend(deep, clone, copy);} else if (copy !== undefined) {target[name] = copy; // 若原對象存在name屬性,則直接覆蓋掉;若不存在,則創建新的屬性。}}}}// 返回修改的對象return target;
};

解決原型的引用

在我們想辦法魔改深拷貝時,先看下以上這么多深拷貝的基本原理:

利用for-in循環遍歷對象屬性,如果屬性值是對象則深拷貝,不是則直接賦值

于是俺眉頭一皺發現事情并不簡單,俺上一篇博客已經說明:for-in遍歷的是對象以及其原型鏈上可枚舉屬性,因此想在遍歷時對源對象的__proto__做手腳是根本不存在的,__proto__以及它的不可枚舉屬性根本不會被遍歷到。可以通過下面的例子看出:

1
2
3
4
5
6
7
8
9
10
11
12
13
var deepClone = function() {...} // 隨便從上面拿一個
var A = function() {this.val = 1;
}
A.prototype.log = function() {console.log(this.val);
}var obj1 = new A();
var obj2 = deepClone(obj1);console.log(obj1); // A {val: 1}
console.log(obj2); // {val: 1, log: function(){...}}

因此,一個解決方法很單純,就是像上面的jQuery.extend方法一樣,自己傳入拷貝的目標對象,extend方法本質上只是拓展目標對象的屬性,使其獲得源對象上的數據,這樣一來只要我們先創建好符合需求的目標對象即可。

另一種方法則是不采用深拷貝,直接取出需要進行拷貝的對象的數據,然后再利用這份數據來實例化和設置一個新的對象出來

1
2
3
4
5
6
7
8
9
10
11
var Foo = function( obj ){this.name = obj.name;this.sex = obj.sex
};Foo.prototype.toJSON = funciton(){return { name: this.name, sex: this.sex };
};var foo = new Foo({ name: "bandit", sex: "male" });
var fooCopy = new Foo( foo.toJSON() );

問題同樣得到解決【鼓掌】


回顧一下,沒有哪種方法是萬用的魔法 —— 在我們想要獲得一個克隆對象之前,或許最好先搞清楚我們到底是在克隆什么,再采用最適合的方法。而非是拘泥于“深拷貝淺拷貝”的說法,去復制一段代碼祈禱他能生效。我相信以上的示例代碼還沒有考慮到克隆對象的所有問題,但它們在合適的場景下能夠處理合適的問題。嗯,其實很多事情都是這樣蛤【帶!】

轉載于:https://www.cnblogs.com/jinhengyu/p/10257781.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/450784.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/450784.shtml
英文地址,請注明出處:http://en.pswp.cn/news/450784.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

開發人員眼中最好的代碼編輯器是誰?

摘要&#xff1a;對開發人員來講&#xff0c;開發工具就好比戰場上的“兵器”&#xff0c;不同領域的開發人員他們所使用的“兵器”也不完全相同&#xff0c;本文從友好性、功能性、擴展等多方面總結了最受開發人員歡迎的“兵器”。你最愛的那個在這里嗎&#xff1f; 如果我們把…

關于RESTful一些注意事項,接口開發規范

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 最近在研究restful&#xff0c;公司開發要使用&#xff0c;所以自己就去網上找了好些資料&#xff0c;并整理了一套公司開發的接口規范。…

【老杜】MySQL—day01

文章目錄day01課堂筆記1、數據庫概述及數據準備1.1、什么是數據庫1.2、什么是數據庫管理系統1.3、SQL概述1.4、安裝MySQL數據庫管理系統。1.4、MySQL數據庫的完美卸載&#xff01;1.5、MySQL的服務1.6、用命令來啟動和關閉mysql服務1.7、登錄mysql數據庫2、MySQL常用命令&#…

【轉載】DRuid 大數據分析之查詢

轉載自http://yangyangmyself.iteye.com/blog/23217591、Druid 查詢概述上一節完成數據導入后&#xff0c;接下來講講Druid如何查詢及統計分析導入的數據。Druid的查詢是使用REST風格的HTTP請求查詢服務節點&#xff08;Broker、Historical、Realtime&#xff09;&#xff0c;這…

記錄 Parameter with that position [1] did not exist; nested exception is java.lang.IllegalArgumentExce

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 報錯如題&#xff1a; Parameter with that position [1] did not exist; nested exception is java.lang.IllegalArgumentException: Pa…

[轉]簡單的動態修改RDLC報表頁邊距和列寬的方法

本文轉自&#xff1a;http://star704983.blog.163.com/blog/static/136661264201161604413204/ 1.修改頁邊距 XmlDocument XMLDoc new XmlDocument();XMLDoc.Load(System.Windows.Forms.Application.StartupPath "\Report_try-2.rdlc");XmlNamespaceManager xmn n…

函數式編程語言天生就慢嗎?

摘要&#xff1a;近期&#xff0c;函數式編程得到了越來越多的關注&#xff0c;Lisp不僅重獲青春還涌現出了一批新函數式編程語言。因此開發者們對函數式編程語言的運行快慢各抒己見&#xff0c;展開激烈討論。本文將和大家一起討論&#xff0c;函數式編程語言真的就慢嗎&#…

【老杜】MySQL—day02

文章目錄day02課堂筆記1、把查詢結果去除重復記錄【distinct】10、連接查詢10.1、什么是連接查詢&#xff1f;10.2、連接查詢的分類&#xff1f;10.3、當兩張表進行連接查詢時&#xff0c;沒有任何條件的限制會發生什么現象&#xff1f;10.4、怎么避免笛卡爾積現象&#xff1f;…

vue根據數組對象中某個唯一標識去重

由于在vue中&#xff0c;會自動在數組和對象中加入_obser__觀察者模式的一些屬性&#xff0c;所以直接用數組的filter去重&#xff08;下面這種&#xff09;&#xff0c;indexOf不能準確識別 var arr [1, 2, 2, 3, 4, 5, 5, 6, 7, 7]; var arr2 arr.filter(function(x, index…

Springsecurity之AuthenticationProvider

2019獨角獸企業重金招聘Python工程師標準>>> 注意&#xff1a;AuthenticationProvider與Authentication緊密聯系&#xff0c;關于Authentication&#xff0c;看我的這篇博客。 先上一張圖&#xff0c;如下圖1 圖1 AuthenticationProvider的類圖 AuthenticationProvi…

Postman使用入門

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 Postman測試管理的單位是測試集&#xff08;Collections&#xff09;&#xff0c;測試集內可以創建文件夾(Folder)和具體的請求(Requests…

編程需要知道多少數學知識?

摘要&#xff1a;許多人認為在開始學習編程之前必須對數學很在行或者數學分數很高。但一個人為了編程的話&#xff0c;需要學習多少數學呢&#xff1f; 實際上不需要很多 。這篇文章中我會深入探討編程中所需要的數學知識。 下面是我在reddit的子論壇 r/learnprogramming 看到的…

HDU 6071 Lazy Running

鏈接HDU 6071 Lazy Running 給出四個點1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;1和2&#xff0c;2和3&#xff0c;3和4&#xff0c;4和1之間有路相連&#xff0c;現在從2點出發&#xff0c;最后回到2點&#xff0c;要求路徑大于等于\(K\)&#xff0c;問路徑長度最…

vue彈窗插件實戰

vue做移動端經常碰到彈窗的需求, 這里寫一個功能簡單的vue彈窗 popup.vue <template><div class"popup-wrapper" v-show"visible" click"hide"><div class"popup-text">{{text}}</div></div> </temp…

【狂神說】Redis筆記

文章目錄1、Nosql概述1.1 為什么要用Nosql1.2 什么是NoSQL1.3 阿里巴巴演進分析2、NoSQL的四大分類3、Redis入門3.1 概述3.2 Windows安裝3.3 Linux安裝3.4 測試性能3.5 基礎的知識4、五大數據類型4.1 Redis-Key4.2 String&#xff08;字符串&#xff09;4.3 List&#xff08;列…

Postman用法說明

見&#xff1a;http://blog.csdn.net/flowerspring/article/details/52774399 Postman用法簡介-Http請求模擬工具 在我們平時開發中&#xff0c;特別是需要與接口打交道時&#xff0c;無論是寫接口還是用接口&#xff0c;拿到接口后肯定都得提前測試一下&#xff0c;這樣的話就…

位、字,字節與KB的關系?

位&#xff1a;我們常說的bit&#xff0c;位就是傳說中提到的計算機中的最小數據單位&#xff1a;說白了就是0或者1&#xff1b;計算機內存中的存儲都是01這兩個東西。 字節&#xff1a;英文單詞&#xff1a;&#xff08;byte&#xff09;&#xff0c;byte是存儲空間的基本計量…

C++ string 介紹

之所以拋棄char *的字符串而選用C標準程序庫中的string類&#xff0c;是因為他和前者比較起來&#xff0c;不必擔心內存是否足夠、字符串長度等等&#xff0c;而且作為一個類出現&#xff0c;他集成的操作函數足以完成我們大多數情況下(甚至是100%)的需要。我們可以用 進行賦…

Linux核心總結

文章目錄1.首先了解一下linux的目錄結構2.linux的基本命令之使用命令開關機3.linux的基本命令之目錄管理1.ls—列出目錄命令2.cd—切換目錄命令3.pwd—查看當前所在目錄命令4.mkdir—創建文件夾命令5.rmdir—刪除文件夾命令6.cp—復制文件命令7.rm—傳說中的刪庫跑路命令8.mv—…