JavaScript的变量类型
基本类型: 5种基本数据类型Undefined
、Null
、Boolean
、Number
和 String
,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。
var a = 1;
b = a; // 栈内存会开辟一个新的内存空间,此时b和a都是相互独立的
b = 2;
console.log(a); // 1
引用类型: 存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象
,数组
等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。
浅拷贝与深拷贝
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。
JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和源对象各自的操作互不影响。
如何区分深拷贝与浅拷贝:简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝;如果B没变,那就是深拷贝。
浅拷贝
- 仅仅是指向被拷贝的内存地址,如果原地址中对象被改变了,那么浅拷贝出来的对象也会相应改变。新旧对象还是共享同一块内存。
深拷贝
- 会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
实现浅拷贝的方式
for…in只循环第一层
/* 只复制一层的浅拷贝 */
function simpleCopy(obj1) {
//判断传入的参数是数组还是对象
//如果是数组,返回的也是数组; 如果是对象则返回对象
var obj2 = Array.isArray(obj1) ? [] : {};
for(let i in obj1) {
obj2[i] = obj1[i];
}
return obj2
}
只拷贝一层浅拷贝:
拷贝的对象的第一层属性修改时,不会影响原对象
但是拷贝的对象的包括第二层属性之内的属性修改时,会影响原对象
测试数组:
// 测试数组
let arr1 = [1,[2,6],3,4];
let arr2 = simpleCopy(arr1);
console.log('未改变属性之前的数组:');
console.log('待拷贝的数组:',arr1);
console.log('拷贝的数组:',arr2);
arr2[0] = 0;
arr2[1][1] = 10;
console.log('改变属性之前的数组:');
console.log('待拷贝的数组:',arr1);
console.log('拷贝的数组:',arr2);
测试对象:
//测试对象
// 定义对象obj1,一会对obj1进行浅复制
var obj1 = {
a: 1,
b: 2,
c: {
d: 3
}
}
// 对obj1进行浅拷贝
let obj2 = simpleCopy(obj1);
//更改obj2的属性的值
obj2.a = 3;
obj2.c.d = 4;
console.log('obj1.a:',obj1.a);
console.log('obj2.a:',obj2.a);
console.log('obj1.c.d:',obj1.c.d);
console.log('obj2.c.d:',obj2.c.d);
Object.assign()方法
// 定义对象obj1,一会对obj1进行浅复制
var obj1 = {
a: 1,
b: 2,
c: {
d: 3
}
}
// 对obj1进行浅拷贝
var obj2 = Object.assign(obj1);
//更改obj2的属性的值
obj2.a = 3;
obj2.c.d = 4;
console.log('obj1.a:',obj1.a);
console.log('obj2.a:',obj2.a);
console.log('obj1.c.d:',obj1.c.d);
console.log('obj2.c.d:',obj2.c.d);
完全的浅拷贝---->拷贝对象的每一层属性改变都会引起原对象属性的改变
注意与上一个只拷贝的一层进行对比
直接用=赋值
// 定义对象obj1,一会对obj1进行浅复制
var obj1 = {
a: 1,
b: 2,
c: {
d: 3
}
}
// 对obj1进行浅拷贝
var obj2 = obj1
//更改obj2的属性的值
obj2.a = 3;
obj2.c.d = 4;
console.log('obj1.a:',obj1.a);
console.log('obj2.a:',obj2.a);
console.log('obj1.c.d:',obj1.c.d);
console.log('obj2.c.d:',obj2.c.d);
拷贝对象时与使用Object.assign()的结果一致
//待拷贝的数组
let arr1 = [0,1,2,3,4];
//进行拷贝
let arr2 = arr1;
console.log(arr1===arr2);
//改变拷贝的对象的值
arr1[0] = 1;
console.log(arr1,arr2);
用slice(start,end)实现对数组的浅拷贝
- Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。
- 当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝
- 当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝
var arr1 = ["1","2","3"];
var arr2 = arr1.slice(0);
arr2[1] = "9";
console.log("数组的原始值:" + arr1 );
console.log("数组的新值:" + arr2 );
用concat()实现对数组的浅拷贝
- 当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝
- 当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝
var arr1 = ["1","2","3"];
var arr2 = arr1.concat();
arr2[1] = "9";
console.log("数组的原始值:" + arr1 );
console.log("数组的新值:" + arr2 );
var arr1 = [{a:1},{b:2},{c:3}];
var arr2 = arr1.concat();
arr2[0].a = "9";
console.log("数组的原始值:" + arr1[0].a ); // 数组的原始值:9
console.log("数组的新值:" + arr2[0].a ); // 数组的新值:9
使用扩展运算符…实现浅拷贝
- 当value是基本数据类型,比如String,Number,Boolean时,是可以使用拓展运算符进行深拷贝的
- 当value是引用类型的值,比如Object,Array,引用类型进行深拷贝也只是拷贝了引用地址,所以属于浅拷贝。
var car = {brand: "BMW", price: "380000", length: "5米"}
var car1 = { ...car, price: "500000" }
console.log(car1); // { brand: "BMW", price: "500000", length: "5米" }
console.log(car); // { brand: "BMW", price: "380000", length: "5米" }
实现深拷贝的方式
采用递归去拷贝所有层级属性
/* 采用递归进行所有层级拷贝 */
// 注:typeof [1,2,3]----->object
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {};
if(obj && typeof obj === 'object') {
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
//判断ojb子元素是否为对象,如果是,递归复制
if(obj[key] && typeof obj[key] === 'object') {
objClone[key] = deepClone(obj[key]);
}else {
//如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
测试数组:
// 测试数组
let arr1 = [1,[2,6],3,4];
let arr2 = deepClone(arr1);
console.log('未改变属性之前的数组:');
console.log('待拷贝的数组:',arr1);
console.log('拷贝的数组:',arr2);
arr2[0] = 0;
arr2[1][1] = 10;
console.log('未改变属性之前的数组:');
console.log('待拷贝的数组:',arr1);
console.log('拷贝的数组:',arr2);
测试对象:
//测试对象
// 定义对象obj1,一会对obj1进行浅复制
var obj1 = {
a: 1,
b: 2,
c: {
d: 3
}
}
// 对obj1进行深拷贝
let obj2 = deepClone(obj1);
//更改obj2的属性的值
obj2.a = 8;
obj2.c.d = 4;
console.log('obj1.a:',obj1.a);
console.log('obj2.a:',obj2.a);
console.log('obj1.c.d:',obj1.c.d);
console.log('obj2.c.d:',obj2.c.d);
通过JSON对象来实现深拷贝
- 缺点:无法实现对对象中方法的深拷贝,会显示为undefined
/* 定义进行深拷贝 */
function deepClone2(obj) {
var _obj = JSON.stringify(obj),
objClone = JSON.parse(_obj);
return objClone;
}
测试:
// 定义对象obj1,一会对obj1进行浅复制
var obj1 = {
a: 1,
b: 2,
c: {
d: 3
}
}
//进行深拷贝
let obj2 = deepClone2(obj1);
console.log('未修改属性之前的obj1,obj2:')
console.log('obj1:',obj1);
console.log('obj2:',obj2)
//修改属性值
obj2.a = 10;
obj2.c.d = 100;
console.log('修改属性之后的obj1,obj2:')
console.log('obj1:',obj1);
console.log('obj2:',obj2)
通过jQuery的extend()方法实现深拷贝
var array = [1,2,3,4];
// true为深拷贝,false为浅拷贝
var newArray = $.extend(true,[],array);
手动实现深拷贝
let obj1 = {
a: 1,
b: 2
}
let obj2 = {
a: obj1.a,
b: obj1.b
}
obj2.a = 3;
console.log(obj1.a); // 1
console.log(obj2.a); // 3
如果对象的value是基本类型的话,也可以用Object.assign()来实现深拷贝,但是要把它赋值给一个空对象
var obj1 = {
a: 1,
b: 2
}
var obj2 = Object.assign({}, obj1); // obj1赋值给一个空{}
obj2.a = 3;
console.log(obj1.a);// 1
console.log(obj2.a);// 3