1.数据类型数据分为基本数据类型(String、Number、Boolean、Null、Undefined、Symbol)和对象数据类型。基本数据类型的特点:数据直接存放在栈(stack)中引用数据类型的特点:存放的是栈中引用的对象,真正的数据存放在堆内存中。引用数据类型在堆栈中存储指针。这个指针指向这个实体在堆中的起始地址。解释器在查找引用值时,首先获取其在栈中的地址,获取到地址后,再从堆中获取实体。2、浅拷贝和深拷贝深拷贝和浅拷贝只针对Object、Array等引用数据类型。深拷贝和浅拷贝的示意图大致如下:浅拷贝只拷贝对象的指针,不拷贝对象本身,新旧对象仍然共享同一块内存。但是,深拷贝将创建一个相同的对象。新对象不与原对象共享内存,修改新对象不会改变原对象。3.赋值和浅拷贝的区别当我们将对象赋值给一个新的变量时,实际赋值的是对象在栈上的地址,而不是堆中的数据。即两个对象指向同一个存储空间。无论哪个对象发生变化,实际上都是变化的存储空间的内容。因此,这两个对象是链接在一起的。浅拷贝是对象的按位拷贝,它使用原始对象属性值的精确副本创建新对象。如果属性是基本类型,则复制基本类型的值;如果属性是内存地址(引用类型),内存地址被复制,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。前端训练是指默认的拷贝构造函数只对对象进行浅拷贝(逐一拷贝成员),即只拷贝对象空间不拷贝资源。我们先来看两个例子。对比赋值和浅拷贝,会给原来的对象带来什么变化?//对象赋值varobj1={'name':'zhangsan','age':'18','language':[1,[2,3],[4,5]],};varobj2=obj1;obj2.name="lisi";obj2.language[1]=["二","三"];console.log('obj1',obj1)console.log('obj2',obj2)//浅拷贝varobj1={'name':'zhangsan','age':'18','language':[1,[2,3],[4,5]],};varobj3=shallowCopy(obj1);obj3.name="lisi";obj3.language[1]=["二","三"];functionshallowCopy(src){vardst={};for(varpropinsrc){if(src.hasOwnProperty(prop)){dst[prop]=src[prop];}}returndst;}console.log('obj1',obj1)console.log('obj3',obj3)上例中obj1为原始数据,obj2通过赋值运算得到,obj3通过shallow得到复制。我们可以清楚的看到对原始数据的影响,具体请看下表:四、浅拷贝的实现1、Object.assign()Object.assign()方法可以将任意数量的源对象赋值给自己enumerable将属性复制到目标对象,然后返回目标对象。但是Object.assign()执行浅拷贝,复制对对象属性的引用,而不是对象本身。varobj={a:{a:"科比",b:39}};varinitalObj=Object.assign({},obj);initalObj.a.a="wade";console.log(obj.a.a);//wade注意:当对象只有一层时,为深拷贝letobj={username:'kobe'};letobj2=Object.assign({},obj);obj2.username='wade';console.log(obj);//{username:"kobe"}2,Array.prototype.concat()letarr=[1,3,{username:'kobe'}];letarr2=arr.concat();arr2[2].username='韦德';控制台日志(arr);修改新对象将更改为原始对象:3.Array.prototype.slice()letarr=[1,3,{username:'kobe'}];letarr3=arr.slice();arr3[2].username='wade'console.log(arr);同样修改新对象会改变为原对象:Array的slice和concat方法补充说明:Array的slice和concat方法不修改原数组,而是返回一个新数组,是对数组中元素的浅拷贝原始数组。原始数组的元素将根据以下规则进行复制:如果元素是对象引用(不是实际对象),slice会将对象引用复制到新数组中。两个对象引用都指向同一个对象。上海前端培训如果引用的对象发生变化,那么新数组和原数组中的这个元素也会发生变化。对于字符串、数字和布尔值(不是String、Number或Boolean对象),slice将值复制到新数组中。在其他数组中修改这些字符串或数字或布尔值不会影响其他数组。这段话可能很难理解。举个例子,稍微修改一下上面的例子:letarr=[1,3,{username:'kobe'}];letarr3=arr.slice();arr3[1]=2console.log(arr,arr3);五、深拷贝的实现1.JSON.parse(JSON.stringify())letarr=[1,3,{username:'kobe'}];letarr4=JSON.parse(JSON.stringify(arr));arr4[2].username='邓肯';console.log(arr,arr4)原理:使用JSON.stringify将对象转为JSON字符串,然后使用JSON.parse()将字符串解析为对象,生成一个新的对象,打开对象一个新的栈来实现深拷贝。这种方法虽然可以实现数组或者对象的深拷贝,但是不能处理函数。让arr=[1,3,{username:'kobe'},function(){}];让arr4=JSON.parse(JSON.stringify(arr));arr4[2].username='duncan';console.log(arr,arr4)这是因为JSON.stringify()方法将JavaScript值(对象或数组)转换为JSON字符串并且不能接受函数。2.手写递归法递归法实现了深克隆的原理:遍历对象和数组,直到都是基本数据类型,然后复制,这就是深复制。//定义检测数据类型的函数functionfunctioncheckedType(target){returnObject.prototype.toString.call(target).slice(8,-1)}//实现深度克隆---对象/数组functionclone(target){//判断拷贝的数据类型//初始化变量result成为最终克隆的数据letresult,targetType=checkedType(target)if(targetType==='object'){result={}}elseif(targetType==='Array'){result=[]}else{returntarget}//遍历目标数据for(letiintarget){//获取遍历数据结构中每一项的值。letvalue=target[i]//判断目标结构中的每个值是否存在对象/数组if(checkedType(value)==='Object'||checkedType(value)==='Array'){//Theobject/array嵌套在object/array中//继续遍历得到值result[i]=clone(value)}else{//得到的值是基本数据类型或者函数。结果[i]=值;}}返回结果}3。函数库lodash也提供了_.cloneDeep用于DeepCopy。var_=require('lodash');varobj1={a:1,b:{f:{g:1}},c:[1,2,3]};varobj2=_.cloneDeep(obj1);console.log(obj1.b.f===obj2.b.f);//错误
