基本数据类型和引用数据类型
我们都知道js的数据类型分为基本类型和引用类型
基本数据类型(7类),number,string,boolean,null,undefined,symbol以及未来ES10新增的BigInt(任意精度整数)七类。
引用数据类型(6类),(Object类){a:1},数组[1,2,3],时间、正则、数学以及函数等。
这两类数据存储分别是这样的:
基本类型--名值存储在栈内存中
引用数据类型--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值,
一般讨论到浅拷贝和深拷贝的都是针对引用类型的,像Object和Array这样的复杂类型。
问题产生:引用类型互相赋值
引用类型直接赋值,对象会指向同一个地址,相互影响
const obj = { name: 'lin' }
const newObj = obj obj.name = 'xxx' // 改变原来的对象
console.log('原来的对象', obj)
console.log('新的对象', newObj)
console.log('两者指向同一地址', obj == newObj) // true 两者指向相同的地址
解决方法:深拷贝和浅拷贝
深拷贝和浅拷贝的区别 (值拷贝和引用拷贝)
浅拷贝只进行一层复制,深层次的引用类型还是共享内存地址,原对象和拷贝对象还是会互相影响。
深拷贝就是无限层级拷贝,深拷贝后的原对象不会和拷贝对象互相影响。
js中的基本数据类型:String Number Boolean Null Undefined,在赋值的过程中都是值拷贝.
Talk is cheap. Show me the code!
浅拷贝的实现方式:
1、Object.assign() 注意:当object只有一层的时候,是深拷贝,例如如下:
const obj = { name: 'lin' }
const newObj = Object.assign({}, obj)
obj.name = 'xxx' // 改变原来的对象
console.log(newObj) // { name: 'lin' } 新对象不变
console.log(obj == newObj) // false 两者指向不同地址
2、数组的slice和concat方法 注意:当arr只有一层的时候,是深拷贝,例如如下:
const arr = ['lin', 'is', 'handsome']
const newArr = arr.slice(0)
arr[2] = 'rich' // 改变原来的数组
console.log(newArr) // ['lin', 'is', 'handsome']
console.log(arr == newArr) // false 两者指向不同地址
const arr = ['lin', 'is', 'handsome']
const newArr = [].concat(arr)
arr[2] = 'rich' // 改变原来的数组
console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变
console.log(arr == newArr) // false 两者指向不同地址
3、扩展运算符
const arr = ['lin', 'is', 'handsome']
const newArr = [...arr] arr[2] = 'rich' // 改变原来的数组
console.log(newArr) // ['lin', 'is', 'handsome'] // 新数组不变
console.log(arr == newArr) // false 两者指向不同地址
const obj = { name: 'lin' }
const newObj = { ...obj }
obj.name = 'xxx' // 改变原来的对象
console.log(newObj) // { name: 'lin' } // 新对象不变
console.log(obj == newObj) // false 两者指向不同地址
浅拷贝底层实现:
function clone (obj) {
const cloneObj = {} // 创建一个新的对象
for (const key in obj) { // 遍历需克隆的对象
cloneObj[key] = obj[key] // 将需要克隆对象的属性依次添加到新对象上
}
return cloneObj
}
深拷贝的实现方式:
1.JSON.parse(JSON.stringify(obj))
2.jQuery.extend()
3.递归
4.使用函数库lodash
第一种方式
用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。
const obj = { person: { name: 'lin' } }
const newObj = JSON.parse(JSON.stringify(obj))
obj.person.name = 'xxx' // 改变原来的深层对象
console.log(newObj) // { person: { name: 'lin' } } 新的深层对象不变
但是这种方式存在弊端,会忽略undefined、symbol和函数和循环引用的问题
const obj = {
a: undefined,
b: Symbol('b'),
c: function () {} }
const newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj) // {}
const obj = { a: 1 }
obj.obj = obj
const newObj = JSON.parse(JSON.stringify(obj)) // 报错
第二种方式
var $ = require('jquery');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
第三种方式
function deepClone (target) {
if (target === null) return target // 处理 null
if (target instanceof Date) return new Date(target) // 处理日期
if (target instanceof RegExp) return new RegExp(target) // 处理正则
if (typeof target !== 'object') return target // 处理原始类型
if (typeof target !== 'object') { return target } // 如果是原始类型
// 如果是引用类型,递归实现每一层的拷贝
const cloneTarget = new target.constructor() // 创建一个新的克隆对象或克隆数组
for (const key in target) { // 遍历原对象
cloneTarget[key] = deepClone(target[key]) // 递归拷贝每一层
}
return cloneTarget // 返回克隆对象
}
const obj = {
a: [1, 2, 3],
b: new Date(),
c: /abc/,
d: null
}
const newObj = deepClone(obj)
console.log('原来的对象', obj)
console.log('新的对象', newObj)
第四种
使用loadsh插件实现浅拷贝和深拷贝
浅拷贝:
import { clone } from 'lodash'
const obj = { name: 'lin' }
const newObj = clone(obj) obj.name = 'xxx' // 改变原来的对象
console.log('原来的对象', obj)
console.log('新的对象', newObj)
console.log('两者指向同一地址', obj == newObj)
深拷贝:
import { cloneDeep } from 'lodash'
const obj = { person: { name: 'lin' } }
const newObj = cloneDeep(obj)
obj.person.name = 'xxx' // 改变原来的对象 console.log('原来的对象', obj)
console.log('新的对象', newObj)
console.log('更深层的对象指向同一地址', obj.person == newObj.person)
针对loash源码是如何实现深拷贝的,可以参考博客 segmentfault.com/a/119000001…
总结: 以上就是个人结合其他文章对JavaScript浅拷贝和深拷贝方式的整理,其中有不对的地方希望能够指出,谢谢!!!