理解深淺拷貝,首先要明白兩種賦值方式: 傳值賦值和引用賦值,而傳值賦值和引用賦值的區別就是「值」的類型,傳值賦值中的值是基本類型,包括六種:string, number, bool, null, undefined, symbol;引用賦值中的值是對象,好比function, object, array等等,而要理解傳遞賦值和引用賦值的區別,就不得不說下javascript中的堆(heap)和棧(stack)。javascript
js中內存主要分爲棧內存和堆內存,一般棧內存存放的是對象的地址,而堆內存存放的是對象的具體內容。對於基本類型來講,其地址和內容都存在棧內存中,而引用類型其地址存在棧內存中,其內容則存在堆內存中。java
棧內存和堆內存區別。棧內存運行效率比較高,空間相對比較小,堆內存則相反。因此將構造簡單的基本類型放在棧內存中,而將複雜的引用類型放在堆內存中。數組
咱們知道了不一樣的數據類型存儲在不一樣的內存區域,那麼變量名是如何和該內存地址作一一關聯的呢?任何高級語言都有編譯的過程,而這個編譯的過程就會創建變量名和內存地址的一一對應關係,變量名只是方便咱們使用而已,在編譯後,計算機實際使用的是內存地址在操做。(大概原理,編譯原理也都忘了)函數
結合上面的堆棧舉個栗子code
基本類型對象
var a = "a"; //基本類型,爲a分配棧內存地址和存儲內容 var b = a; //基本類型,爲b分配新的棧內存地址和內容 a = "a1"; //a對應內存地址內容修改成'a1',b對應的內存地址內容不變 console.log(a, b); //a1, a
引用類型遞歸
var a = {name: 'a'}; //引用類型,爲a分配棧內存地址,爲對象{name: 'a'}分配堆內存空間,並將內存地址做爲a地址的內容 var b = a; //引用類型,爲b分配棧內存空間,將a中對象引用的地址存到b地址中 a.name = 'a1'; //根據a用存放的地址引用,修改堆內存中對象的屬性 console.log(a.name, b.name, a===b); //此時a和b仍舊同時引用同一個對象, ===直接對比的內存地址
深拷貝和淺拷貝不一樣的地方,就是對對象屬性操做的不一樣。簡單來講,淺拷貝,新生成對象對象屬性是源對象的引用,共同指向同一塊堆內存;深拷貝,新生成的對象屬性是一個新對象,裏面屬性和源對象中的一致。圖片
在操做層面,淺拷貝,只須要拷貝源對象的第一層屬性;而深拷貝則不斷遞歸遍歷源對象中的對象屬性,遇到對象則先新建一個對象,並將源對象中基本類型中賦值給新建對象。ip
舉個栗子內存
淺拷貝
var a = {name: 'a', toys: ['dog', 'cat']}; function simpleClone(obj) { var _obj = {}; for(var p in obj) { _obj[p] = obj[p]; //只拷貝第一層 } return _obj; } var b = simpleClone(a); a.toys.push('bird'); console.log(a.toys, b.toys, a.toys===b.toys); //["dog", "cat", "bird"] ["dog", "cat", "bird"] true
從上面能夠看出修改了a以後,b也隨之改動,說明在淺拷貝中,對象屬性是在目標和源之間是同一個引用。
深拷貝
var a = {name: 'a', toys: ['dog', 'cat']}; function simpleDeepClone(obj) { var _obj = {}; for (var p in obj) { //對象類型,遞歸處理,其餘對象類型暫不處理 if(obj[p].constructor === object) { _obj[p] = simpleDeepClone(obj[p]) } else { //基本類型 _obj[p] = ojb[p] } } return _obj }
從上面能夠看出深拷貝中對於對象屬性總會有一個new的過程,因此新生成的屬性和源屬性再也不是同一個引用。
淺拷貝
function baseClone(value) { if (!isObject(value)) { //非object和function直接返回,基本數據類型直接返回 return value; } if (isArr) {//數組對象 result = initCloneArray(value); //調用該數組對象的構造函數,構造出等長的新空數組 if (!isDeep) { return copyArray(value, result);//遍歷源數組值,依次賦值給新的數組 } } if (tag == objectTag || tag == argsTag || (isFunc && !object)) { result = initCloneObject(isFunc ? {} : value); //函數對象都返回空對象 if (!isDeep) { /**baseAssign就是遍歷源對象屬性依次賦值給上面新建立的空對象,因爲symbol類型做爲屬性的特殊性,它不能被for...in,for...of,Object.keys(),Object.getOwnPropertyNames()返回,只能經過Object.getOwnPropertySymbols這個特殊獲取,因此須要再次單獨處理一下**/ return copySymbols(value, baseAssign(result, value)); } } } }
因此lodash淺拷貝的整個流程 1)若是源爲基本類型直接賦值(包括symbol),數組對象建立一個內容同樣的新數組 2)若是源爲對象且原型爲object或function,則依次變量其屬性,將其賦值給新建立的空對象(這個過程當中基本類型屬於直接賦值,對象屬性則屬於引用賦值)
深拷貝
深拷貝就是遍歷執行淺拷貝的流程(淺拷貝遇到對象會生成一個新對象,深拷貝的屬性若是是對象,就將該對象做爲淺拷貝的源,依次遍歷。)
函數參數都是傳值賦值,不管對象與否
不改變原數組方法
var a = [{name: 'a'}, 1,2,3] var b = a.slice(0, 2); console.log(a,b) //[ { name: 'a' }, 1, 2, 3 ] [ { name: 'a' }, 1 ] b[0].name = 'b'; console.log(a,b)//[ { name: 'b' }, 1, 2, 3 ] [ { name: 'b' }, 1 ] var c = a.map(function(key) { if(typeof key == 'object') { return key.name = 'c' }else { return key } }) console.log(c, a)//[ { name: 'c' }, 1, 2, 3 ] [ { name: 'c' }, 1, 2, 3 ]