赋值是将某一数值或对象赋给某个变量的过程,分为下面 2 部分前端
对基本类型进行赋值操做,两个变量互不影响。webpack
// 木易杨 let a = "muyiy"; let b = a; console.log(b); // muyiy a = "change"; console.log(a); // change console.log(b); // muyiy
对引用类型进行赋址操做,两个变量指向同一个对象,改变变量 a 以后会影响变量 b,哪怕改变的只是对象 a 中的基本类型数据。git
// 木易杨 let a = { name: "muyiy", book: { title: "You Don't Know JS", price: "45" } } let b = a; console.log(b); // { // name: "muyiy", // book: {title: "You Don't Know JS", price: "45"} // } a.name = "change"; a.book.price = "55"; console.log(a); // { // name: "change", // book: {title: "You Don't Know JS", price: "55"} // } console.log(b); // { // name: "change", // book: {title: "You Don't Know JS", price: "55"} // }
一般在开发中并不但愿改变变量 a 以后会影响到变量 b,这时就须要用到浅拷贝和深拷贝。github
建立一个新对象,这个对象有着原始对象属性值的一份精确拷贝。若是属性是基本类型,拷贝的就是基本类型的值,若是属性是引用类型,拷贝的就是内存地址 ,因此若是其中一个对象改变了这个地址,就会影响到另外一个对象。web
上图中,SourceObject
是原对象,其中包含基本类型属性 field1
和引用类型属性 refObj
。浅拷贝以后基本类型数据 field2
和 filed1
是不一样属性,互不影响。但引用类型 refObj
仍然是同一个,改变以后会对另外一个对象产生影响。面试
简单来讲能够理解为浅拷贝只解决了第一层的问题,拷贝第一层的基本类型值,以及第一层的引用类型地址。算法
Object.assign()
Object.assign()
方法用于将全部可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。跨域
有些文章说Object.assign()
是深拷贝,其实这是不正确的。数组
// 木易杨 let a = { name: "muyiy", book: { title: "You Don't Know JS", price: "45" } } let b = Object.assign({}, a); console.log(b); // { // name: "muyiy", // book: {title: "You Don't Know JS", price: "45"} // } a.name = "change"; a.book.price = "55"; console.log(a); // { // name: "change", // book: {title: "You Don't Know JS", price: "55"} // } console.log(b); // { // name: "muyiy", // book: {title: "You Don't Know JS", price: "55"} // }
上面代码改变对象 a 以后,对象 b 的基本属性保持不变。可是当改变对象 a 中的对象 book
时,对象 b 相应的位置也发生了变化。浏览器
Spread
// 木易杨 let a = { name: "muyiy", book: { title: "You Don't Know JS", price: "45" } } let b = {...a}; console.log(b); // { // name: "muyiy", // book: {title: "You Don't Know JS", price: "45"} // } a.name = "change"; a.book.price = "55"; console.log(a); // { // name: "change", // book: {title: "You Don't Know JS", price: "55"} // } console.log(b); // { // name: "muyiy", // book: {title: "You Don't Know JS", price: "55"} // }
经过代码能够看出实际效果和 Object.assign()
是同样的。
Array.prototype.slice()
slice()
方法返回一个新的数组对象,这一对象是一个由 begin
和 end
(不包括end
)决定的原数组的浅拷贝。原始数组不会被改变。
// 木易杨 let a = [0, "1", [2, 3]]; let b = a.slice(1); console.log(b); // ["1", [2, 3]] a[1] = "99"; a[2][0] = 4; console.log(a); // [0, "99", [4, 3]] console.log(b); // ["1", [4, 3]]
能够看出,改变 a[1]
以后 b[0]
的值并无发生变化,但改变 a[2][0]
以后,相应的 b[1][0]
的值也发生变化。说明 slice()
方法是浅拷贝,相应的还有concat
等,在工做中面对复杂数组结构要额外注意。
深拷贝会拷贝全部的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一块儿拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢而且花销较大。拷贝先后两个对象互不影响。
JSON.parse(JSON.stringify(object))
// 木易杨 let a = { name: "muyiy", book: { title: "You Don't Know JS", price: "45" } } let b = JSON.parse(JSON.stringify(a)); console.log(b); // { // name: "muyiy", // book: {title: "You Don't Know JS", price: "45"} // } a.name = "change"; a.book.price = "55"; console.log(a); // { // name: "change", // book: {title: "You Don't Know JS", price: "55"} // } console.log(b); // { // name: "muyiy", // book: {title: "You Don't Know JS", price: "45"} // }
彻底改变变量 a 以后对 b 没有任何影响,这就是深拷贝的魔力。
咱们看下对数组深拷贝效果如何。
// 木易杨 let a = [0, "1", [2, 3]]; let b = JSON.parse(JSON.stringify( a.slice(1) )); console.log(b); // ["1", [2, 3]] a[1] = "99"; a[2][0] = 4; console.log(a); // [0, "99", [4, 3]] console.log(b); // ["1", [2, 3]]
对数组深拷贝以后,改变原数组不会影响到拷贝以后的数组。
可是该方法有如下几个问题。
一、会忽略 undefined
二、会忽略 symbol
三、不能序列化函数
四、不能解决循环引用的对象
五、不能正确处理new Date()
六、不能处理正则
undefined
、symbol
和函数这三种状况,会直接忽略。// 木易杨 let obj = { name: 'muyiy', a: undefined, b: Symbol('muyiy'), c: function() {} } console.log(obj); // { // name: "muyiy", // a: undefined, // b: Symbol(muyiy), // c: ƒ () // } let b = JSON.parse(JSON.stringify(obj)); console.log(b); // {name: "muyiy"}
// 木易杨 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
new Date
状况下,转换结果不正确。// 木易杨 new Date(); // Mon Dec 24 2018 10:59:14 GMT+0800 (China Standard Time) JSON.stringify(new Date()); // ""2018-12-24T02:59:25.776Z"" JSON.parse(JSON.stringify(new Date())); // "2018-12-24T02:59:41.523Z"
解决方法转成字符串或者时间戳就行了。
// 木易杨 let date = (new Date()).valueOf(); // 1545620645915 JSON.stringify(date); // "1545620673267" JSON.parse(JSON.stringify(date)); // 1545620658688
// 木易杨 let obj = { name: "muyiy", a: /'123'/ } console.log(obj); // {name: "muyiy", a: /'123'/} let b = JSON.parse(JSON.stringify(obj)); console.log(b); // {name: "muyiy", a: {}}
PS:为何会存在这些问题能够学习一下 JSON。
除了上面介绍的深拷贝方法,经常使用的还有jQuery.extend()
和 lodash.cloneDeep()
,后面文章会详细介绍源码实现,敬请期待!
-- | 和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含子对象 |
---|---|---|---|
赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改变 |
深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 |
js 深拷贝 vs 浅拷贝
进阶系列文章汇总以下,内有优质前端资料,以为不错点个star。
https://github.com/yygmind/blog
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!