javascript之copy

深浅copy

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// 基本类型和引用类型 copy问题:

let a = 12;
let aa = a;
console.log(a); // 12
aa = 123;
console.log(a); // 12

let b = 'dottie';
let bb = b;
console.log(b); // dottie
bb = 'dottie daejong'
console.log(b); // dottie

// 基本类型复制数据都不会有问题。都是栈中值的拷贝

// 而引用类型即 对象的拷贝就得注意了。

var arr = [1, 2, 3];
var arr1 = arr;
console.log(arr); // [ 1, 2, 3 ]
arr1.push(4);
console.log(arr); // [ 1, 2, 3, 4 ]
// 我们修改arr1.但是arr也被影响了。

// 再看看对象
var obj = {
name: 'dottie',
};
var obj1 = obj;
obj1.name = 'daejong';
obj1.age = 23;
console.log(obj); // { name: 'daejong', age: 23 }发现也被影响了。


// 故 引用类型直接赋值的方法,其实是把该对象在堆内存中的地址赋值给变量。所以他们就操作的是同一块堆内存。当然一变就全变了。


// 但是我们想copy出一份对象。使得新copy出的对象和被copy的对象互不影响。 该怎么做呢?
// 可以对于数组我们首先 想到的是slice方法和concat方法。
var array1 = [1, 2, 3];
var array2 = array1.slice();
console.log(array1) // [ 1, 2, 3 ]
array2.push(4);
console.log(array1) // [ 1, 2, 3 ]
// 结果确实没有受影响,但是仔细的童鞋可能会发现。我们这array1数组里面的值都是基本类型的值.
// 如果我们对引用类型进行操作呢?

var array3 = [1, 2, 3, [4, 5]];
var array4 = array3.slice();
console.log(array3) // [ 1, 2, 3, [ 4, 5 ] ]
array4[3].push(666);
console.log(array3) // [ 1, 2, 3, [ 4, 5, 666 ] ]
// 结果就显而易见了。 slice方法其实是一个浅copy。
// 讲解:
// 本质就是 栈中变量array3和array4 确实指向堆中两块不同的堆内存
// 一个是[ 1, 2, 3, [ 4, 5 ] ],另一个也是[ 1, 2, 3, [ 4, 5 ] ],
// 但是这两块堆内存中的第四个元素其实是一个指针,指向同一块堆内存[4, 5]
// 所以我们其实操作array3的第四个元素,其实同时也是在操作array4的第四个元素。

// 其实Object里面也提供了一个方法 Object.assign,也是浅copy。效果跟slice一样。
// 当然了,他也可以用来合并对象 var obj = Object.assign(target, source1, source2) === target 合并对象, source2中可以覆盖source1中同名属性。
var obj2 = {
name: 'dottie',
fn: function() {},
objj: {}
};
var obj3 = Object.assign({}, obj2);
console.log(obj2) // { name: 'dottie', fn: [Function: fn], objj: {} }
console.log(obj3) // { name: 'dottie', fn: [Function: fn], objj: {} }
obj3.name = 'daejong';
console.log(obj2); // { name: 'dottie', fn: [Function: fn], objj: {} } 确实没有受影响
console.log(obj3) // { name: 'daejong', fn: [Function: fn], objj: {} }
// 但是
obj3.objj.age = 23;
console.log(obj2) // { name: 'dottie', fn: [Function: fn], objj: { age: 23 } } 受影响
console.log(obj3) // { name: 'daejong', fn: [Function: fn], objj: { age: 23 } }



// 那么到底如何deep copy呢?
// 我第一想法是: 既然基本类型赋值是值拷贝。其实也等价于完全copy了一份副本。
// 所以我就像到用对象可以转化成字符串,然后再转会头。
// 序列化反序列化法
let object1 = {
name: 'dottie',
age: null,
friend: undefined,
fn: function() {},
objj: {},
};
let object2 = JSON.parse(JSON.stringify(object1));
console.log(object1) // { name: 'dottie',age: null,friend: undefined,fn: [Function: fn],objj: {} }
object2.name = 'daejong';
object2.objj.phone = '12123';
console.log(object1) // { name: 'dottie',age: null,friend: undefined,fn: [Function: fn],objj: {} } 确实没有受影响
// 但是, 这时候打印object2
console.log(object2) // { name: 'daejong', age: null, objj: { phone: '12123' } }
// 会发现 function和undefined的属性。被`吃`了?

// 查找原因
console.log(JSON.stringify(object1)); // {"name":"dottie","age":null,"objj":{}}
// 原来JSON序列化时。会忽略点function和undefined的属性。

// wtf 那么我们到底如何deep 一份完全的副本呢?
// 其实有许多js库帮我实现了。 但是我们先不用库,自己想一下该如何实现?
// 迭代递归法
function deepCopy(obj) {
var result;
var toString = Object.prototype.toString; //用来判断数据类型
var objType = toString.call(obj).slice(8, -1);
if (objType === 'Array') {
result = [];
for (var i = 0; i < obj.length; i++) {
result[i] = deepCopy(obj[i]);
}
} else if (objType === 'Object') {
result = {};
for (var key in obj) {
console.log(key);
if (obj.hasOwnProperty(key)) {
result[key] = deepCopy(obj[key]);
}
}
} else {
return obj;
}
return result;
}

// 测试下咯
var objj1 = {
name: 'dottie',
age: null,
friend: undefined,
fn: function() {},
objj: {},
grils: ['dottie', 'nobody']
};
var objj2 = deepCopy(objj1)
console.log(objj1) // { name: 'dottie',age: null,friend: undefined,fn: [Function: fn],objj: {},grils: [ 'dottie', 'nobody' ] }
console.log(objj2) // { name: 'dottie',age: null,friend: undefined,fn: [Function: fn],objj: {},grils: [ 'dottie', 'nobody' ] }
objj2.name = 'daejong';
console.log(objj1) // { name: 'dottie',age: null,friend: undefined,fn: [Function: fn],objj: {},grils: [ 'dottie', 'nobody' ] }
console.log(objj2) // { name: 'daejong',age: null,friend: undefined,fn: [Function: fn],objj: {},grils: [ 'dottie', 'nobody' ] }
objj2.objj.phone = '123123';
console.log(objj1) // { name: 'dottie',age: null,friend: undefined,fn: [Function: fn],objj: {},grils: [ 'dottie', 'nobody' ] }
console.log(objj2) // { name: 'daejong',age: null,friend: undefined,fn: [Function: fn],objj: { phone: '123123' },grils: [ 'dottie', 'nobody' ] }
// 测试通过。可以的

// 当然了,也可以使用js库,如lodash中_.cloneDeep
// var obj2 = _.cloneDeep(obj1)


console.log(objj1.__proto__ === Object.prototype); // true


console.log(Object.entries(objj1))
console.log(Object.keys(objj1))
console.log(Object.values(objj1))
console.log(Object.getOwnPropertyDescriptor(objj1, 'fn'))
/*
{ value: [Function: fn],
writable: true,
enumerable: true,
configurable: true }
*/

console.log(Object.keys(Function.prototype)); // [] 因为不可以枚举
console.log(Object.getOwnPropertyDescriptor(Function.prototype, 'call'))
/*
{ value: [Function: call],
writable: true,
enumerable: false,
configurable: true }
*/