我们都知道js中的变量分为原始数据类型和引用数据类型,其中原始数据类型包括,字符串类型,数字类型,布尔类型,undefined,null,以及后来新添加的标志类型(symbol),引用数据类型就是对象数据类型,他们在计算机中所存储的方式也不近相同,原始数据类型的访问方式是按值访问,而引用数据类型的访问方式是按引用访问,一般来说,各类书籍介绍到这里就会停下来,可是我们仔细想想,什么是按值访问,什么是按引用访问吗?我们今天就来深入了解一下这个问题。
原来,在js中存在着堆和栈,栈存放基础数据类型和指向堆的地址,堆中储存着引用数据类型,每当变量存储一个基本数据类型,便在栈里面建立一个新的空间,每当要引用这个数据时,js后台便会直接引用这个值类型,这也是按值引用的意思,而对象要复杂一些,每当建立一个新的对象实例,计算机便会自动在堆中建立一个引用数据类型空间,然后在栈里存放一个指向这个地址空间的地址,当调用对象数据时,首先是调用这个指针所在的地址,这就是按引用访问。
这就是按值访问和按引用访问的真实意义,可是这又会带来新的问题,也就是俗称的深拷贝和浅拷贝的问题,所谓深浅拷贝的区分,也就是,在复制一个变量的值后,修改复制后的值,原先的值是否会被改变,这样说有点抽象我们通过一个图表来看看:
浅拷贝:
栈内存 堆内存
name val val
a 堆地址1 ============> [0,1,2,3,4]
b = a 进行拷贝时,其实复制的是a的应用地址,并非堆里面的值。
栈内存 堆内存
name val val
a 堆地址1 ============> [0,1,2,3,4]
b 堆地址1 ============>
当我们a[0] = 1进行数组修改时,a和b由于指向同一个地址,所以b自然也收到了影响,这就是浅拷贝
栈内存 堆内存
name val val
a 堆地址1 ============> [1,1,2,3,4]
b 堆地址1 ============>
如果在堆内存中也开辟一个新的内存专门为b存放值,就达到了深拷贝的效果
栈内存 堆内存
name val val
a 堆地址1 ============> [0,1,2,3,4]
b 堆地址1 ============> [0,1,2,3,4]
从这几个表格就可以清楚的看出两者的区别,因为这个特性,可能会对我们写代码造成影响,这点需要牢记,如何解决呢,要想实现深拷贝有以下几种方法可以解决:
1.采用递归拷贝所有的层级属性
2.JSON对象来实现
3.jQuery的extend方法实现深拷贝
4.手动实现(较为笨拙)
5.for in遍历实现
如果感兴趣可以自己去查询相关资料,以后有时间我也会详细的为大家解释一番。
注意:深拷贝只针对较为复杂的对象类型数据,一般的数据复制并不需要考虑这些问题。
数据类型传输中还有一点是极易混淆人的,就是函数参数的传递方式,js中所有的参数都是按值传递的,并不像上面不同数据类型有着不同的访问方式,
有些迷糊?没关系,我们看看几个红宝书上给的例子:
(原文)在按值传递参数时,值会被复制到一个局部变量(arguments对象中的一个槽位),在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地的修改会反映到函数外部
<script>
function addTen(num) {
num +=10;
return num;
}
let count = 20;
let result = addTen(count);
console.log(count);
console.log(result);
</script>
打印结果:
这里显示如果输入的参数数据类型是原始数据类型,也即按值传递参数时对本地的修改不会传递到函数外部(也既count的值并没有修改,如果修改了,说明参数传递是按照值传递的)这里还好理解一些,下面则是容易混淆的部分,如果输入的是对象这个复杂的数据类型呢?我们来看看例子:
<script>
function setName(obj) {
obj.name = "Nicholas";
}
let person = new Object();
setName(person);
console.log(person.name);
</script>
打印结果:
可以清楚的看到在外部定义的person对象因为函数内部的参数传递发生了变化,这表明参数是按照引用传递的,可是我们前面才刚刚讲到函数的参数是按值传递的,这不是前后矛盾吗?我们再看看下面这个例子:
<script>
function setName(obj) {
obj.name = "Nicholas";
obj = new Object();
obj.name = "greg"
}
let person = new Object();
setName(person);
console.log(person.name);
</script>
我们在函数内部添加了两端代码再来看看打印结果:
我们看到结果并未改变,如果是按照引用传输,在函数内部的属性值修改会修改指针导致输出的值应该是greg才对,这表明函数的参数传输方式依然是按值传输,只不过当obj在函数内部被重写时,它变成了一个指向本地对象的指针,而那个本地对象在函数执行结束时就被销毁了。