
本文共 7488 字,大约阅读时间需要 24 分钟。
前言:
阅读此文前需掌握:
在JavaScript
中,我们将数据分为 基本数据类型(原始值)
与 引用类型
- 基本数据类型的值是按值访问的,基本类型的值是不可变的
- 引用类型的值是按引用访问的,引用类型的值是动态可变的
var zxx = 100;var zxx1 = 100;console.log(zxx === zxx1) // truevar zxx2 = {a: 1, b: 2};var zxx3 = {a: 1, b: 2};console.log(zxx2 === zxx3) // false 两个不同的对象
- 基本数据类型的比较是值得比较
- 引用类型的比较是引用地址的比较
鉴于以上数据类型的特点,我们可以初步想到:所谓 浅拷贝
与 深拷贝
可能就是对于值的拷贝和引用的拷贝(基本数据类型都是对值的拷贝,不进行区分)。一般来说,我们所涉及的拷贝对象,也都是针对引用类型的。
- 浅拷贝是拷贝一层,深层次的对象级别的就拷贝引用;深拷贝是拷贝多层,每一级别的数据都会拷贝出来;
- 浅拷贝和深拷贝都只针对于引用数据类型,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象;
- 区别:浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制;
var zxxArr = ["One", "Two", "Three"]var zxxArrs = zxxArrzxxArrs[1] = "love";// 由于是赋值所以zxxArr的值也发生改变console.log(zxxArr) // ["One", "love", "Three"]console.log(zxxArrs) // ["One", "love", "Three"]
浅拷贝:
对对象进行浅层次的复制,只复制一层对象的属性,并不包括对象里面的引用类型数据。
数组的浅拷贝:
解决方法一:数组的slice方法
var zxxArr = ["One", "Two", "Three"]var zxxArrs = zxxArr.slice(0)zxxArrs[1] = "love";console.log(zxxArr) // ["One", "Two", "Three"]console.log(zxxArrs) // ["One", "love", "Three"]
解决方法二:数组的concat方法
var zxxArr = ["One", "Two", "Three"]var zxxArrs = zxxArr.concat()zxxArrs[1] = "love";console.log(zxxArr) // ["One", "Two", "Three"]console.log(zxxArrs) // ["One", "love", "Three"]
解决方法三:...
var zxxArr = ["One", "Two", "Three"]var zxxArrs = [...zxxArr]zxxArrs[1] = "love";console.log(zxxArr) // ["One", "Two", "Three"]console.log(zxxArrs) // ["One", "love", "Three"]
对象的浅拷贝:
第一种方法
// 只复制第一层的浅拷贝function simpleCopy (obj1) { var obj2 = Array.isArray(obj1) ? [] : {} for (let i in obj1) { obj2[i] = obj1[i] } return obj2}var zxxObj = { age: 18, nature: ['smart', 'good'], names: { name1: 'zxx', name2: 'xka' }, love: function () { console.log('zxx is a great girl') }}var newZxxObj = simpleCopy(zxxObj)newZxxObj.age = 8newZxxObj.nature.push('why')newZxxObj.names.name1 = 'why zxx'newZxxObj.love = function () { console.log('zxx is 18 years old')}console.log(zxxObj.age) // 18 基本数据类型不会改变console.log(zxxObj.nature) // ["smart", "good", "why"] 引用类型会改变console.log(zxxObj['names']) // {name1: "why zxx", name2: "xka"} 引用类型会改变console.log(zxxObj['love']) // ƒ () {console.log('zxx is a great girl')}
第二种方法:Object.assign方法(只能处理深度只有一层的对象)
var zxxObj = { age: 18, nature: ['smart', 'good'], names: { name1: 'zxx', name2: 'xka' }, love: function () { console.log('zxx is a great girl') }}var newZxxObj = Object.assign({}, zxxObj);newZxxObj.age = 8newZxxObj.nature.push('why')newZxxObj.names.name1 = 'why zxx'newZxxObj.love = function () { console.log('zxx is 18 years old')}console.log(zxxObj.age) // 18console.log(zxxObj.nature) // ["smart", "good", "why"]console.log(zxxObj['names']) // {name1: "why zxx", name2: "xka"}console.log(zxxObj['love']) // ƒ () {console.log('zxx is a great girl')}
方法三:ES6的对象扩展方法var newZxxObj = {...zxxObj}
var zxxObj = { age: 18, nature: ['smart', 'good'], names: { name1: 'zxx', name2: 'xka' }, love: function () { console.log('zxx is a great girl') }}var newZxxObj = {...zxxObj}newZxxObj.age = 8newZxxObj.nature.push('why')newZxxObj.names.name1 = 'why zxx'newZxxObj.love = function () { console.log('zxx is 18 years old')}console.log(zxxObj.age) // 18console.log(zxxObj.nature) // ["smart", "good", "why"]console.log(zxxObj['names']) // {name1: "why zxx", name2: "xka"}console.log(zxxObj['love']) // ƒ () {console.log('zxx 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 // tadpolep2.age // 19p2.friends // ["oo", "cc", "yy", "tt"]p1.name // ttp1.age // 18p1.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 zxxObj = { age: 18, why: undefined, why1: Symbol('why1'), nature: ['smart', 'good'], names: { name1: 'zxx', name2: 'xka' }, love: function () { console.log('zxx is a great girl') }}var newZxxObj = JSON.parse(JSON.stringify(zxxObj))newZxxObj.age = 8newZxxObj.nature.push('why')newZxxObj.names.name1 = 'why zxx'newZxxObj.love = function () { console.log('zxx is 18 years old')}console.log(zxxObj.age) // 18console.log(zxxObj.nature) // ["smart", "good"]console.log(zxxObj['names']) // {name1: "zxx", name2: "xka"}console.log(zxxObj['love']) // ƒ () {console.log('zxx is a great girl')}console.log(newZxxObj['love']) // undefined function没办法转成JSON。console.log(newZxxObj) // {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 zxxObj = { age: 18, nature: ['smart', 'good'], names: { name1: 'zxx', name2: 'xka' }, love: function () { console.log('zxx is a great girl') }}var newZxxObj = deepClone(zxxObj);newZxxObj.age = 8newZxxObj.names.name1 = 'newzxx'console.log(zxxObj)console.log(newZxxObj)
输出
方法三: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 // falseobj1.func === obj2.func // true
对于深拷贝的对象,改变源对象不会对得到的对象有影响。只是在拷贝的过程中源对象的方法丢失了,这是因为在序列化 JavaScript
对象时,所有函数和原型成员会被有意忽略
发表评论
最新留言
关于作者
