JavaScript是一种传递引用还是按值传递的语言?

时间:2009-02-05 21:23:56

标签: javascript pass-by-reference pass-by-value

基本类型(Number,String等)按值传递,但是对象是未知的,因为它们可以是值传递的(如果我们认为持有对象的变量实际上是一个引用到对象)和传递引用(当我们认为对象的变量保存对象本身时)。

虽然最后并不重要,但我想知道提交传递约定的参数的正确方法是什么。是否有JavaScript规范的摘录,它定义了与此相关的语义?

32 个答案:

答案 0 :(得分:1474)

在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.item对函数外部的obj1没有任何影响。
  • 如果它是纯粹的参考传递,那么一切都会改变。 num100obj2.item"changed"

相反,情况是传入的项目是按值传递的。但是按值传递的项目是本身一个引用。 从技术上讲,这称为call-by-sharing

实际上,这意味着如果您更改参数本身(与numobj2一样),则不会影响输入参数的项目。 但是,如果您更改参数的 INTERNALS ,那么它将传播回来(与obj1一样)。

答案 1 :(得分:413)

它总是按值传递,但对于对象,变量的值是引用。因此,当您传递一个对象并更改其成员时,这些更改将在该函数之外保留。这使得看起来就像通过引用传递一样。但是,如果您实际更改了对象变量的值,您将看到更改不会持续存在,从而证明它确实是按值传递的。

示例:

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

答案 2 :(得分:139)

变量不“保持”对象,它包含引用。您可以将该引用分配给另一个变量,现在它们都引用同一个对象。它总是按值传递(即使该值是参考值......)。

没有办法改变作为参数传递的变量所持有的值,如果JS支持通过引用传递,这将是可能的。

答案 3 :(得分:87)

我的2分......这是我理解它的方式。 (如果我错了,请随意纠正我)

现在是时候抛弃你所知道的关于传递值/参考的所有内容了。

因为在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 ...为什么?因为字符串是不可变的,我们没有改变它。我们所做的只是“RE ASSIGN”favoriteString到一个新的字符串。这基本上与string1断开了连接。在前面的示例中,当我们重命名对象时,我们没有分配任何内容。 (好吧,实际上......我们做了,我们将name属性分配给一个新字符串。)相反,我们只是改变了保持2个变量和底层对象之间连接的对象。

现在,转到函数和传递参数....当你调用一个函数并传递一个参数时,你实际上在做的是“赋值”到一个新变量,它的工作方式与你简单的一样使用等号(=)分配。

采取这些例子。

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
otherObj = {
    firstName: 'Jack',
    lastName: 'Frost'
};

// Now, otherObj and myObject are assigned to 2 very different objects
// And mutating one object no longer mutates 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 no longer mutates 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中的工作原理。无论是通过引用还是通过值传递的东西都没关系......重要的是赋值与变异。

每次将变量传递给函数时,您都会“分配”参数变量的名称,就像使用等号(=)一样。

始终记住,等号(=)表示分配。 永远记住,将参数传递给函数也意味着赋值。 它们是相同的,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'

答案 4 :(得分:64)

请考虑以下事项:

  1. 变量是指向内存中值的指针
  2. 重新分配变量只会将指针指向新值。
  3. 重新分配变量绝不会影响指向同一个对象的其他变量
  4. 所以,忘记“通过引用/值传递” 不要挂断“通过引用/值传递”,因为:

    1. 这些术语仅用于描述语言的行为,不一定是实际的底层实现。作为这种抽象的结果,对于正确解释至关重要的关键细节将丢失,这不可避免地导致当前情况,即单个术语无法充分描述实际行为并且必须提供补充信息
    2. 这些概念最初并不是为了特别描述javascript而定义的,所以当它们只会增加混淆时,我不觉得有必要使用它们。
    3. 回答你的问题:传递指针。


      // 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
      

答案 5 :(得分:23)

