基元类型(数字、字符串等)是通过值传递的,但对象是未知的,因为它们既可以通过值传递(如果我们认为持有对象的变量实际上是对对象的引用),也可以通过引用传递(当我们认为对象的变量持有对象本身)。
虽然最后这并不重要,但我想知道通过约定来表达论点的正确方式是什么。是否有JavaScript规范的摘录,该规范定义了与此相关的语义?
基元类型(数字、字符串等)是通过值传递的,但对象是未知的,因为它们既可以通过值传递(如果我们认为持有对象的变量实际上是对对象的引用),也可以通过引用传递(当我们认为对象的变量持有对象本身)。
虽然最后这并不重要,但我想知道通过约定来表达论点的正确方式是什么。是否有JavaScript规范的摘录,该规范定义了与此相关的语义?
当前回答
我已经多次阅读了这些答案,但直到我了解了Barbara Liskov所称的“通过共享呼叫”的技术定义,我才真正理解
通过共享调用的语义与通过引用调用的语义不同,因为对函数内函数参数的赋值对调用方不可见(与引用语义不同)[需要引用],因此例如,如果传递了变量,则无法在调用方的范围内模拟对该变量的赋值。然而,由于函数可以访问与调用方相同的对象(不进行复制),因此如果对象是可变的,则调用方可以看到函数中这些对象的突变,这可能与逐值调用语义不同。调用方可以看到函数中可变对象的变体,因为该对象未被复制或克隆,而是共享的。
也就是说,如果您访问参数值本身,参数引用是可变的。另一方面,对参数的赋值将在求值后消失,函数调用方无法访问。
其他回答
确定某个对象是否“通过引用传递”的一个简单方法是您是否可以编写“交换”函数。例如,在C中,您可以执行以下操作:
void swap(int *i, int *j)
{
int t;
t = *i;
*i = *j;
*j = t;
}
如果你不能在JavaScript中做到这一点,那就不是“通过引用传递”。
对于编程语言律师,我已经阅读了ECMAScript 5.1的以下部分(它比最新版本更容易阅读),并在ECMASript邮件列表中询问了它。
TL;DR:一切都是通过值传递的,但Objects的财产是引用,标准中令人毛骨悚然地缺少Object的定义。
参数列表的构造
第11.2.4节“参数列表”说明了生成仅由1个参数组成的参数列表的以下内容:
生产ArgumentList:AssignmentExpression的计算如下:让ref是赋值表达式的结果。让arg为GetValue(ref)。返回唯一项为arg的列表。
本节还列举了参数列表包含0或>1个参数的情况。
因此,所有的都是通过引用传递的。
对象财产的访问
第11.2.1节“财产访问人”
生产MemberExpression:MemberExpression[Expression]的计算如下:让baseReference作为计算MemberExpression的结果。让baseValue为GetValue(baseReference)。让propertyNameReference作为表达式求值的结果。让propertyNameValue为GetValue(propertyNameReference)。调用CheckObject强制(baseValue)。让propertyNameString为ToString(propertyNameValue)。如果正在计算的语法生成包含在严格模式代码中,则让strict为true,否则让严格是假的。返回一个Reference类型的值,其基值为baseValue,其引用名称为propertyNameString,其严格模式标志为strict。
因此,对象的财产始终可用作参考。
关于参考
第8.7节“引用规范类型”中描述了引用在语言中不是真正的类型——它们仅用于描述删除行为、类型和赋值运算符。
“对象”的定义
5.1版中定义了“对象是财产的集合”。因此,我们可以推断,对象的值是集合,但是关于集合的值是什么,在规范中定义得很差,需要一些努力才能理解。
它总是按值传递,但对于对象,变量的值是一个引用。因此,当您传递一个对象并更改其成员时,这些更改将在函数之外持续存在。这使得它看起来像是通过引用传递。但如果您实际更改了对象变量的值,您将看到更改不会持续,这证明它确实是通过值传递的。
例子:
函数changeObject(x){x={member:“bar”};console.log(“in changeObject:”+x.member);}函数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);/*更改没有持续*/console.log(“changeMember之前:”+x.member);changeMember(x);console.log(“after changeMember:”+x.member);/*更改持续存在*/
输出:
before changeObject: foo
in changeObject: bar
after changeObject: foo
before changeMember: foo
in changeMember: bar
after changeMember: bar
我已经多次阅读了这些答案,但直到我了解了Barbara Liskov所称的“通过共享呼叫”的技术定义,我才真正理解
通过共享调用的语义与通过引用调用的语义不同,因为对函数内函数参数的赋值对调用方不可见(与引用语义不同)[需要引用],因此例如,如果传递了变量,则无法在调用方的范围内模拟对该变量的赋值。然而,由于函数可以访问与调用方相同的对象(不进行复制),因此如果对象是可变的,则调用方可以看到函数中这些对象的突变,这可能与逐值调用语义不同。调用方可以看到函数中可变对象的变体,因为该对象未被复制或克隆,而是共享的。
也就是说,如果您访问参数值本身,参数引用是可变的。另一方面,对参数的赋值将在求值后消失,函数调用方无法访问。
嗯,这是关于“性能”和“速度”的,在编程语言中,简单地说就是“内存管理”。
在javascript中,我们可以将值放在两层:type1对象和type2所有其他类型的值,如字符串和布尔值等
如果你把记忆想象成下面的方块,其中每个方块中只能保存一个type2值:
每个type2值(绿色)是一个正方形,而type1值(蓝色)是它们的一组:
重点是,如果你想指示一个type2值,地址是很简单的,但如果你想对type1值做同样的事情,那根本不容易!:
在一个更复杂的故事中:
所以这里的参考资料可以拯救我们:
虽然这里的绿色箭头是一个典型变量,但紫色箭头是对象变量,所以因为绿色箭头(典型变量)只有一个任务(这表示一个典型值),所以我们不需要将它的值与它分开,所以我们将绿色箭头与它的值一起移动到任何位置,在所有赋值、函数等中。。。
但我们不能用紫色箭头做同样的事情,我们可能想把“约翰”牢房搬到这里或其他很多事情。。。,所以紫色的箭头会固定在它的位置上,而分配给它的典型箭头会移动。。。
一个非常令人困惑的情况是,您无法意识到引用的变量是如何变化的,让我们来看一个非常好的例子:
let arr = [1, 2, 3, 4, 5]; //arr is an object now and a purple arrow is indicating it
let obj2 = arr; // now, obj2 is another purple arrow that is indicating the value of arr obj
let obj3 = ['a', 'b', 'c'];
obj2.push(6); // first pic below - making a new hand for the blue circle to point the 6
//obj2 = [1, 2, 3, 4, 5, 6]
//arr = [1, 2, 3, 4, 5, 6]
//we changed the blue circle object value (type1-value) and due to arr and obj2 are indicating that so both of them changed
obj2 = obj3; //next pic below - changing the direction of obj2 array from blue circle to orange circle so obj2 is no more [1,2,3,4,5,6] and it's no more about changing anything in it but we completely changed its direction and now obj2 is pointing to obj3
//obj2 = ['a', 'b', 'c'];
//obj3 = ['a', 'b', 'c'];