我一直在写关于JavaScript的文章,我也帮助一些朋友学习JavaScript。这是个人最喜欢的话题之一,但我有点惊讶地发现有多少次,人们弄错了。
JS的问题在于它不像其他语言那样遵循一般模式。例如,在C#中,ref关键字用于定义具有其中一个参数的方法,该参数遵循call by reference的调用方式。
public void useCallByRef(ref int val)
在深入讨论主题之前,让我们看一下JavaScript为我们提供的不同数据类型。
数据类型
有7种数据类型总共分成两个小组,Primitive和Non-Primitive数据类型。
Primitive
Number
String
Boolean
Undefined
Null
Non-Primitive
Arrays
Objects
**注意:**** 类型 Null和Arrays是Object。
Array被视为具有特殊的属性length的Object,构造函数名称属性为“Array” 。
Constructor name is Array
**注意****:了解数据类型有助于更好地理解JavaScript编译器如何处理它们。
按值调用
当变量作为参数传递给函数时,如果对参数进行了任何更改,则原始变量将保持不受影响。这称为按值调用,对于具有基本数据类型的所有值都是如此。
我们来看一个例子:
按值调用示例
上例的输出是20,10。那么,为什么第3行的原始值没有更新?
它实际上很简单,把它想象成当我们将原始值函数传递给updateOriginal函数时,我们直接传递值而不是对值的引用 (分配的内存地址)。对值进行的任何更改都不会更新原始值的实际引用。
按引用调用
当一个变量的引用(地址)而不是它的值被传递给一个函数的参数时,对该参数所做的任何更改都会更新原始的变量引用。这称为引用调用,对于所有具有非基本数据类型的值都是如此。
我们来看一个例子:
按引用调用示例
对于上面的例子中的输出是20.因此,为何值在MyObj中在第3行更新?
考虑指向例如1000 的内存地址的对象以及指向值的该对象的每个属性。
myObj --> 1000(内存地址位置)
所以现在当我们将此对象作为参数传递给函数(updateValue)时,该参数指向相同的引用。
objRef --> 1000
更新属性的值时,它们会反映在整个对象的范围内,因为对象是引用。那么我们如何在不更新对象本身的情况下访问属性并对其进行操作?
解决方案比您想象的更容易,创建对象的副本并对其进行操作。这是不可变编程风格的基础之一。
let myObj = { value:10}function updateValue(objRef){//creates a new object reference and assigns objRef properties to //the object let copy = Object.assign({},objRef); console.log(copy.value); copy.value = 20; console.log(copy.value);}updateValue(myObj);console.log(myObj.value);
使用数组时会发生同样的情况,因为它们在JavaScript中也被视为对象:
let myArray =[1,2,3];function addToArray(arrayRef,value){ arrayRef.push(value);}console.log(myArray);addToArray(myArray,4);console.log(myArray);
输出为[1,2,3],[1,2,3,4],因为arrayRef将指向myArray引用。
因此,在不引起任何副作用的情况下操作数组的最佳方法是创建数组的副本。我们可以使用更高阶的函数map,因为它返回一个新的数组(对象引用)
let myArray =[1,2,3];function addToArray(arrayRef,value){ //returns a new array let newArray = arrayRef.map(val=>val); newArray.push(value); console.log(newArray);}addToArray(myArray,4);console.log(myArray);
输出为[1,2,3,4],[1,2,3]。
还有另一种情况,人们可能会感到困惑,那就是我们正在使用一组对象。
let myArray =[{a:1},{b:2},{c:3}];function addToArray(arrayRef,value){ var newArray = arrayRef.map(val=>val); newArray.push(value); console.log(newArray);}addToArray(myArray,{d:4});console.log(myArray);
所以输出是预期的newArray是[{a:1},{b:2},{c:3},{d:4}]而myArray是[{a:1},{b:2},{ C:3}]。
但是,如果我们尝试在newArray中将{a:1}的值更新为{a:2},该怎么办?
let myArray =[{a:1},{b:2},{c:3}];function addToArray(arrayRef,value){ var newArray = arrayRef.map(val=>val); newArray.push(value); newArray[0].a = 10; console.log(newArray);}addToArray(myArray,{d:4});console.log(myArray);
*注意:在查看输出之前,请仔细考虑一下。
输出是:
那么为什么值更新,即使我们创建了一个新列表?
答案比你想象的要简单,因为我们知道对象是引用,所以当我们将对象推送到数组时,我们推送它的引用,而不是值。因此,对对象所做的任何更改都将直接反映在所有事件中。在我们的情况下,{a:1}在newArray中更新为{a:10},此更改也反映在myArray中。
注意:请尝试修复上面的代码段,我很乐意看到您的回复。
因此,在处理作为Object类型的变量时,应遵循创建副本或新引用。在实际需要时更新方法末尾的主对象。这有助于避免通常很难找到的错误,这甚至可以帮助查明错误。
翻译自:https://medium.com/swlh/call-by-value-call-by-reference-in-javascript-c5738600d9cd