基元类型(数字、字符串等)是通过值传递的,但对象是未知的,因为它们既可以通过值传递(如果我们认为持有对象的变量实际上是对对象的引用),也可以通过引用传递(当我们认为对象的变量持有对象本身)。
虽然最后这并不重要,但我想知道通过约定来表达论点的正确方式是什么。是否有JavaScript规范的摘录,该规范定义了与此相关的语义?
基元类型(数字、字符串等)是通过值传递的,但对象是未知的,因为它们既可以通过值传递(如果我们认为持有对象的变量实际上是对对象的引用),也可以通过引用传递(当我们认为对象的变量持有对象本身)。
虽然最后这并不重要,但我想知道通过约定来表达论点的正确方式是什么。是否有JavaScript规范的摘录,该规范定义了与此相关的语义?
当前回答
我在AirBNB风格指南中找到了最简洁的解释:
基本体:访问基本体类型时,直接处理其价值一串数字布尔型无效的未定义
例如。:
var foo = 1,
bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
复杂:访问复杂类型时,需要处理对其值的引用对象大堆作用
例如。:
var foo = [1, 2],
bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9
也就是说,基本类型是通过值传递的,复杂类型是通过引用传递的。
其他回答
这里有一些关于JavaScript中使用术语“通过引用传递”的讨论,但要回答您的问题:
对象通过引用自动传递,无需特别声明
(摘自上述文章。)
我的两分钱。。。这是我理解它的方式。(如果我错了,请随时纠正我)
是时候抛弃你所知道的关于传递值/引用的一切了。
因为在JavaScript中,它是通过值传递还是通过引用传递都不重要。重要的是变异与传递给函数的参数赋值。
好吧,让我尽力解释一下我的意思。假设您有几个对象。
var object1 = {};
var object2 = {};
我们所做的是“任务”。。。我们为变量“object1”和“object2”分配了两个单独的空对象。
现在,假设我们更喜欢对象1。。。因此,我们“分配”一个新变量。
var favoriteObject = object1;
接下来,无论出于什么原因,我们决定更喜欢对象2。所以,我们做了一点重新分配。
favoriteObject = object2;
对象1或对象2未发生任何情况。我们根本没有改变任何数据。我们所做的只是重新分配我们最喜欢的对象。重要的是要知道object2和favoriteObject都分配给了同一个对象。我们可以通过这些变量中的任何一个来改变这个对象。
object2.name = 'Fred';
console.log(favoriteObject.name) // Logs Fred
favoriteObject.name = 'Joe';
console.log(object2.name); // Logs Joe
好了,现在让我们看一下像字符串这样的原语
var string1 = 'Hello world';
var string2 = 'Goodbye world';
再次,我们选择一个最喜欢的。
var favoriteString = string1;
我们的favoriteString和string1变量都分配给“Helloworld”。现在,如果我们想更改我们的收藏夹字符串???会发生什么???
favoriteString = 'Hello everyone';
console.log(favoriteString); // Logs 'Hello everyone'
console.log(string1); // Logs 'Hello world'
噢。。。。发生了什么。我们无法通过更改收藏夹字符串来更改字符串1。。。为什么?因为我们没有更改字符串对象。我们所做的只是将favoriteString变量“RE ASSIGN”为一个新字符串。这实际上将其与字符串1断开。在前面的示例中,当我们重命名对象时,我们没有分配任何内容。(好吧,不是变量本身,……但是,我们确实将name属性分配给了一个新字符串。)相反,我们对保持2个变量和基础对象之间连接的对象进行了变异。(即使我们想修改或变异字符串对象本身,我们也不能这样做,因为字符串在JavaScript中实际上是不可变的。)
现在,转到函数和传递参数。。。。当你调用一个函数并传递一个参数时,你所做的基本上是对一个新变量的“赋值”,它的工作方式与你使用等号(=)赋值的方式完全相同。
举这些例子。
var myString = 'hello';
// Assign to a new variable (just like when you pass to a function)
var param1 = myString;
param1 = 'world'; // Re assignment
console.log(myString); // Logs 'hello'
console.log(param1); // Logs 'world'
现在,同样的事情,但有一个函数
function myFunc(param1) {
param1 = 'world';
console.log(param1); // Logs 'world'
}
var myString = 'hello';
// Calls myFunc and assigns param1 to myString just like param1 = myString
myFunc(myString);
console.log(myString); // logs 'hello'
好的,现在让我们举几个使用对象的例子。。。首先,没有函数。
var myObject = {
firstName: 'Joe',
lastName: 'Smith'
};
// Assign to a new variable (just like when you pass to a function)
var otherObj = myObject;
// Let's mutate our object
otherObj.firstName = 'Sue'; // I guess Joe decided to be a girl
console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Sue'
// Now, let's reassign the variable
otherObj = {
firstName: 'Jack',
lastName: 'Frost'
};
// Now, otherObj and myObject are assigned to 2 very different objects
// And mutating one object has no influence on the other
console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Jack';
现在,同样的事情,但有一个函数调用
function myFunc(otherObj) {
// Let's mutate our object
otherObj.firstName = 'Sue';
console.log(otherObj.firstName); // Logs 'Sue'
// Now let's re-assign
otherObj = {
firstName: 'Jack',
lastName: 'Frost'
};
console.log(otherObj.firstName); // Logs 'Jack'
// Again, otherObj and myObject are assigned to 2 very different objects
// And mutating one object doesn't magically mutate the other
}
var myObject = {
firstName: 'Joe',
lastName: 'Smith'
};
// Calls myFunc and assigns otherObj to myObject just like otherObj = myObject
myFunc(myObject);
console.log(myObject.firstName); // Logs 'Sue', just like before
好的,如果你通读了这篇文章,也许你现在对JavaScript中函数调用的工作方式有了更好的理解。不管什么是通过引用还是通过值传递。。。重要的是赋值与变异。
每次将变量传递给函数时,都会“赋值”参数变量的名称,就像使用等号(=)一样。
始终记住等号(=)表示赋值。始终记住,在JavaScript中向函数传递参数也意味着赋值。它们是相同的,并且这两个变量以完全相同的方式连接(也就是说它们不是,除非你认为它们被分配给了同一个对象)。
“修改变量”影响不同变量的唯一时间是基础对象发生变化时(在这种情况下,您没有修改变量,而是修改对象本身)。
区分对象和基元是没有意义的,因为它的工作方式与没有函数的情况完全相同,只是使用等号分配给一个新变量。
唯一的错误是当传递到函数中的变量的名称与函数参数的名称相同时。当发生这种情况时,您必须将函数内的参数视为该函数专用的一个全新变量(因为它是)
function myFunc(myString) {
// myString is private and does not affect the outer variable
myString = 'hello';
}
var myString = 'test';
myString = myString; // Does nothing, myString is still 'test';
myFunc(myString);
console.log(myString); // Logs 'test'
在JavaScript中,值的类型仅控制该值是由值副本分配还是由引用副本分配。
基本值始终由值副本分配/传递:
无效的未定义一串数字布尔型ES6中的符号
复合值始终由引用副本分配/传递
物体阵列作用
例如
var a = 2;
var b = a; // `b` is always a copy of the value in `a`
b++;
a; // 2
b; // 3
var c = [1,2,3];
var d = c; // `d` is a reference to the shared `[1,2,3]` value
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]
在上面的代码段中,因为2是标量原语,所以a保存该值的一个初始副本,而b被分配了该值的另一个副本。更改b时,绝对不能更改a中的值。
但c和d都是对同一共享值[1,2]的单独引用,这是一个复合值。需要注意的是,c和d都没有“拥有”[1,2,3]值——两者都只是对该值的对等引用。因此,当使用任意一个引用来修改(.push(4))实际的共享数组值本身时,它只影响一个共享值,并且两个引用都将引用新修改的值[1,2,3,4]。
var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// later
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
当我们赋值b=[4,5,6]时,我们没有做任何事情来影响a仍然引用的位置([1,2,3])。要做到这一点,b必须是指向a的指针,而不是对数组的引用——但JS中不存在这样的功能!
function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// later
x = [4,5,6];
x.push( 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // [1,2,3,4] not [4,5,6,7]
当我们传入参数a时,它将一个引用的副本分配给x。x和a是指向相同[1,2,3]值的独立引用。现在,在函数内部,我们可以使用该引用来改变值本身(push(4))。但是当我们赋值x=[4,5,6]时,这不会影响初始引用a指向的位置——仍然指向(现在已修改)[1,2,3,4]值。
要有效地通过值副本传递复合值(如数组),需要手动复制它,这样传递的引用就不会指向原始值。例如:
foo( a.slice() );
可以通过引用副本传递的复合值(对象、数组等)
function foo(wrapper) {
wrapper.a = 42;
}
var obj = {
a: 2
};
foo( obj );
obj.a; // 42
这里,obj充当标量基元属性a的包装器。当传递给foo(..)时,将传入obj引用的副本,并将其设置为wrapper参数。我们现在可以使用包装器引用来访问共享对象,并更新其属性。函数完成后,obj.a将看到更新的值42。
来源
变量不“保存”对象;它有一个参考。您可以将该引用分配给另一个变量,现在两者都引用同一个对象。它总是按值传递(即使该值是引用…)。
无法更改作为参数传递的变量所持有的值,如果JavaScript支持通过引用传递,这是可能的。
MDN文档对其进行了清晰的解释,但并不过于冗长:
函数调用的参数是函数的参数。参数按值传递给函数。如果功能发生变化参数的值,此更改不会在全局或调用函数。但是,对象引用也是值,并且它们是特殊的:如果函数更改引用对象的财产,该更改在函数外部可见,(…)
资料来源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description