在被调用函数中重新初始化数组

时间:2016-01-10 16:50:18

标签: javascript python arrays parameter-passing mutable

数组在javascript中通过引用传递。这意味着数组是可变的,可以在被调用的函数中进行更改。为什么我无法在被调用函数中重新初始化数组?

对于python

也是如此
function calling(){
    var arr = [1,2]
    console.log(arr); //prints [1,2]
    called(arr);
    console.log(arr); //prints [1,2,3,4],but I was expecting [5,6]

}

function called(arr){
    arr.push(3)
    arr.push(4)
    arr = [5,6]
}

calling()

更新 我正在寻找(可变性,通过引用传递,通过赋值传递和重新初始化数组)的理由 请不要发布解决方法和问题的解决方案。我知道如何获得打印[5,6]

3 个答案:

答案 0 :(得分:4)

  

在javascript中通过引用传递数组。

不,他们不是。 ECMAScript / JavaScript严格按值传递。 (更准确地说,是分享呼叫,这是传递值的特殊情况。)

  

这意味着数组是可变的,可以在被调用的函数中进行更改。

不,这不是它的意思。它的含义正是它所说的:调用者范围中对数组的引用作为参数传递给函数,而不是值。

数组是否可变是与传递引用和传递值无关。 ECMAScript不是纯函数式语言,大多数对象都可以变异。 Number s,SymbolString是个例外。

  

为什么我无法在被调用函数中重新初始化数组?

您正尝试修改调用者范围内的引用。这只适用于传递引用。 ECMAScript不是传递引用,无论谁告诉你这是完全错误的。

  

对于python

也是如此

Python在这方面的行为与ECMAScript相同,是的,也是传递值。

你的困惑源于这样一个事实:你错误地认为ECMAScript / JavaScript是传递引​​用的,而实际上并非如此。

ECMAScript使用按值传递,或者更准确地说,是传递值的特殊情况,其中传递的值总是指针。这种特殊情况有时也称为分享呼叫,按对象分享或逐个呼叫。

它与Java(用于对象),C#(默认情况下用于引用类型),Smalltalk,Python,Ruby以及或多或少的所有面向对象语言所使用的约定相同。

注意:某些类型(例如Number s)实际上是通过直接传递的,而不是通过中间指针传递。但是,由于这些是不可变的,在这种情况下,传值和对象共享之间没有可观察到的行为差异,因此您可以通过简单地处理所有内容来大大简化您的心理模型作为对象分享。只需将这些特殊情况解释为内部编译器优化,您无需担心。

这是一个简单的例子,您可以运行以确定传递ECMAScript(或任何其他语言,翻译后)约定的参数:

