javascript之copy 发表于 2018-08-12 深浅copy 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176// 基本类型和引用类型 copy问题:let a = 12;let aa = a;console.log(a); // 12aa = 123;console.log(a); // 12let b = 'dottie';let bb = b;console.log(b); // dottiebb = '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: {} } 确实没有受影响// 但是, 这时候打印object2console.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 } */