通过提供对外部obejct的引用,将函数外部的对象传递给函数。当您使用该引用来操纵其对象时,外部对象因此受到影响。但是,如果在函数内部您决定将引用指向其他内容,则根本不会影响外部对象,因为您所做的只是将引用重定向到其他内容。

答案 6 :(得分:19)

可以这样想:它总是按价值传递。但是,对象的值不是对象本身,而是对该对象的引用。

这是一个例子,传递数字(基本类型)

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}

答案 7 :(得分:14)

Javascript始终是按值传递,所有内容都是值类型。对象是值,对象的成员函数本身就是值(请记住,函数是Javascript中的第一类对象)。此外,关于Javascript中的所有内容都是对象的概念,这是错误的。字符串,符号,数字,布尔值,空值和未定义是 primitives 。有时他们可以利用从基础原型继承的一些成员函数和属性,但这只是为了方便,并不意味着它们本身就是对象。请尝试以下参考

x = "test";
alert(x.foo);
x.foo = 12;
alert(x.foo);

在两个警报中,您都会发现未定义的值。

答案 8 :(得分:12)

在JavaScript中,值 的值类型控制该值是由值复制还是由引用副本指定的。

原始值始终由值复制分配/传递:

  • null
  • undefined
  • 字符串
  • 布尔
  • {li}中的符号ES6

复合值始终由参考副本分配/传递

  • objects
  • 阵列
  • 功能

例如

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中的值。

cd都是对同一共享值[1,2,3]的单独引用,这是一个复合值。值得注意的是,cd更多"拥有" [1,2,3]值 - 两者都是对值的等同对等引用。因此,当使用任一引用来修改(.push(4))实际的共享array值本身时,它只影响一个共享值,并且两个引用都将引用新修改的值{{1 }}