function isEcmascriptPassByValue(foo) {
  foo.push('More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value!');
  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, for reference types it is call-by-object-sharing, which is a special case of pass-by-value!

def is_python_pass_by_value(foo):
    foo[0] = 'More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value!'
    foo = ['Python is not pass-by-reference.']

quux = ['Yes, of course, Python *is* pass-by-value!']

is_python_pass_by_value(quux)

print(quux[0])
# More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value!

如果您熟悉C#,那么理解值类型和引用类型的传值和传递引用之间的差异是非常好的方法,因为C#支持所有4种组合:pass-by-值类型的值(“传统的按值传递”),引用类型的值传递(按共享调用,按对象调用,在ECMAScript中按对象分享),传递 - 引用类型的引用,以及值类型的引用传递。

(实际上,即使你知道C#,这也不难理解。)

// In C#, struct defines a value type, class defines a reference type
struct MutableCell
{
    public string value;
}

class Program
{
    // the ref keyword means pass-by-reference, otherwise it's pass-by-value
    // You must explicitly request pass-by-reference both at the definition and the call
    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." };

        // the first two are passed by value, the other two 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.
    }
}

答案 1 :(得分:3)

如果你想获得技术,Javascript真的只是"传递价值" - 它不是"通过引用"。当您传递一个对象(如数组)时,您将传递值,该值是一个引用(对数组的引用的副本,而不是实际数组的副本)。这意味着您可以使用值引用来改变原始对象,但它不是对原始变量的实际引用,因此您不能影响原始变量本身的内容(例如,您可以' t导致其他变量指向新对象。)

由于这种语言行话并没有真正解释许多人的情况,因此这里的内容更多地描述了它的工作原理:

当你这样做时:

arr

在函数内部,您只是为arr函数参数分配一个新数组。您刚刚指示Javascript采用function called(arr){ arr.push(3) arr.push(4) // now set arr back to [5,6] arr[0] = 5; arr[1] = 6; arr.length = 2; } 参数变量并将其指向新的数组对象。这对传递给函数的原始数组对象完全没有影响。它仍然幸福地存在于其他生活中。

您可以像这样更改该数组:

arr[0] = 5;    // this changes the original object

说数组是通过引用传递的,这有点用词不当,因为它并不像你在C ++中找到的那样完全引用。有人说它通过分享"或者"通过指针"。有些人甚至称之为"传递价值,但价值是参考"这可能在技术上是正确的,但通常不会帮助新手了解正在发生的事情。在我的大脑中(部分是因为我知道C / C ++),我认为它就像"通过指针"。

虽然Javascript实际上没有它自己的数据类型是指针,但传递数组的工作方式更像是在C ++中传递指针。如果索引指针,则访问原始对象:

arr = [5,6];   // this just makes arr point at a new and different array

但是,如果你为指针重新分配一个新值:

arr

然后,你只是让指针变量本身指向一个新对象,原始对象仍然保持不变。

总而言之。如果您在arr.push(x)中引用arr,那么您正在改变原始数组对象。

但是,如果您在arr = [5,6]中分配arr一个新数组,那么您只是告诉Javascript您希望arr变量指向一个新数组。现在,该函数中的var x = [1,2]; var y = x; console.log(x); // [1,2] console.log(y); // [1,2] y = [3,4]; console.log(x); // [1,2] console.log(y); // [3,4] 变量指向的数组与之前不同。此代码无法再访问原始数组。但是指向该原始数组的任何其他代码仍然指向该原始数组。如果您了解C / C ++,它实际上与C ++中的指针非常相似。

这是一个更简单的例子,它不在函数参数的上下文中:

y

你只是要求y现在指向一个新数组[3,4]。将y指定为指向新数组根本不会影响原始数组。

在此示例中,变量arrcalled()函数中的{{1}}函数参数类似。为其分配新值不会以任何方式更改原件。

答案 2 :(得分:1)

在Python中:

In [170]: def called(arr):
   .....:     arr.append(3)
   .....:     arr.append(4)
   .....:     arr=[5,6]
   .....:     
In [171]: arr=[1,2]
In [172]: called(arr)
In [173]: arr
Out[173]: [1, 2, 3, 4]

最初在called中,arr是对可变列表[1,2]的引用。 2个追加调用会修改该列表。但arr=[5,6]步骤重新分配变量。外部arr的链接已中断,因此无法进一步更改。

如果我在通话中添加了return arr语句,

In [175]: called(arr)
Out[175]: [5, 6]
In [176]: arr
Out[176]: [1, 2, 3, 4, 3, 4]

我再次修改全局arr,但返回[5,6]

如果写的话,

called会更清楚:

def called(arr):
    arr.append(3)
    arr.append(4)
    res=[5,6]
    return res

最后两行与初始arr参数无关,因此重用变量名称没有意义。

有关正在进行的操作的进一步说明,请在各个点查看对象id

In [178]: def called(arr):
    arr.append(3)
    arr.append(4)
    print(id(arr))
    arr = [5,6]
   .....:     return arr
   .....: 
In [179]: id(arr)
Out[179]: 2999818796
In [180]: id(called(arr))
2999818796             # id of global arr
Out[180]: 2999999564   # id of returned list
In [181]: id(arr)
Out[181]: 2999818796