Javascript - 原始类型 vs 引用类型

7
在下面的代码中,我们正在传递一个对象。因此,根据JavaScript的规定,我们正在传递一个引用并进行操作。
var a = new Number(10);
x(a);
alert(a);

function x(n) {
n = n + 2;
}

但是警报会提示10而不是12。为什么?

这个答案可能会帮助你理解 JavaScript 中的“按引用传递”:https://dev59.com/RWsz5IYBdhLWcg3wpZvy#7744623 - shriidhar
6个回答

10
n 是局部于 x 的变量,并且首先它被设置为与全局变量 a 相同的引用。然后评估右侧的表达式 n + 2,结果为一个数字(原始类型)。 赋值语句的左侧n,从未被评估过,在这里它只是一个标识符。因此,我们的局部变量现在被设置为右侧表达式的原始值。由 a 引用的值实际上从未被修改过。参见
var a = new Number(10);
x(a);
alert(a);  // 10

function x(n) {
  alert(typeof n);  // object
  n = n + 2;
  alert(typeof n);  // number
}

4
当你进行计算时
n + 2

即使n确实是一个Number对象实例,这也会导致一个新的“本地数字”。

然后将值分配给n只会更改本地变量n所引用的内容,而不会更改Number对象实例。您可以通过以下方式查看:

n = new Number(10);
console.log(typeof n);     // ---> "object"
console.log(n + 2);        // ---> 12
console.log(typeof (n+2)); // ---> "number"
n = n + 2;
console.log(typeof n);     // ---> "number"

在Javascript(或Python或Lisp)中,没有办法传递变量的“地址”,以使被调用的函数可以改变它。你只能做的是传递一个setter函数...例如:
function foo(setter) {
    setter(42);
}

funciton bar() {
    var x = 12;
    foo(function(newx){x = newx;});
    console.log(x); // ---> 42
}

数字是不可变的,对吧?如果 x = new Number(5) 或者 x = 5,那么没有任何操作可以使 x 保持相同的引用但具有不同的值。当我执行 console.log(new Number(5)) 时,它会输出 Number {[[PrimitiveValue]]: 5}。但是我假设你不能改变那个 [[PrimitiveValue]],对吗? - soktinpk
@soktinpk:是的,在JavaScript中,数字和字符串是不可变的。但是,您可以创建一个具有valueOf()方法的对象,该方法可以在数学表达式中像数字一样运行,但具有可变状态,您可以控制它。 - 6502

2
答案非常简单:因为ECMAScript是传值而不是传引用,你的代码证明了这一点。(更准确地说,它是称为调用共享的特定类型的传值。)
请参见JavaScript是按引用还是按值传递的语言?以获取更多信息。
ECMAScript使用传值,或更准确地说,传值的一个特殊情况——被传递的值始终是一个指针。这个特殊情况有时也被称为调用共享、调用对象共享或调用对象。
它是Java(用于对象)、C#(默认为引用类型)、Smalltalk、Python、Ruby等几乎所有面向对象的语言所采用的同一约定。
注意:某些类型(例如)Number实际上是通过值直接传递的,而不是通过中介指针传递的。但是,由于这些类型是不可变的,因此在这种情况下,传值和调用对象共享之间没有可观察到的行为差异,因此您可以大大简化您的心理模型,只需将所有东西视为调用对象共享。只需将这些特殊情况解释为内部编译器优化,您就无需担心。
这里有一个简单的示例,您可以运行它来确定ECMAScript(或任何其他语言在翻译后)的参数传递约定:

function isEcmascriptPassByValue(foo) {
  foo.push('More precisely, it is call-by-object-sharing!');
  foo = 'No, ECMAScript is pass-by-reference.';
  return;
}

var bar = ['Yes, of course, ECMAScript *is* pass-by-value!'];

isEcmascriptPassByValue(bar);

console.log(bar);
// Yes, of course, ECMAScript *is* pass-by-value!,
// More precisely, it is call-by-object-sharing!

