前言:
閱讀此文前需掌握:javascript的堆棧原理,引用類型與基本類型差別 在
JavaScript
中,我們将資料分為
基本資料類型(原始值)
與
引用類型
- 基本資料類型的值是按值通路的,基本類型的值是不可變的
- 引用類型的值是按引用通路的,引用類型的值是動态可變的
var fx = 100;
var fx1 = 100;
console.log(fx === fx1) // true
var fx2 = {a: 1, b: 2};
var fx3 = {a: 1, b: 2};
console.log(fx2 === fx3) // false 兩個不同的對象
- 基本資料類型的比較是值得比較
- 引用類型的比較是引用位址的比較
鑒于以上資料類型的特點,我們可以初步想到:所謂
淺拷貝
與
深拷貝
可能就是對于值的拷貝和引用的拷貝(基本資料類型都是對值的拷貝,不進行區分)。一般來說,我們所涉及的拷貝對象,也都是針對引用類型的。
- 淺拷貝是拷貝一層,深層次的對象級别的就拷貝引用;深拷貝是拷貝多層,每一級别的資料都會拷貝出來;
- 淺拷貝和深拷貝都隻針對于引用資料類型,淺拷貝隻複制指向某個對象的指針,而不複制對象本身,新舊對象還是共享同一塊記憶體;但深拷貝會另外創造一個一模一樣的對象,新對象跟原對象不共享記憶體,修改新對象不會改到原對象;
- 差別:淺拷貝隻複制對象的第一層屬性、深拷貝可以對對象的屬性進行遞歸複制;
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5yM0QDN3UDN5IzMxImZjdDNzYzX5IzM0YTMzEzLcBTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
var fxArr = ["One", "Two", "Three"]
var fxArrs = fxArr
fxArrs[1] = "love";
// 由于是指派是以fxArr的值也發生改變
console.log(fxArr) // ["One", "love", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
淺拷貝:
對對象進行淺層次的複制,隻複制一層對象的屬性,并不包括對象裡面的引用類型資料。
數組的淺拷貝:
解決方法一:數組的slice方法
var fxArr = ["One", "Two", "Three"]
var fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
解決方法二:數組的concat方法
var fxArr = ["One", "Two", "Three"]
var fxArrs = fxArr.concat()
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
解決方法三:ES6的擴充運算符...
var fxArr = ["One", "Two", "Three"]
var fxArrs = [...fxArr]
fxArrs[1] = "love";
console.log(fxArr) // ["One", "Two", "Three"]
console.log(fxArrs) // ["One", "love", "Three"]
對象的淺拷貝:
第一種方法
// 隻複制第一層的淺拷貝
function simpleCopy (obj1) {
var obj2 = Array.isArray(obj1) ? [] : {}
for (let i in obj1) {
obj2[i] = obj1[i]
}
return obj2
}
var fxObj = {
age: 18,
nature: ['smart', 'good'],
names: {
name1: 'fx',
name2: 'xka'
},
love: function () {
console.log('fx is a great girl')
}
}
var newFxObj = simpleCopy(fxObj)
newFxObj.age = 8
newFxObj.nature.push('why')
newFxObj.names.name1 = 'why fx'
newFxObj.love = function () {
console.log('fx is 18 years old')
}
console.log(fxObj.age) // 18 基本資料類型不會改變
console.log(fxObj.nature) // ["smart", "good", "why"] 引用類型會改變
console.log(fxObj['names']) // {name1: "why fx", name2: "xka"} 引用類型會改變
console.log(fxObj['love']) // ƒ () {console.log('fx is a great girl')}
第二種方法:Object.assign方法(隻能處理深度隻有一層的對象)
var fxObj = {
age: 18,
nature: ['smart', 'good'],
names: {
name1: 'fx',
name2: 'xka'
},
love: function () {
console.log('fx is a great girl')
}
}
var newFxObj = Object.assign({}, fxObj);
newFxObj.age = 8
newFxObj.nature.push('why')
newFxObj.names.name1 = 'why fx'
newFxObj.love = function () {
console.log('fx is 18 years old')
}
console.log(fxObj.age) // 18
console.log(fxObj.nature) // ["smart", "good", "why"]
console.log(fxObj['names']) // {name1: "why fx", name2: "xka"}
console.log(fxObj['love']) // ƒ () {console.log('fx is a great girl')}
方法三:ES6的對象擴充方法var newFxObj = {...fxObj}
var fxObj = {
age: 18,
nature: ['smart', 'good'],
names: {
name1: 'fx',
name2: 'xka'
},
love: function () {
console.log('fx is a great girl')
}
}
var newFxObj = {...fxObj}
newFxObj.age = 8
newFxObj.nature.push('why')
newFxObj.names.name1 = 'why fx'
newFxObj.love = function () {
console.log('fx is 18 years old')
}
console.log(fxObj.age) // 18
console.log(fxObj.nature) // ["smart", "good", "why"]
console.log(fxObj['names']) // {name1: "why fx", name2: "xka"}
console.log(fxObj['love']) // ƒ () {console.log('fx is a great girl')}
例子:
var person = {
name: 'tt',
age: 18,
friends: ['oo', 'cc', 'yy']
}
function shallowCopy(source) {
if (!source || typeof source !== 'object') {
throw new Error('error');
}
var targetObj = source.constructor === Array ? [] : {};
for (var keys in source) {
if (source.hasOwnProperty(keys)) {
targetObj[keys] = source[keys];
}
}
return targetObj;
}
var p1 = shallowCopy(person);
console.log(p1)
在上面的代碼中,我們建立了一個
shallowCopy
函數,它接收一個參數也就是被拷貝的對象。
- 首先建立了一個對象
- 然後
循環傳進去的對象,為了避免循環到原型上面會被周遊到的屬性,使用for...in
限制循環隻在對象自身,将被拷貝對象的每一個屬性和值添加到建立的對象當中hasOwnProperty
- 最後傳回這個對象
通過測試,我們拿到了和
person
對象幾乎一緻的對象
p1
。看到這裡,你是不是會想那這個結果和
var p1 = person
這樣的指派操作又有什麼差別呢?
var p2 = person;
// 這個時候我們修改person對象的資料
person.name = 'tadpole';
person.age = 19;
person.friends.push('tt')
p2.name // tadpole
p2.age // 19
p2.friends // ["oo", "cc", "yy", "tt"]
p1.name // tt
p1.age // 18
p1.friends // ["oo", "cc", "yy", "tt"]
上面我們建立了一個新的變量
p2
,将
person
指派給
p2
,然後比較兩個變量
深拷貝:
淺拷貝由于隻是複制一層對象的屬性,當遇到有子對象的情況時,子對象就會互相影響。是以,深拷貝是對對象以及對象的所有子對象進行拷貝
方法一:用
JSON.stringify
把對象轉成字元串,再用
JSON.parse
把字元串轉成新的對象。
缺點:
1、會忽略
undefined
2、會忽略
symbol
3、不能序列化函數,,無法拷貝函數
4、不能解決循環引用的對象 const a = {val:2}; a.target = a; 拷貝a會出現系統棧溢出,因為出現了
無限遞歸
的情況
5、不能正确處理RegExp, Date, Set, Map等
6、不能處理正則
7、會抛棄對象的constructor。也就是深拷貝之後,不管這個對象原來的構造函數是什麼,在深拷貝之後都會變成Object。
var fxObj = {
age: 18,
why: undefined,
why1: Symbol('why1'),
nature: ['smart', 'good'],
names: {
name1: 'fx',
name2: 'xka'
},
love: function () {
console.log('fx is a great girl')
}
}
var newFxObj = JSON.parse(JSON.stringify(fxObj))
newFxObj.age = 8
newFxObj.nature.push('why')
newFxObj.names.name1 = 'why fx'
newFxObj.love = function () {
console.log('fx is 18 years old')
}
console.log(fxObj.age) // 18
console.log(fxObj.nature) // ["smart", "good"]
console.log(fxObj['names']) // {name1: "fx", name2: "xka"}
console.log(fxObj['love']) // ƒ () {console.log('fx is a great girl')}
console.log(newFxObj['love']) // undefined function沒辦法轉成JSON。
console.log(newFxObj) // {age: 8, nature: Array(3), names: Object, love: function} why why1 love 都會被忽略
循環引用情況下,會報錯。
let obj = {
a: 1,
b: {
c: 2,
d: 3
}
}
obj.a = obj.b;
obj.b.c = obj.a;
let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON
方法二:循環遞歸
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免互相引用對象導緻死循環
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : {};
arguments.callee(prop, obj[i]);
} else {
obj[i] = prop;
}
}
return obj;
}
var fxObj = {
age: 18,
nature: ['smart', 'good'],
names: {
name1: 'fx',
name2: 'xka'
},
love: function () {
console.log('fx is a great girl')
}
}
var newFxObj = deepClone(fxObj);
newFxObj.age = 8
newFxObj.names.name1 = 'newfx'
console.log(fxObj)
console.log(newFxObj)
輸出
方法三:jquery 和 zepto 裡的 $.extend 方法可以用作深拷貝
var $ = require('jquery');
var newObj = $.extend(true, {}, obj);
function deepCopy(source){
if(!source || typeof source !== 'object'){
throw new Error('error');
}
var targetObj = source.constructor === Array ? [] : {};
for(var keys in source){
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepCopy(source[keys]);
}else{
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
var obj1 = {
arr: [1, 2, 3],
key: {
id: 22
},
func: function() {
console.log(123)
}
}
var obj2 = deepCopy(obj1);
obj1.arr.push(4);
obj1.arr // [1, 2, 3, 4]
obj2.arr // [1, 2, 3]
obj1.key === obj2.key // false
obj1.func === obj2.func // true