[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中不存在这样的功能!

array

当我们传递参数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引用的副本分配给axx是指向相同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引用来访问共享对象,并更新其属性。功能完成后,wrapper会看到更新后的值obj.a

Source

答案 9 :(得分:11)

有关按值和参考进行复制,传递和比较的详细说明,请参阅“JavaScript:The Definitive Guide”一书的this chapter

  

在我们离开主题之前   通过操纵对象和数组   参考,我们需要澄清一点   命名法。短语“经过   参考“可以有几个含义。   对于一些读者来说,这句话指的是   一种函数调用技术   允许函数分配新值   它的论点和拥有它们   修改后的值可见   功能。这不是这个词的方式   在本书中使用。我们的意思是   只是对对象的引用   或数组 - 不是对象本身 -   被传递给一个函数。一个功能   可以使用引用来修改   对象或元素的属性   数组。但如果功能   用a覆盖引用   引用一个新对象或数组,   该修改不可见   功能之外。读者   熟悉其他含义   这个词可能更喜欢这样说   对象和数组传递   值,但传递的值是   实际上是一个参考而不是   对象本身。

有一点我还是想不通。检查下面的代码。有什么想法吗?

function A() {}
A.prototype.foo = function() {
    return 'initial value';
}


function B() {}
B.prototype.bar = A.prototype.foo;

console.log(A.prototype.foo()); //initial value
console.log(B.prototype.bar()); //initial value

A.prototype.foo = function() {
    return 'changed now';
}

console.log(A.prototype.foo()); //changed now
console.log(B.prototype.bar()); //Why still 'initial value'???

答案 10 :(得分:7)

JavaScript通过值传递原始类型,并通过引用传递对象类型

  

现在,人们喜欢无休止地争论是否“通过引用”   是描述Java等人的正确方法。确实可以。重点   是这个吗?

     
      
  1. 传递对象不会复制该对象。
  2.   
  3. 传递给函数的对象可以通过该函数修改其成员。
  4.   
  5. 传递给函数的原始值不能被该函数修改。已复制。
  6.   
     

在我的书中,这称为参考传递。

Brian Bi-Which programming languages are pass by reference?


更新

以下是对此的反驳:

There is no "pass by reference" available in JavaScript. a

答案 11 :(得分:3)

语义!!设置具体的定义必然会使一些答案和注释不相容,因为即使使用相同的单词和短语它们也没有描述相同的东西,但是要克服困难(特别是对于新的程序员)是至关重要的。

首先,有多个抽象层次并不是每个人都能掌握的。在第4代或第5代语言中学习过的较新的程序员可能难以将他们的思想包含在汇编中熟悉的概念或C程序员之间,而不是指向指针的指针。传递引用并不仅仅意味着使用函数参数变量更改引用对象的能力。

变量:符号的组合概念,它引用内存中特定位置的值。这个术语通常太多,不能单独用于讨论细节。

符号:用于引用变量的文本字符串(即变量的名称)。

:特定位存储在内存中并使用变量符号引用。

内存位置:存储变量值的位置。 (位置本身由与存储在该位置的值分开的数字表示。)

函数参数:在函数定义中声明的变量,用于引用传递给函数的变量。

函数参数:函数外部的函数,由调用者传递给函数。

对象变量:其基本底层值不是"对象的变量"本身,它的值是指向存储对象实际数据的存储器中另一个位置的指针(存储位置值)。在大多数更高代语言中,"指针"通过在各种上下文中自动解除引用来有效地隐藏方面。

原始变量:变量,其值为实际值。即使这个概念也可能因各种语言的自动装箱和类似对象的上下文而变得复杂,但一般的想法是变量的值是由变量的符号表示的实际值而不是指向另一个记忆位置。

函数参数和参数不是一回事。此外,变量的值不是变量的对象(正如各个人已经指出的那样,但显然被忽略了)。这些区别对于正确理解至关重要。

按值传递或按共享调用(对象):函数参数的值被COPIED到另一个由函数引用的内存位置参数符号(无论它是在堆栈还是堆上)。换句话说,函数参数接收到传递的参数的值的副本... AND(严重)参数的值不被调用函数更新/改变/改变。请记住,对象变量的值不是对象本身,而是指向对象的指针,因此通过值传递对象变量会将指针复制到函数参数变量。函数参数的值指向内存中完全相同的对象。对象数据本身可以通过函数参数直接更改,但函数参数的值永远不会更新,因此它将继续指向相同的对象,甚至在函数调用之后(即使其对象的数据被更改,或者函数参数被完全分配了不同的对象)。通过引用传递函数参数是错误的,因为引用的对象可以通过函数参数变量更新。

调用/按引用传递:函数参数的值可以/将由相应的函数参数直接更新。如果有帮助,函数参数将成为一个有效的别名"对于参数 - 它们在同一内存位置有效地引用相同的值。如果函数参数是对象变量,则更改对象数据的能力与按值传递的情况没有区别,因为函数参数仍将指向与参数相同的对象。但是在对象变量的情况下,如果函数参数设置为完全不同的对象,那么参数同样也会指向不同的对象 - 这不会发生在按值传递的情况下。

JavaScript不通过引用传递。如果你仔细阅读,你会发现所有相反的观点都误解了传值的含义,他们错误地断定通过函数参数更新对象数据的能力是"传递的同义词。逐值"

对象克隆/复制:创建一个新对象并复制原始对象的数据。这可以是深拷贝或浅拷贝,但重点是创建一个新对象。创建对象的副本是一个单独的概念,而不是传值。某些语言区分类对象和结构(或类似),并且可能具有不同的行为以传递不同类型的变量。但是,当传递对象变量时,JavaScript不会自动执行此类操作。但缺少自动对象克隆并不能转化为传递参考。

答案 12 :(得分:3)

关于在JS here中使用“通过引用传递”一词的讨论,但是要回答你的问题:

  

对象通过引用自动传递,无需特别说明

(来自上面提到的文章。)

答案 13 :(得分:3)

将参数传递给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;
}

答案 14 :(得分:2)

