原始类型(数字,字符串等)按值传递,但对象是未知的,因为它们可以是值传递(如果我们认为持有对象的变量实际上是对象的引用)并且通过引用传递(当我们认为对象的变量保存对象本身时)。
虽然最后并不重要,但我想知道提交传递约定的参数的正确方法是什么。是否有JavaScript规范的摘录,它定义了与此相关的语义?
它在JavaScript中很有趣。考虑这个例子:
function changeStuff(a, b, c)
{
a = a * 10;
b.item = "changed";
c = {item: "changed"};
}
var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num);
console.log(obj1.item);
console.log(obj2.item);
这会产生输出:
10
changed
unchanged
obj1
根本不是参考,那么改变obj1.item
对函数外的obj1
没有影响。num
将是100
,obj2.item
将阅读"changed"
。相反,情况是传入的项目是按值传递的。但是通过值传递的项本身就是一个引用。从技术上讲,这被称为call-by-sharing。
实际上,这意味着如果您更改参数本身(与num
和obj2
一样),则不会影响输入参数的项目。但是如果你更改参数的INTERNALS,它将传播回来(与obj1
一样)。
有关复制,传递和按值和参考进行比较的详细说明,请参阅“JavaScript:The Definitive Guide”一书的this chapter。
在我们通过引用离开操作对象和数组的主题之前,我们需要清楚一个命名点。
短语“通过引用传递”可以有几个含义。对于某些读者来说,该短语指的是一种函数调用技术,它允许函数为其参数赋值新值,并使这些修改后的值在函数外可见。这不是本书中使用的术语。
这里,我们只是简单地将对象或数组的引用 - 而不是对象本身 - 传递给函数。函数可以使用引用来修改对象或数组元素的属性。但是,如果函数使用对新对象或数组的引用覆盖引用,则该修改在函数外部不可见。
熟悉该术语其他含义的读者可能更喜欢说对象和数组是按值传递的,但传递的值实际上是一个引用而不是对象本身。
现在,人们喜欢无休止地争论“通过引用传递”是否是描述Java等人的正确方法。实际上。关键在于:
- 传递对象不会复制对象。
- 传递给函数的对象可以通过函数修改其成员。
- 传递给函数的原始值不能被函数修改。制作副本。
在我的书中,这被称为通过引用传递。
- Brian Bi - Which programming languages are pass by reference?
这是对此的反驳:
这是对值传递和传递引用(JavaScript)的更多解释。在这个概念中,他们讨论的是通过引用传递变量并通过引用传递变量。
按值传递(原始类型)
var a = 3;
var b = a;
console.log(a); // a = 3
console.log(b); // b = 3
a=4;
console.log(a); // a = 4
console.log(b); // b = 3
通过引用传递(对象)
var c = { "name" : "john" };
var d = c;
console.log(c); // { "name" : "john" }
console.log(d); // { "name" : "john" }
c.name = "doe";
console.log(c); // { "name" : "doe" }
console.log(d); // { "name" : "doe" }
c
,它指向一些内存,比如说(0x012)。d
指向相同的位置(0x012)。特殊情况,通过引用传递(对象)
c = {"name" : "jane"};
console.log(c); // { "name" : "jane" }
console.log(d); // { "name" : "doe" }
将参数传递给JavaScript中的函数类似于通过C中的指针值传递参数:
/*
The following C program demonstrates how arguments
to JavaScript functions are passed in a way analogous
to pass-by-pointer-value in C. The original JavaScript
test case by @Shog9 follows with the translation of
the code into C. This should make things clear to
those transitioning from C to JavaScript.
function changeStuff(num, obj1, obj2)
{
num = num * 10;
obj1.item = "changed";
obj2 = {item: "changed"};
}
var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num);
console.log(obj1.item);
console.log(obj2.item);
This produces the output:
10
changed
unchanged
*/
#include <stdio.h>
#include <stdlib.h>
struct obj {
char *item;
};
void changeStuff(int *num, struct obj *obj1, struct obj *obj2)
{
// make pointer point to a new memory location
// holding the new integer value
int *old_num = num;
num = malloc(sizeof(int));
*num = *old_num * 10;
// make property of structure pointed to by pointer
// point to the new value
obj1->item = "changed";
// make pointer point to a new memory location
// holding the new structure value
obj2 = malloc(sizeof(struct obj));
obj2->item = "changed";
free(num); // end of scope
free(obj2); // end of scope
}
int num = 10;
struct obj obj1 = { "unchanged" };
struct obj obj2 = { "unchanged" };
int main()
{
// pass pointers by value: the pointers
// will be copied into the argument list
// of the called function and the copied
// pointers will point to the same values
// as the original pointers
changeStuff(&num, &obj1, &obj2);
printf("%d\n", num);
puts(obj1.item);
puts(obj2.item);
return 0;
}
语义!设置具体的定义必然会使一些答案和注释不相容,因为即使使用相同的单词和短语它们也没有描述相同的东西,但是要克服困难(特别是对于新的程序员)是至关重要的。
首先,有多个抽象层次并不是每个人都能掌握的。在第4代或第5代语言中学习过的较新的程序员可能难以将他们的思想包含在汇编中熟悉的概念或C程序员之间,而不是指向指针的指针。传递引用并不仅仅意味着使用函数参数变量更改引用对象的能力。
变量:符号的组合概念,它引用内存中特定位置的值。这个术语通常太多,不能单独用于讨论细节。
符号:用于表示变量的文本字符串(即变量的名称)。
值:特定位存储在内存中并使用变量符号引用。
内存位置:存储变量值的位置。 (位置本身由与存储在该位置的值分开的数字表示。)
函数参数:在函数定义中声明的变量,用于引用传递给函数的变量。
函数参数:函数外部的变量,由调用者传递给函数。
对象变量:其基本底层值不是“对象”本身的变量,而是其值是指向存储对象实际数据的内存中另一个位置的指针(内存位置值)。在大多数高级语言中,“指针”方面通过在各种上下文中自动解除引用而被有效地隐藏。
基元变量:其值为实际值的变量。即使这个概念也可能因各种语言的自动装箱和类似对象的上下文而变得复杂,但一般的想法是变量的值是由变量的符号表示的实际值,而不是指向另一个内存位置的指针。
函数参数和参数不是一回事。此外,变量的值不是变量的对象(正如各个人已经指出的那样,但显然被忽略了)。这些区别对于正确理解至关重要。
按值传递或按共享调用(对象):函数参数的值被COPIED到另一个由函数的参数符号引用的内存位置(无论它是在堆栈还是堆上)。换句话说,函数参数接收传递参数的值的副本... AND(严重)参数的值不被调用函数更新/更改/更改。请记住,对象变量的值不是对象本身,而是指向对象的指针,因此按值传递对象变量会将指针复制到函数参数变量。函数参数的值指向内存中完全相同的对象。对象数据本身可以通过函数参数直接更改,但函数参数的值永远不会更新,因此它将继续指向同一个对象,甚至在函数调用之后(即使其对象的数据被更改或者如果函数参数被分配一个不同的对象)。通过引用传递函数参数是错误的,因为引用的对象可以通过函数参数变量更新。
调用/传递引用:函数参数的值可以/将由相应的函数参数直接更新。如果有帮助,函数参数将成为参数的有效“别名” - 它们在同一内存位置有效地引用相同的值。如果函数参数是对象变量,则更改对象数据的能力与按值传递的情况没有区别,因为函数参数仍将指向与参数相同的对象。但是在对象变量的情况下,如果将函数参数设置为完全不同的对象,则该参数同样也将指向不同的对象 - 这不会发生在按值传递的情况下。
JavaScript不通过引用传递。如果你仔细阅读,你会发现所有相反的观点都误解了传值的含义,他们错误地断定通过函数参数更新对象数据的能力与“按值传递”同义。
对象克隆/复制:创建一个新对象并复制原始对象的数据。这可以是深拷贝或浅拷贝,但重点是创建一个新对象。创建对象的副本是一个单独的概念,而不是传值。某些语言区分类对象和结构(或类似),并且可能具有不同的行为以传递不同类型的变量。但是,当传递对象变量时,JavaScript不会自动执行此类操作。但缺少自动对象克隆并不能转换为引用传递。
在JavaScript中分享我所知的引用
在JavaScript中,对象存储为引用:
var a = {
a: 1,
b: 2,
c: 3
};
var b = a;
// b.c is referencing to a.c value
console.log(b.c) // Output: 3
// Changing value of b.c
b.c = 4
// Also changes the value of a.c
console.log(a.c) // Output: 4
我理解这个的简单方法......
var var1 = 13;
var var2 = { prop: 2 };
//13 and var2's content (reference) are being passed here
foo(var1, var2);
inVar1
和inVar2
接收传递的内容。
function foo(inVar1, inVar2){
//changing contents of inVar1 and inVar2 won't affect variables outside
inVar1 = 20;
inVar2 = { prop: 7 };
}
inVar2
收到了{ prop: 2 }
的引用,您可以更改对象属性的值。
function foo(inVar1, inVar2){
inVar2.prop = 7;
}
在低级语言中,如果要通过引用传递变量,则必须在创建函数时使用特定语法:
int myAge = 14;
increaseAgeByRef(myAge);
function increaseAgeByRef(int &age) {
*age = *age + 1;
}
&age
是对myAge
的引用,但是如果你想要使用*age
转换引用的值。
Javascript是一种高级语言,可以为您执行此转换。因此,尽管通过引用传递对象,但语言将引用参数转换为值。你不需要在函数定义中使用&
来通过引用传递它,在函数体上都没有*
来将引用转换为值,JS为你做了。
这就是为什么当你试图改变一个函数内部的对象时,通过替换它的值(即age = {value:5}
),改变不会持久,但如果你改变它的属性(即age.value = 5
),它就会这样做。
对于编程语言律师,我已经阅读了ECMAScript 5.1的以下部分(比最新版本更容易阅读),并且在ECMAScript邮件列表上直到asking。
TL; DR:Everythings按值传递,但是Objects的属性是引用,并且Object的定义在标准中非常缺乏。
第11.2.4节“参数列表”说明了生成仅包含1个参数的参数列表:
生产ArgumentList:AssignmentExpression的计算方法如下:
- 设ref为评估AssignmentExpression的结果。
- 设arg为GetValue(ref)。
- 返回唯一项为arg的List。
该部分还列举了参数列表具有0或> 1个参数的情况。
因此,一切都通过引用传递。
第11.2.1节“属性访问者”
生成MemberExpression:MemberExpression [Expression]的计算方法如下:
- 让baseReference成为评估MemberExpression的结果。
- 让baseValue为GetValue(baseReference)。
- 设propertyNameReference是评估Expression的结果。
- 设propertyNameValue为GetValue(propertyNameReference)。
- 调用CheckObjectCoercible(baseValue)。
- 设属性Name String为String(propertyName Value)。
- 如果正在评估的语法生成包含在严格模式代码中,则let strict为true,否则let strict为false。
- 返回类型为Reference的值,其基值为baseValue,其引用名称为propertyNameString,其严格模式标志为strict。
因此,对象的属性始终可用作参考。
它在第8.7节“参考规范类型”中描述,引用不是语言中的实际类型 - 它们仅用于描述delete,typeof和赋值运算符的行为。
它在5.1版本中定义为“对象是属性的集合”。因此,我们可以推断,对象的值是集合,但是关于集合的值是什么,在规范中定义不明确,需要一些effort才能理解。
它总是按值传递,但对于对象,变量的值是引用。因此,当您传递一个对象并更改其成员时,这些更改将在该函数之外保留。这使它看起来像通过引用传递。但是,如果您实际更改了对象变量的值,您将看到更改不会持续存在,从而证明它确实是通过值传递的。
例:
function changeObject(x) {
x = { member: "bar" };
console.log("in changeObject: " + x.member);
}
function changeMember(x) {
x.member = "bar";
console.log("in changeMember: " + x.member);
}
var x = { member: "foo" };
console.log("before changeObject: " + x.member);
changeObject(x);
console.log("after changeObject: " + x.member); /* change did not persist */
console.log("before changeMember: " + x.member);
changeMember(x);
console.log("after changeMember: " + x.member); /* change persists */
输出:
before changeObject: foo
in changeObject: bar
after changeObject: foo
before changeMember: foo
in changeMember: bar
after changeMember: bar
我发现的最简洁的解释是在AirBNB style guide:
Ef。:
var foo = 1,
bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
Ef。:
var foo = [1, 2],
bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9
即有效的原始类型按值传递,复杂类型通过引用传递。
我已多次阅读这些答案,但直到我了解到Barbaz Liskov所称的"Call by sharing"的技术定义时才真正得到它。
通过共享调用的语义与通过引用调用的不同之处在于,对函数内的函数参数的赋值对于调用者是不可见的(与引用语义不同)[引用需要],例如,如果传递了变量,则无法在调用者范围内模拟该变量的赋值。但是,由于该函数可以访问与调用者相同的对象(不进行复制),因此对于这些对象的突变(如果对象是可变的)在调用者中是可见的,这可能看起来与按值调用不同语义。函数内的可变对象的变异对调用者是可见的,因为不会复制或克隆对象 - 它是共享的。
也就是说,如果您去访问参数值本身,参数引用是可以改变的。另一方面,对参数的赋值将在评估后消失,并且对函数调用者不可访问。
MDN文档清楚地解释它,而不是太冗长:
函数调用的参数是函数的参数。参数按值传递给函数。如果函数更改参数的值,则此更改不会全局或在调用函数中反映。但是,对象引用也是值,它们是特殊的:如果函数更改了引用对象的属性,则该更改在函数外部可见,(...)
资料来源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description
当我想传入一个对象作为参数时,我发现extend method的Underscore.js library非常有用,可以修改或完全替换。
function replaceOrModify(aObj) {
if (modify) {
aObj.setNewValue('foo');
} else {
var newObj = new MyObject();
// _.extend(destination, *sources)
_.extend(newObj, aObj);
}
}
我会说它是副本 -
考虑参数和变量对象是在函数调用开始时创建的执行上下文期间创建的对象 - 并且传递给函数的实际值/引用只是存储在此参数+变量对象中。
简单来说,对于原始类型,值在函数调用的开头被复制,对于对象类型,引用被复制。
确定某些内容是否“通过引用传递”的简单方法是您是否可以编写“交换”功能。例如,在C中,您可以执行以下操作:
void swap(int *i, int *j)
{
int t;
t = *i;
*i = *j;
*j = t;
}
如果你不能在JavaScript中做相同的事情,它不是“通过引用传递”。
object1 = {item: "car"};
array1=[1,2,3];
在这里,您将新对象或数组分配给旧对象。您不会更改旧对象的属性值。因此它是按值传递的。
如果要更改对象或数组的属性值,则它将通过引用传递。
object1.key1= "car";
array1[0]=9;
在这里,您正在更改旧对象的属性值。您没有将新对象或数组分配给旧对象。因此它是通过引用传递的。码
function passVar(object1, object2, number1) {
object1.key1= "laptop";
object2 = {
key2: "computer"
};
number1 = number1 + 1;
}
var object1 = {
key1: "car"
};
var object2 = {
key2: "bike"
};
var number1 = 10;
passVar(object1, object2, number1);
console.log(object1.key1);
console.log(object2.key2);
console.log(number1);
Output: -
laptop
bike
10
在某些情况下,改变anArg
可能会有所帮助:
function alterMyArg(func) {
// Process some data
// ...
func(data);
}
alertMyArg(function(d) {anArg = d;});
函数内部的简单值不会更改函数外部的值(它们通过值传递),而复杂的值将(通过引用传递)。
function willNotChange(x) {
x = 1;
}
var x = 1000;
willNotChange(x);
document.write('After function call, x = ' + x + '<br>'); // Still 1000
function willChange(y) {
y.num = 2;
}
var y = {num: 2000};
willChange(y);
document.write('After function call y.num = ' + y.num + '<br>'); // Now 2, not 2000
变量不“保持”对象;它有一个参考。您可以将该引用分配给另一个变量,现在它们都引用同一个对象。它总是按值传递(即使该值是参考值......)。
没有办法改变作为参数传递的变量所持有的值,如果JavaScript支持通过引用传递,那么这是可能的。
基元按值传递,对象通过引用传递。这与其他语言(如C,Visual Basic或Delphi)完全不同。我不能说他们如何准确地处理对象和原语,但我知道Visual Basic和Delphi可以(并且应该)指定它。
PHP从版本5开始做类似的事情:所有对象都通过引用传递,但是所有基元都可以通过引用传递,如果前面带有符号(&)。否则原语按值传递。
所以在JavaScript中,如果我通过参数将对象X传递给函数,它仍然是X.如果要更改函数内部的数据(或任何其他对象,但这并不重要),那么新值也可以在功能。
我的两分钱......这就是我理解它的方式。 (如果我错了,请随意纠正我)
是时候抛弃你所知道的关于通过值/参考传递的所有内容了。
因为在JavaScript中,无论是通过值传递还是通过引用或其他任何方式传递都无关紧要。重要的是突变与传递给函数的参数的赋值。
好的,让我尽力解释我的意思。假设你有几个对象。
var object1 = {};
var object2 = {};
我们所做的是“赋值”......我们已经为变量“object1”和“object2”分配了2个单独的空对象。
现在,让我们说我们更喜欢object1 ......所以,我们“分配”一个新的变量。
var favoriteObject = object1;
接下来,无论出于何种原因,我们都认为我们更喜欢对象2。所以,我们只是做一点重新分配。
favoriteObject = object2;
object1或object2没有任何反应。我们根本没有改变任何数据。我们所做的就是重新分配我们最喜欢的对象。重要的是要知道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变量都分配给'Hello world'。现在,如果我们想改变我们最喜欢的字符串怎么办?会发生什么???
favoriteString = 'Hello everyone';
console.log(favoriteString); // Logs 'Hello everyone'
console.log(string1); // Logs 'Hello world'
呃哦......发生了什么事。我们无法通过更改favoriteString更改string1 ...为什么?因为我们没有改变我们的字符串对象。我们所做的就是将“FavorString”变量“RE ASSIGN”为一个新字符串。这基本上与string1断开了连接。在前面的示例中,当我们重命名对象时,我们没有分配任何内容。 (好吧,不是变量本身,...我们确实将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中将参数传递给函数也意味着赋值。它们是相同的,并且2个变量以完全相同的方式连接(也就是说它们不是,除非你指望它们被分配给同一个对象)。
“修改变量”影响不同变量的唯一时间是基础对象发生变异时(在这种情况下,您没有修改变量,而是修改了对象本身)。
在对象和基元之间进行区分是没有意义的,因为它的工作方式与没有函数的情况完全相同,只是使用等号分配给新变量。
唯一的问题是当您传递给函数的变量名称与函数参数的名称相同时。当发生这种情况时,你必须将函数内部的参数视为一个私有函数的全新变量(因为它是)
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'
考虑以下:
所以, 忘记“通过引用/值传递” 不要挂断“通过引用/值传递”,因为:
回答你的问题:指针被传递。
// code
var obj = {
name: 'Fred',
num: 1
};
// illustration
'Fred'
/
/
(obj) ---- {}
\
\
1
// code
obj.name = 'George';
// illustration
'Fred'
(obj) ---- {} ----- 'George'
\
\
1
// code
obj = {};
// illustration
'Fred'
(obj) {} ----- 'George'
| \
| \
{ } 1
// code
var obj = {
text: 'Hello world!'
};
/* function parameters get their own pointer to
* the arguments that are passed in, just like any other variable */
someFunc(obj);
// illustration
(caller scope) (someFunc scope)
\ /
\ /
\ /
\ /
\ /
{ }
|
|
|
'Hello world'
一些最终评论:
var a = [1,2];
var b = a;
a = [];
console.log(b); // [1,2]
// doesn't work because `b` is still pointing at the original array
函数外部的对象通过提供对外部对象的引用而传递给函数。
当您使用该引用来操纵其对象时,外部对象因此受到影响。但是,如果在函数内部您决定将引用指向其他内容,则根本不会影响外部对象,因为您所做的只是将引用重定向到其他内容。
可以这样想:它总是按价值传递。但是,对象的值不是对象本身,而是对该对象的引用。
这是一个例子,传递一个数字(原始类型)
function changePrimitive(val) {
// At this point there are two '10's in memory.
// Changing one won't affect the other
val = val * 10;
}
var x = 10;
changePrimitive(x);
// x === 10
用对象重复此操作会产生不同的结果:
function changeObject(obj) {
// At this point there are two references (x and obj) in memory,
// but these both point to the same object.
// changing the object will change the underlying object that
// x and obj both hold a reference to.
obj.val = obj.val * 10;
}
var x = { val: 10 };
changeObject(x);
// x === { val: 100 }
还有一个例子:
function changeObject(obj) {
// Again there are two references (x and obj) in memory,
// these both point to the same object.
// now we create a completely new object and assign it.
// obj's reference now points to the new object.
// x's reference doesn't change.
obj = { val: 100 };
}
var x = { val: 10 };
changeObject(x);
// x === { val: 10}
JavaScript始终是按值传递的;一切都是有价值的。
对象是值,对象的成员函数本身就是值(请记住,函数是JavaScript中的第一类对象)。此外,关于JavaScript中的所有内容都是对象的概念;这是错的。字符串,符号,数字,布尔值,空值和未定义是原语。
有时他们可以利用从他们的基础原型继承的一些成员函数和属性,但这只是为了方便。这并不意味着它们本身就是对象。请尝试以下参考:
x = "test";
alert(x.foo);
x.foo = 12;
alert(x.foo);
在两个警报中,您都会发现未定义的值。
在JavaScript中,值的类型仅控制是通过value-copy还是通过引用副本分配该值。
始终通过value-copy分配/传递原始值:
null
undefined
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,3]
的单独引用,这是一个复合值。值得注意的是,c
和d
都没有“拥有”[1,2,3]
值 - 两者都只是对价值的平等对等参考。因此,当使用任一引用修改(.push(4)
)实际共享的array
值本身时,它只影响一个共享值,并且两个引用都将引用新修改的值[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
的指针,而不是array
的引用 - 但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
时,它会将a
引用的副本分配给x
。 x
和a
是指向相同[1,2,3]
值的单独引用。现在,在函数内部,我们可以使用该引用来改变值本身(push(4)
)。但是当我们进行赋值x = [4,5,6]
时,这绝不会影响初始参考a
指向的位置 - 仍然指向(现在修改的)[1,2,3,4]
值。
要通过值复制有效地传递复合值(如array
),您需要手动复制它,以便传递的引用仍然不指向原始值。例如:
foo( a.slice() );
可以通过引用副本传递的复合值(对象,数组等)
function foo(wrapper) {
wrapper.a = 42;
}
var obj = {
a: 2
};
foo( obj );
obj.a; // 42
在这里,obj
充当标量原始属性a
的包装器。当传递给foo(..)
时,传入obj
参考的副本并设置为wrapper
parameter。我们现在可以使用wrapper
引用来访问共享对象,并更新其属性。函数完成后,obj.a
将看到更新后的值42
。