如果你熟悉C#,那么理解传值和传引用在值类型和引用类型之间的区别就非常容易了。因为C#支持所有四种组合:对于值类型的传值(“传统的传值方式”),对于引用类型的传值(按共享调用、按对象调用、按对象共享调用,如ECMAScript中所述),对于引用类型的传引用,以及对于值类型的传引用。
实际上,即使你不熟悉C#,这也不太难理解。
struct MutableCell
{
    public string value;
}

class Program
{
    static void IsCSharpPassByValue(string[] foo, MutableCell bar, ref string baz, ref MutableCell qux)
    {
        foo[0] = "More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.";
        foo = new string[] { "C# is not pass-by-reference." };

        bar.value = "For value types, it is *not* call-by-sharing.";
        bar = new MutableCell { value = "And also not pass-by-reference." };

        baz = "It also supports pass-by-reference if explicitly requested.";

        qux = new MutableCell { value = "Pass-by-reference is supported for value types as well." };
    }

    static void Main(string[] args)
    {
        var quux = new string[] { "Yes, of course, C# *is* pass-by-value!" };

        var corge = new MutableCell { value = "For value types it is pure pass-by-value." };

        var grault = "This string will vanish because of pass-by-reference.";

        var garply = new MutableCell { value = "This string will vanish because of pass-by-reference." };

        IsCSharpPassByValue(quux, corge, ref grault, ref garply);

        Console.WriteLine(quux[0]);
        // More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.

        Console.WriteLine(corge.value);
        // For value types it is pure pass-by-value.

        Console.WriteLine(grault);
        // It also supports pass-by-reference if explicitly requested.

        Console.WriteLine(garply.value);
        // Pass-by-reference is supported for value types as well.
    }
}

2

让我用例子来回答这个问题:

function modify(obj) {
    // modifying the object itself
    // though the object was passed as reference
    // it behaves as pass by value
    obj = {c:3};
}

var a = {b:2}
modify(a);
console.log(a)
// Object {b: 2}

function increment(obj) {
    // modifying the value of an attribute
    // working on the same reference
    obj.b = obj.b + 1;
}

var a = {b:2}
increment(a);
console.log(a)
// Object {b: 3}

function augument(obj) {
    // augument an attribute
    // working on the same reference
    obj.c = 3;
}

var a = {b:2}
augument(a);
console.log(a)
// Object {b: 2, c: 3}

请参考JSFiddle获取可运行的演示。

0
var a = new Number(10);    
x(a);
alert(a);

function x(n) {
    n = n + 2; // NOT VALID as this would essentially mean 10 = 10 + 2 since you are passing the 'value' of a and not 'a' itself
}

你需要编写以下内容才能使其正常工作

var a = new Number(10);
x(a);
alert(a);

function x(n) {
    a = n + 2; // reassign value of 'a' equal to the value passed into the function plus 2
}

1
我不需要可运行的代码。你第一组解释中的“10 = 10 + 2”是错误的。 - sarthak sharma
此方程式无效。因为左边是一个数字 - Anubhav
正确。你是正确的。 - Anubhav

0
JavaScript的参数传递方式与Java类似。单个值通过值传递,但对象属性通过它们的指针值引用传递。在函数中,一个值本身不会被修改,但对象的属性会被修改。
考虑以下代码:
function doThis(param1, param2) {
    param1++;
    if(param2 && param2.value) {
        param2.value++;
    }
}

var initialValue = 2;
var initialObject = {value: 2};

doThis(initialValue, initialObject);

alert(initialValue); //2
alert(initialObject.value); //3

http://jsfiddle.net/bfm01b4x/


Java(和JS)对象按名称传递,即通过值传递对象的引用。对象参数(而不是属性)不会按引用传递。术语很重要。在Java中,“属性”具有特定的含义,与“字段”不同-请参见https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7。 - Pete Kirkham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接