这只是有关按值传递和按引用传递(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
  • 适用于JS中的所有原始类型(字符串,数字,布尔值,未定义,空)。
  • a被分配了一个内存(例如0x001),b则在内存中创建了该值的副本(例如0x002)。
  • 因此,更改变量的值不会影响其他变量,因为它们都位于两个不同的位置。

通过引用传递(对象)

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" }
  • JS引擎将对象分配给变量c,它指向某个内存,例如(0x012)
  • 当d = c时,在此步骤中d指向相同的位置(0x012)。
  • 更改两个变量的任何更改值的值。
  • 功能就是对象

特殊情况,通过引用传递(对象)

c = {"name" : "jane"}; 
console.log(c); // { "name" : "jane" }    
console.log(d); // { "name" : "doe" }
  • equal(=)运算符设置新的内存空间或地址

答案 15 :(得分:2)

对于编程语言律师,我已经阅读了ECMAScript 5.1的以下部分(比最新版本更容易阅读),并在ECMAScript邮件列表中尽可能地asking

TL; DR :Everythings按值传递,但是Objects的属性是引用,并且Object的定义在标准中非常缺乏。

参数列表的构建

第11.2.4节“参数列表”说明了生成仅包含1个参数的参数列表:

  

生产ArgumentList:AssignmentExpression的计算方法如下:

     
      
  1. 让ref成为评估AssignmentExpression的结果。
  2.   
  3. 让arg成为GetValue(ref)。
  4.   
  5. 返回唯一项目为arg的列表。
  6.   

该部分还列举了参数列表具有0或> 1参数的情况。

因此,所有内容都通过引用传递。

访问对象属性

第11.2.1节“财产配件”

  

生成MemberExpression:MemberExpression [Expression]的计算方法如下:

     
      
  1. 让baseReference成为评估MemberExpression的结果。
  2.   
  3. 让baseValue为GetValue(baseReference)。
  4.   
  5. 让propertyNameReference成为评估Expression的结果。
  6.   
  7. 让propertyNameValue为GetValue(propertyNameReference)。
  8.   
  9. 调用CheckObjectCoercible(baseValue)。
  10.   
  11. 让propertyNameString为ToString(propertyNameValue)。
  12.   
  13. 如果正在评估的语法生成包含在严格模式代码中,则let strict为true,否则为let   严格是假的。
  14.   
  15. 返回类型为Reference 的值,其基值为baseValue,其引用名称为   propertyNameString,其严格模式标志是严格的。
  16.   

因此,对象的属性始终可用作参考。

参考

第8.7节“参考规范类型”中描述了引用不是语言中的实际类型 - 它们仅用于描述delete,typeof和赋值运算符的行为。

“对象”的定义

在5.1版本中定义“An Object是属性的集合”。因此,我们可以推断,对象的值是集合,但是关于集合的值是什么,在规范中定义不明确,需要一些effort才能理解。

答案 16 :(得分:2)

我已多次阅读这些答案,但在我了解Barbara Liskov称之为"Call by sharing"的技术定义之前,我并没有真正理解这些答案

  

通过共享调用的语义与通过引用调用的不同之处在于,对函数内的函数参数的赋值对于调用者是不可见的(与引用语义不同)[引用需要],例如,如果传递了变量,则无法在调用者范围内模拟该变量的赋值。但是,由于函数可以访问与调用者相同的对象(不进行复制),因此调用者可以看到函数内对这些对象的突变(如果对象是可变的),这可能看起来与按值调用不同语义。调用者可以看到函数中可变对象的变异,因为不会复制或克隆对象 - 它是共享的。

也就是说,如果您去访问参数值本身,参数引用是可以改变的。另一方面,对参数的赋值将在评估后消失,并且函数调用者无法访问。

答案 17 :(得分:2)

在低级语言中,如果要通过引用传递变量,则必须在创建函数时使用特定语法:

int myAge = 14;
increaseAgeByRef(myAge);
function increaseAgeByRef(int &age) {
  *age = *age + 1;
}

&agemyAge的引用,但是如果您想要使用*age转换引用的值。

Javascript是一种高级语言,可以为您执行此转换。 因此,尽管通过引用传递对象,但语言将引用参数转换为值。您不需要在函数定义上使用&来通过引用传递它,而不是函数体上的*,以将引用转换为值,JS为您执行此操作。

这就是为什么当你尝试更改一个函数内部的对象时,通过替换它的值(即age = {value:5}),更改不会持久,但是如果你改变了它的属性(即age.value = 5)它确实如此。

Learn more

答案 18 :(得分:2)

观察:如果观察者无法检查引擎的基础内存,则无法确定是否复制了不变值或传递了引用。

JavaScript或多或少与基础内存模型无关。没有 reference ²之类的东西。 JavaScript具有。两个变量可以具有相同的(或更准确地说:两个环境记录可以绑定相同的值)。可以更改的值的唯一类型是通过抽象[[Get]]和[[Set]]操作的对象。 如果您忘记了计算机和内存,那么这就是描述JavaScript行为的全部,它使您可以了解规范。

 let a = { prop: 1 };
 let b = a; // a and b hold the same value
 a.prop = "test"; // the object gets mutated, can be observed through both a and b
 b = { prop: 2 }; // b holds now a different value
 

现在,您可能会问自己,两个变量如何在计算机上保持相同的值。然后,您可能会查看JavaScript引擎的源代码,并且很可能会找到使用该引擎所用语言的程序员称为引用的内容。

因此,实际上,您可以说JavaScript是“按值传递”,而值可以共享,您可以说JavaScript是“按引用传递”,对于从底层语言开始的程序员来说,这可能是有用的逻辑抽象,或者您可以将行为称为“共享通话”。 由于JavaScript中没有作为参考的东西,因此所有这些都没有错,也没有意义。因此,我认为答案对于搜索并不是特别有用。

²规范中的术语参考不是传统意义上的参考。它是对象和属性名称的容器,并且是中间值(例如a.b的值为Reference { value = a, name = "b" })。术语引用有时还会出现在规范中不相关的部分。

答案 19 :(得分:2)

我理解这个的简单方法......

  • 调用函数时,您传递的内容(引用或 参数变量的值,而不是变量本身。

    var var1 = 13;
    var var2 = { prop: 2 };
    
    //13 and var2's content (reference) are being passed here
    foo(var1, var2); 
    
  • 在函数内部,参数变量inVar1inVar2接收传递的内容。

    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; 
    }
    

答案 20 :(得分:1)

我发现的最简洁的解释是AirBNB style guide

  • 基元:当您访问基本类型时,您可以直接使用它 值

    • 字符串
    • 布尔
    • 未定义

E.g:

var foo = 1,
    bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9
  • 复杂:当您访问复杂类型时,您会处理对其值的引用

    • 物体
    • 阵列
    • 功能

E.g:

var foo = [1, 2],
    bar = foo;

bar[0] = 9;

console.log(foo[0], bar[0]); // => 9, 9

即。有效的原始类型按值传递,复杂类型通过引用传递。

答案 21 :(得分:1)

好吧,这是关于“性能”和“速度”以及编程语言中简单的“内存管理”一词。

在javascript中,我们可以将值分为两层: type1 -objects type2 -所有其他类型的值,例如stringboolean

如果您将内存想象成下面的正方形,那么在每个正方形中只能保存一个type2值:

enter image description here

每个类型2值(绿色)是单个正方形,而类型1值(蓝色)是它们的一组

enter image description here

关键是,如果要表示type2值,则地址是简单的,但是如果要对type1值执行相同的操作,那根本就不容易! :

enter image description here

还有一个更复杂的故事:

enter image description here

所以在这里引用可以拯救我们: enter image description here

此处的绿色箭头是一个典型变量,而紫色的箭头是一个对象变量,因此因为绿色箭头(典型变量)只有一项任务(这表示一个典型值)我们不需要将它的值与它分开,所以我们将绿色箭头的值移动到任何地方,在所有赋值,函数等中……

但是我们不能用紫色箭头做同样的事情,我们可能想将“ john”单元格移到这里或其他许多事情...,因此紫色箭头会坚持其位置,而只是分配给它会移动...

一个非常令人困惑的情况是,您无法意识到所引用的变量如何变化,让我们来看一个非常好的示例:

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'];

enter image description here enter image description here

答案 22 :(得分:0)

我会说它是传递副本 -

考虑参数和变量对象是在函数调用开始时创建的执行上下文期间创建的对象 - 传递给函数的实际值/引用只是存储在此参数+变量对象中。

简单来说,对于基本类型,值在函数调用的开头被复制,对于对象类型,引用被复制。

答案 23 :(得分:0)

  1. Primitives(Number,Boolean)按值传递。
    • 字符串是不可变的,所以对它们来说并不重要。
  2. 通过引用传递对象(引用按值传递)

答案 24 :(得分:0)

当我想传入一个对象作为参数时,我发现extend methodUnderscore.js library非常有用,可以对其进行修改或完全替换。

function replaceOrModify(aObj) {
  if (modify) {

    aObj.setNewValue('foo');

  } else {

   var newObj = new MyObject();
   // _.extend(destination, *sources) 
   _.extend(newObj, aObj);
  }
}

答案 25 :(得分:0)

MDN文档清楚地说明了这一点,而不必太冗长:

  

函数调用的参数是函数的自变量。   参数按值传递给函数 。如果功能改变   参数值,此更改不会在全局或全局中反映   调用函数。但是,对象引用也是值,并且   它们很特殊:如果函数更改了所引用对象的   属性,该更改在函数外部可见(...)

来源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description

答案 26 :(得分:0)

确定某些内容是否“通过引用传递”的简单方法是您是否可以编写“交换”功能。例如,在C中,您可以执行以下操作:

void swap(int *i, int *j)
{
    int t;
    t = *i;
    *i = *j;
    *j = t;
}

如果您不能在Javascript中执行相同的操作,则不是“通过引用传递”。

答案 27 :(得分:-2)

  1. 基本类型变量,如string,number始终作为pass传递 按价值计算。
  2. Array和Object作为传递引用传递,或者根据这两个条件传递值。

    • 如果要使用新的Object或Array更改该Object或数组的值,则它将通过Value传递。

      object1 = {item: "car"}; array1=[1,2,3];

    这里你要为旧的一个分配新的对象或数组。你没有改变属性的值 旧的对象。所以它是通过价值。

    • 如果要更改对象或数组的属性值,则它将通过引用传递。

      object1.key1= "car"; array1[0]=9;

    这里你要更改旧对象的属性值。你没有将新对象或数组分配给旧对象。所以它是通过引用传递的。

  3. <强>代码

        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
    

答案 28 :(得分:-2)

在某些情况下,这可能有助于改变anArg

function alterMyArg(func) {
    // process some data
    // ...
    func(data);
}

alertMyArg(function(d) {anArg = d;});

答案 29 :(得分:-3)

最简单的方法

// Copy JS object without reference
var first = {"a":"value1","b":"value2"};
var clone = JSON.parse( JSON.stringify( first ) ); 

var second = ["a","b","c"];
var clone = JSON.parse( JSON.stringify( second ) ); 

答案 30 :(得分:-4)

函数内部的简单值不会更改函数外部的值(它们通过值传递),而复杂值则会(通过引用传递)。

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

答案 31 :(得分:-4)

基元按值传递,对象通过引用传递。这与C,VB或Delphi等其他语言完全不同。我不能说他们如何准确处理对象和原语,但我知道VB和Delphi可以(并且应该)指定它。

从版本5开始,php执行类似的操作:所有对象都通过引用传递,但所有基元可以通过引用传递,如果前面是&符号(&amp;)。否则原语会按值传递。

所以在javascript中,如果我通过参数将对象X传递给函数,它仍然是X.如果要更改里面的函数(或任何其他对象,但那不是重要的是,在函数外部也可以使用新值。