js知识版图-引用类型赋值、浅拷贝、深拷贝

JavaScript015

js知识版图-引用类型赋值、浅拷贝、深拷贝,第1张

我们先来复习下数据类型相关知识:

此处引申知识点:基本数据类型

这里所说的赋值是对象的引用赋值,当我们把一个对象赋值给一个新的变量时,赋的其实是该对象在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

由此可见浅拷贝只解决了第一层的问题,如果接下去的值中还有对象,两者享有相同的地址。

深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

1.浅拷贝:复制一份引用,所有引用对象都指向一份数据,并且都可以修改这份数据。

2.深拷贝(复杂):复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。

一、数组的深浅拷贝

在使用JavaScript对数组进行操作的时候,我们经常需要将数组进行备份,事实证明如果只是简单的将它赋予其他变量,那么我们只要更改其中的任何一个,然后其他的也会跟着改变,这就导致了问题的发生。

var arr = ["One","Two","Three"] var arrto = arr arrto[1] = "test" document.writeln("数组的原始值:" + arr + "<br />")//Export:数组的原始值:One,test,Three document.writeln("数组的新值:" + arrto + "<br />")//Export:数组的新值:One,test,Three

像上面的这种直接赋值的方式就是浅拷贝,很多时候,这样并不是我们想要得到的结果,其实我们想要的是arr的值不变,不是吗?

方法一:js的slice函数

var arr = ["One","Two","Three"] var arrtoo = arr.slice(0) arrtoo[1] = "set Map" document.writeln("数组的原始值:" + arr + "<br />")//Export:数组的原始值:One,Two,Three document.writeln("数组的新值:" + arrtoo + "<br />")//Export:数组的新值:One,set Map,Three

方法二:js的concat方法

var arr = ["One","Two","Three"] var arrtooo = arr.concat() arrtooo[1] = "set Map To" document.writeln("数组的原始值:" + arr + "<br />")//Export:数组的原始值:One,Two,Three document.writeln("数组的新值:" + arrtooo + "<br />")//Export:数组的新值:One,set Map To,Three

二、对象的深浅拷贝

var a={name:'yy',age:26} var b=new Object() b.name=a.name b.age=a.age a.name='xx' console.log(b)//Object { name="yy", age=26} console.log(a)//Object { name="xx", age=26}

就是把对象的属性遍历一遍,赋给一个新的对象。

var deepCopy= function(source) { var result={} for (var key in source) { result[key] = typeof source[key]==='object'? deepCoyp(source[key]): source[key] }return result }

举一个jQuery中的例子:

jQuery.extend = jQuery.fn.extend = function() {//1.将extend方法扩展到JQ(函数)下边:扩展静态方法 //2. jQuery.fn.extend 把extend扩展到jq.fn下 且jQuery.fn = jQuery.prototype 扩展实例方法 // 1.2.功能相似 var options, name, src, copy, copyIsArray, clone, //定义一些变量 target = arguments[0] || {},//目标元素是【0】第一个元素$.extend( a , { name : 'hello' } , { age : 30 } ) i = 1, //第一个元素的位置 length = arguments.length,//第一个个对象的元素 deep = false//是否是深拷贝 默认 false不是 // Handle a deep copy situation 看是不是深拷贝情况 if ( typeof target === "boolean" ) { //是布尔值deep = target target = arguments[1] || {}//目标元素是第二个$.extend( true , a , b ) // skip the boolean and the target i = 2 } // Handle case when target is a string or something (possible in deep copy) 看参数正确不 if ( typeof target !== "object" &&!jQuery.isFunction(target) ) { // 当目标不是对象或者不是函数的时候 target = {}//变成一个空的jason } // extend jQuery itself if only one argument is passed看是不是插件情况 if ( length === i ) { //只写了一个对象 要把这个对象扩展到jq源码上 静态方法 或者是实例方法 target = this//this 是$ 或者 $() --i } // 可能有多个对象情况 for ( i <lengthi++ ) { // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) {//看后边的对象是否都有值 // Extend the base object for ( name in options ) { src = target[ name ] copy = options[ name ]// Prevent never-ending loop if ( target === copy ) {//防止循环引用 continue//跳出本次循环继续执行// $.extend( a , { name : a } ) )循环引用 a也是一个对象 } // Recurse if we're merging plain objects or arrays深拷贝 if ( deep &&copy &&( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {// 是深拷贝 且需有var b = { name : { age : 30 } }且b必须是对象自变量(jason) 或者是个数组//递归if ( copyIsArray ) { //数组copyIsArray = false clone = src &&jQuery.isArray(src) ? src : []//定义一个空数组 } else {//jasonclone = src &&jQuery.isPlainObject(src) ? src : {}//看原有的属性有没有且是不是jason定义一个空jason}// var a = { name : { job : 'it' } }看有没有原有的属性 有的话在原有的上边添加// var b = { name : {age : 30} } // $.extend( true , a , b )//a继承b// console.log( a )a{ name:{ job : 'it' ,age : 30}} 如果只有一个{} 则只有,age : 30// Never move original objects, clone(a) themtarget[ name ] = jQuery.extend( deep, clone, copy )//调用函数本身进行进一步的递归处理 // Don't bring in undefined values浅拷贝 } else if ( copy !== undefined ) {target[ name ] = copy//直接复制因为里边没有对象 } } } } // Return the modified object return target }

近日在写一个新功能的时候,其实没有很难,数据交互有一点复杂,发灰度之后,自测出一个小bug,虽然整体上似乎无伤大雅,但是发现了就犯强迫症想要解决,仔细debug了一晚上+一上午,甚至把自己组件拆分,数据传递的逻辑都重新理了一遍,实在没发现什么问题,一直看断点,数据在某一个阶段没有按照理想的状态变化,但是一直没明白是为什么,原本没往深浅拷贝的方面去想,一直纠结于各个组件之间的state的变化等等,最后突然灵光了一把,原来是这么小的错误,可把我愁了半天。就是某onchange函数中 把state里的数组赋值给新的变量,新的变量对数组做了一些操作,但却把state里面原本的值也修改了 (这是之前一直没发现的),正好其他地方state里的此数组是需要修改的,所以整体上似乎没产生什么影响,但在那个onchange函数里就改变了,会影响第一次渲染之后的再次渲染数据显示有错误。解决了也是终于舒畅了~特此记录,一定要重视对象的深浅拷贝!!一不小心就用错了酿成大祸!

 js中储存对象都是存引用地址,所以浅拷贝会导致两个变量指向同一块内存地址。数组的赋值其实相当于给了索引,改变其中一个变量其他的引用其他都会改变。如下为浅拷贝

// var a = [1,2,3]

// var b = a  //此步不是赋值,而是将a的引用赋给b,所以改变b也会改变a

// b[0]=4

// a为[4,2,3]

// b为[4,2,3]

总的来说 :原始参数(比如一个具体的数字)被作为值传递给函数,如果被调用函数改变了这个参数的值,这样的改变不会影响到全局或调用函数。但当你传递一个对象(js里数组也是对象)到一个函数,如果在函数里面改变了这个参数的内容,那么这个改变在外部是可见的,也就是会影响到全局。

深拷贝数组的方法:

(1)slice函数,newArr = arr.slice(0)

(2)concat函数,newArr = [].concat(arr,arr2,...)

(3)assign函数(对象),newObj = object.assign({},obj)

但以上三种方式都是对对象第一层的深拷贝,第二层之后还是浅拷贝,要实现多维数组的深拷贝可以用:

newArr = JSON.parse(JSON.stringify(arr))