【javascript】值传递 pass by value
最近有一个需求,我们先看一个小的例子
var student = {
name: \'Catherine\',
age: 19,
address: {
province: \'江苏\',
city: \'南昌\'
},
landLinePhone: {
district: 028,
number: 553478909
}
}
改变这个对象的province属性值为’江西’,而且是原对象,并不是返回一个新的对象, 可能你会说直接xx.xx.xx.province = ‘江西西’就可以了,但是我实际上遇到的需求,是这个特殊的province 字段是可能存在不同的level 层级的。 就是说我实际的需求是这个对象不是规范化的,他可能是这样 的 { a: {province: \'xx\'}, b: {c: {province: \'yy\'}}, d:{e: {f: {province: \'xxx\'}}} }
, 所以我是这样做的,一个递归方法:
var modifyProvince = (obj) => {
for(let key in obj ) {
if(typeof(obj[key]) === \'string\' && key ===\'province\') {
console.log(obj[key], student.address.province, obj === student.address)
obj[key]=\'江西西\'
}
//请注意,typeof(null) === \'object\',平时我们用的时候要严谨一点
if(typeof(obj[key]) ===\'object\') {
modifyProvince(obj[key])
}
}
}
modifyProvince(student) //输出结果是什么?没错,这个值被改变了,没有意外
我们来修改一下这个方法,因为现在我们的数据不需要landLinePhone这个字段了,要将landLinePhone的值改为null:
var modifyLandLinePhone = (obj) => {
for(let key in obj ) {
let subKeys = Object.keys(obj[key])
if(typeof(obj[key]) === \'object\' && subKeys.length===2 && subKeys.join(\';\')===\'district;number\') {
console.log(obj, \'here is the landlinePhone\')
obj = null
}
//请注意,typeof(null) === \'object\',平时我们用的时候要严谨一点
//这里如果用if会报错,因为上面obj已经置为null了,下面typeof 为\'object\',进入if语句后 null.landLinePhone 报错
else if(typeof(obj[key]) ===\'object\') {
modifyLandLinePhone(obj[key])
}
}
}
但是我们打印变量student,发现landLinePhone 不是空,对象没有发生任何变化。
这就引出来了 pass by value, 按值传递。我们知道js里面除了 Number String Boolean Null Undefined 5种基础数据类型,暂且把其他的数据结构叫做引用类型(引用类型值是指的是那些可能由多个值构成的对象,保存在内存中的对象。摘自《Javascript高级程序设计》)
重点内容:
基本数据类型是寸在栈里面的。
引用类型是寸在堆内存里面的。
值传递,我理解成为两种:
1,赋值
A, 基本类型,基本类型是寸在栈里面的。 var a = 5
var b = 5
这时候在栈里面有两个空间,分别存储了5.
请看下图:
这时候我们做一个操作,声明一个变量c
var c
var c = a
这时候实际上是复制了一个c的副本,基本数据类型的值是不能被改变的。c 和 a 的5 是完全独立的,他们只是副本,看起来是一样但是不会相互影响。这时候的情况类似下面的图:
b,引用类型,JS不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间,我们在操作对象的时候实际上是操作对象的引用而不是实际的对象,引用类型的值是按引用访问的。 var obj1 = new Object()
var obj2 = new Object()
模拟一下内存中的情况,请看下图:
可以明显的看到,实际上我们操作的只是引用,或者地址。
我们来做一个操作和上面的基本数据类型同样的操作:
var obj3
obj3 = obj1
然后
obj3 = null
//what is obj1 now ?
我们将obj1赋值给obj3, 我们上面说了操作对象实际上操作只是引用或者指针,这个引用是存在栈里面的,而它指向的就是堆中的数据。如果引用类型obj1传递的是同一个指针,那么这时候obj1也为null,但是obj1没有变化。
实际上赋值的时候,将指向obj1的指针复制了一份,然后赋值给obj3, 我们先看将obj1赋值给 obj3的时候发生了什么?看下图
所以我们如果执行
obj3 = null
就相当于obj3这个变量存储的指针已经不再指向obj1了,他已经和obj1指向堆内存中的对象没有任何关系了。
2,传递参数
好了,如果你看了上面的内容,传递参数的这一块可以不用看了,比如我们调用一个函数,传递的参数是基本数据类型,传递的值的副本,如果传递的是引用类型,传递的是指向这个引用类型数据的指针的副本给函数内部的一个局部变量。这就可以解释了文章开篇的那个两个现象,第一个函数能够修改值,虽然传递的是指向对象的指针副本,但是他没有将这个副本引用给移除,是动态修改了值,而第二个是直接将局部变量(存储了指针的副本)给设置null,那这个函数局部变量和原来的对象没有任何关系了。
function modifyObj (obj) {
obj = null
}
var temp = {name: \'Catherine\'}
modifyObj(temp)
//then what is temp??????
function setName(obj) {
obj.name="kevin";
obj =new Object();
obj.name="Kesha"
}
var person = new Object()
setName(person)
//then what is person.name??????
var a = [1,2,3]
var b = a[0]
a[0] = 11
// then what is b??????
所以我们第二个例子为什么没改动成功,有两种方式
第一种方案是通过lodash的mapValues方法去实现(还是要递归),但是我工作上遇到的问题是这个特殊的对象也有可能在数组里面,mapValues这个方法适用于数组是有问题的。
_.mapValues([], function(val, key){ return val })//返回的是{}
没有数组的情况是可以通过mapValues来做,有的话就不行。
第二种方案是JSON.stringify(obj, function(key, val){ //manipulate code }) 最后再JSON.parse(),这是可行的。
参考资料:
《javascript高级程序设计》第四章