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

2024-11-02 21:00:00
admin
原创
35
摘要:问题描述:基本类型(数字、字符串等)通过值传递。但对象是未知的,因为它们既可以通过值传递(在这种情况下,我们认为保存对象的变量是对象的引用),也可以通过引用传递(我们认为对象的变量保存对象本身)。虽然最终这并不重要,但我想知道传递参数约定的正确方式是什么。是否有 JavaScript 规范摘录,定义关于此的语...

问题描述:

基本类型(数字、字符串等)通过值传递。但对象是未知的,因为它们既可以通过值传递(在这种情况下,我们认为保存对象的变量是对象的引用),也可以通过引用传递(我们认为对象的变量保存对象本身)。

虽然最终这并不重要,但我想知道传递参数约定的正确方式是什么。是否有 JavaScript 规范摘录,定义关于此的语义应该是什么?


解决方案 1:

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

运行代码片段Hide results展开片段

输出结果如下:

10
changed
unchanged
  • 如果obj1根本不是引用,那么更改obj1.item就不会对obj1函数外部产生任何影响。

  • 如果参数是适当的引用,那么一切都会改变。num将是100,并且obj2.item将读作"changed"。相反,num保持不变10obj2.item保持"unchanged“。

相反,情况是传入的项是按值传递的。但按值传递的项本身是一个引用。从技术上讲,这称为共享调用。

从实际角度来说,这意味着如果您更改参数本身(如numobj2),则不会影响输入到参数中的项。但如果您更改参数的内部结构,则更改将传播回去(如obj1)。

解决方案 2:

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

例子:

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

运行代码片段Hide results展开片段

输出:

before changeObject: foo
in changeObject: bar
after changeObject: foo

before changeMember: foo
in changeMember: bar
after changeMember: bar

解决方案 3:

变量并不“保存”对象;它保存的是引用。您可以将该引用分配给另一个变量,现在它们都引用同一个对象。它始终按值传递(即使该值是引用...)。

无法改变作为参数传递的变量所持有的值,如果 JavaScript 支持通过引用传递,则可以实现这一点。

解决方案 4:

我的看法...这是我的理解。(如果我错了,请随时纠正我)

现在是时候抛弃你所知道的一切关于按值/引用传递的知识了。

因为在 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 怎么办?会发生什么?

favoriteString = 'Hello everyone';
console.log(favoriteString); // Logs 'Hello everyone'
console.log(string1); // Logs 'Hello world'

哦哦……发生了什么事。我们无法通过更改 favoriteString 来更改 string1……为什么?因为我们没有更改字符串对象。我们所做的只是将 favoriteString变量“重新分配”为新字符串。这实际上断开了它与 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'

解决方案 5:

这些短语/概念最初是在 JS 创建之前很久定义的,它们不能准确描述 javascript 的语义。我认为尝试将它们应用于 JS 会造成更多的混乱。

因此,不要纠结于“通过引用/值传递”。

请考虑以下情况:

  1. 变量是指向值的指针

  2. 重新分配变量仅仅使指针指向一个新值。

  3. 重新分配变量永远不会影响指向同一对象的其他变量,因为每个变量都有自己的指针。

所以如果我必须给它起个名字的话,我会说“传递指针” ——我们不处理 JS 中的指针,但底层引擎会处理。

// code
var obj = {
    name: 'Fred',
    num: 1
};

// illustration
               'Fred'
              /
             /
(obj) ---- {}
             \n              \n               1
// code
obj.name = 'George';


// illustration
                 'Fred'


(obj) ---- {} ----- 'George'
             \n              \n               1
// code
obj = {};

// illustration
                 'Fred'


(obj)      {} ----- 'George'
  |          \n  |           \n { }            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

运行代码片段Hide results展开片段

解决方案 6:

通过提供对外部对象的引用,将函数外部的对象传递到函数中。

当你使用该引用来操作其对象时,外部的对象会受到影响。但是,如果你在函数内部决定将引用指向其他对象,则根本不会影响外部对象,因为你所做的只是将引用重定向到其他对象。

解决方案 7:

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

下面是一个例子,传递一个数字(原始类型)

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}

解决方案 8:

《JavaScript:权威指南》一书的这一章对按值和按引用复制、传递和比较进行了非常详细的解释。

在我们结束通过引用操作对象和数组的主题之前,我们需要澄清一个术语。

短语“通过引用传递”有多种含义。对于一些读者来说,该短语指的是一种函数调用技术,允许函数为其参数分配新值,并使这些修改后的值在函数外部可见。但本书中使用该术语的方式并非如此。

这里,我们的意思只是将对对象或数组的引用(而不是对象本身)传递给函数。函数可以使用该引用来修改对象的属性或数组的元素。但如果函数用对新对象或数组的引用覆盖该引用,则该修改在函数外部是不可见的。

熟悉该术语其他含义的读者可能更喜欢说对象和数组是按值传递的,但传递的值实际上是一个引用而不是对象本身。

解决方案 9:

JavaScript 总是按值传递;一切都是值类型。

对象是值,对象的成员函数本身也是值(请记住,函数是 JavaScript 中的一等对象)。此外,关于 JavaScript 中的所有内容都是对象的概念这是错误的。字符串、符号、数字、布尔值、null 和 undefined 都是原语

有时它们可​​以利用从其基本原型继承的一些成员函数和属性,但这只是为了方便。这并不意味着它们本身就是对象。请尝试以下方法作为参考:

x = "test";
console.log(x.foo);
x.foo = 12;
console.log(x.foo);

运行代码片段Hide results展开片段

在这两种情况下,console.log您都会发现其价值undefined

解决方案 10:

在 JavaScript 中,值的类型控制该值是通过值复制还是引用复制来分配。

原始值始终通过值复制进行分配/传递

  • 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],该值是一个复合值。需要注意的是, 和 more 都不cd拥有”该[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`xxa[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将看到更新的值42

来源

解决方案 11:

嗯,它与编程语言中的“性能”和“速度”以及简单的词“内存管理”有关。

在 javascript 中,我们可以将值放在两层:类型 1 -objects类型 2 - 所有其他类型的值,例如string& boolean& 等

如果你把内存想象成下面的方块,其中每一个方块中只能保存一个 type2 值:

在此处输入图片描述

每个 type2-value(绿色)都是一个正方形,而 type1-value(蓝色)是一组正方形

在此处输入图片描述

关键是,如果您想指示一个 type2-value,地址很简单,但如果您想对 type1-value 做同样的事情,那就不容易了!:

在此处输入图片描述

还有一个更复杂的故事:

在此处输入图片描述

因此,以下参考资料可以拯救我们:

在此处输入图片描述

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

但是我们不能对紫色箭头做同样的事情,我们可能想将“约翰”单元格移动到这儿或做许多其他事情......所以紫色箭头会粘在它的位置,只有分配给它的典型箭头才会移动......

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

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

在此处输入图片描述
在此处输入图片描述

解决方案 12:

这是对按值传递和按引用传递(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
  • 适用于 JavaScript 中的所有原始类型(字符串、数字、布尔值、未定义和 null)。

  • 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" }
  • JavaScript 引擎将对象分配给变量c,并且它指向一些内存,比如 (0x012)。

  • 当d=c时,在此步骤d指向同一位置(0x012)。

  • 改变任何一个值都会改变两个变量的值。

  • 函数是对象


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

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

解决方案 13:

一切都是通过价值传递的。

基本类型是按值传递的(即将实际变量值的新副本传递给函数)。

复杂类型(对象)作为“指向对象的指针”传递。因此,您传递的实际内容是按值传递的指针(它是一个地址,一个像其他任何值一样的数值)。显然,如果您尝试在函数内部修改对象的属性,则即使在该函数外部也会反映出修改。这是因为您是通过指向属性的唯一副本的指针来访问属性的。

“按值传递指针”和“按引用传递对象”是同一件事。

解决方案 14:

语义!设置具体的定义必然会导致一些答案和评论不兼容,因为即使使用相同的单词和短语,它们也不是在描述同一件事,但克服这种混淆至关重要(尤其是对于新程序员而言)。

首先,抽象有多个层次,并不是每个人都能理解。学习过第四代或第五代语言的新程序员可能难以理解汇编或 C 程序员熟悉的概念,而这些程序员对指针指向指针指向指针并不感兴趣。通过引用传递并不只是意味着能够使用函数参数变量更改引用对象。

变量:引用内存中特定位置的值的符号的组合概念。此术语通常含义过于丰富,不适合单独用于讨论细节。

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

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

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

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

函数参数:函数调用者传递给函数的函数外部的变量。

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

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

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

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

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

JavaScript 不通过引用传递。如果你仔细阅读,你会发现所有相反的观点都误解了按值传递的含义,并且他们错误地得出结论,认为通过函数参数更新对象数据的能力与“按值传递”同义。

对象克隆/复制:创建一个新对象并复制原始对象的数据。这可以是深层复制或浅层复制,但重点是创建一个新对象。创建对象的副本是与按值传递不同的概念。有些语言区分类对象和结构(或类似对象),并且可能对传递不同类型的变量有不同的行为。但 JavaScript 在传递对象变量时不会自动执行任何此类操作。但没有自动对象克隆并不意味着按引用传递。

解决方案 15:

分享我所知道的 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

运行代码片段Hide results展开片段

解决方案 16:

观察:如果观察者没有任何办法检查引擎的底层内存,就无法确定不可变值是否被复制或引用是否被传递。

JavaScript 或多或少与底层内存模型无关。不存在引用²。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" })。术语“引用”有时也会出现在规范中不相关的部分中。

解决方案 17:

MDN 文档对此进行了清晰的解释,并且并不太冗长:

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

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

解决方案 18:

我理解这一点的简单方法是......

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

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

解决方案 19:

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

现在,人们喜欢无休止地争论“通过引用传递”是否是描述 Java 等实际所做事情的正确方式。关键在于:

  1. 传递对象并不会复制该对象。

  2. 传递给函数的对象可以通过函数修改其成员。

  3. 传递给函数的原始值不能被函数修改。将进行复制。

在我的书中,这被称为通过引用传递。

— Brian Bi -哪些编程语言是通过引用传递的?


更新

以下是对此的反驳:

JavaScript 中没有可用的“通过引用传递”。

解决方案 20:

在 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
", num);
    puts(obj1.item);
    puts(obj2.item);
    return 0;
}

解决方案 21:

引用提问者的话:

原始类型(数字、字符串等)通过值传递,但对象 [...] 既可以按值传递(在这种情况下,我们认为保存对象的变量实际上是对该对象的引用),也可以按引用传递(我们认为对象的变量保存对象本身)。

事实并非如此。对象作为参数传递的方式只有一种。尽管其他答案也区分了原始值和非原始值,但这是一种干扰,而且无关紧要。

无引用传递

考虑一下此代码,它不关心它a是原始类型还是对象:

function alter(arg) {  /* some magic happening here to `arg` */ }

function main() {
    var a, b;
    a = b = { x: 1 }; // Example value. Can be object or primitive: irrelevant here
    console.log(a === b); // true
    alter(a);
    console.log(a === b); // false? (not possible)
}

如果函数alter有某种方式使第二个输出为false那么我们可以说这是按引用传递的,因为 thenarg是 的别名a但这在 JavaScript 中是不可能的。原始的概念与此无关:JavaScript 中没有按引用传递。

对象

在 JavaScript 中,对象是对属性(和槽)集合的引用。当使用对象作为参数调用函数时,这不会创建新的属性集合。函数的参数变量(局部变量)将接收相同的值(即对属性集合的引用)。

变量赋值与对象变异

调用者的数据结构可能会发生变化,也可以通过函数调用进行。这显然只适用于可变值(根据定义),在 JavaScript 中,这是通过为属性赋值来实现的。需要区分两件事:

  • 对对象属性的赋值会改变调用者和被调用者所引用的对象。

  • 赋值给参数变量不会改变调用者的数据。具体来说,如果该参数变量是一个对象(引用),则该引用会被覆盖,因此函数会将自身与调用者的数据分离。

解决方案 22:

对于编程语言律师,我已经阅读了 ECMAScript 5.1(比最新版本更容易阅读)的以下部分,甚至在 ECMAScript 邮件列表中询问。

TL;DR:所有东西都是通过值传递的,但是对象的属性是引用,而对象(Object)的定义在标准中却非常缺乏。

参数列表的构造

第 11.2.4 节“参数列表”说明了如何生成仅由 1 个参数组成的参数列表:

生产的 ArgumentList : AssignmentExpression 评估如下:

  1. 让 ref 成为评估 AssignmentExpression 的结果。

  2. 让 arg 成为 GetValue(ref)。

  3. 返回唯一项目为 arg 的列表。

本节还列举了参数列表中有 0 个或 >1 个参数的情况。

因此,一切都是通过引用传递的。

访问对象属性

第 11.2.1 节“属性访问器”

产生式 MemberExpression : MemberExpression [ Expression ] 的求值如下:

  1. 让 baseReference 成为评估 MemberExpression 的结果。

  2. 令 baseValue 为 GetValue(baseReference)。

  3. 让 propertyNameReference 成为评估 Expression 的结果。

  4. 让 propertyNameValue 为 GetValue(propertyNameReference)。

  5. 调用 CheckObjectCoercible(baseValue)。

  6. 让 propertyNameString 成为 ToString(propertyNameValue)。

  7. 如果正在评估的句法产生式包含在严格模式代码中,则令 strict 为真,否则令 strict 为假。

  8. 返回一个 Reference 类型的值,其基值为 baseValue ,引用名称为 propertyNameString ,且严格模式标志为 strict。

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

关于参考

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

“对象”的定义

在 5.1 版中定义“对象是属性的集合”。因此,我们可以推断,对象的值就是集合,但至于集合的值是什么,规范中定义不明确,需要花点功夫才能理解。

解决方案 23:

我发现最简洁的解释是在AirBNB 风格指南中:

  • 原始类型:当你访问原始类型时,你直接对其值进行操作

+ 细绳
+ 数字
+ 布尔值
+ 无效的
+ 不明确的

例如:

var foo = 1,
    bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9
  • 复杂:当你访问一个复杂类型时,你将对其值的引用进行操作

+ 目的
+ 大批
+ 功能

例如:

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

bar[0] = 9;

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

即,实际上原始类型是通过值传递的,而复杂类型是通过引用传递的。

解决方案 24:

我多次阅读过这些答案,但直到我了解了Barbara Liskov 所说的“通过共享进行呼叫”的技术定义后,我才真正明白

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

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

解决方案 25:

当我想将一个对象作为参数传入,该参数可以进行修改或完全替换时,我发现Underscore.js 库的extend 方法非常有用。

function replaceOrModify(aObj) {
  if (modify) {

    aObj.setNewValue('foo');

  } else {

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

解决方案 26:

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

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

&age对 的引用myAge,但如果您想要该值,则必须使用 转换该引用*age

JavaScript 是一种高级语言,可以为您完成这种转换。

因此,尽管对象是通过引用传递的,但语言会将引用参数转换为值。您无需&在函数定义上使用 来通过引用传递它,也无需*在函数主体上使用 来将引用转换为值,JavaScript 会为您完成此操作。

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

了解更多

解决方案 27:

如果您想要像其他语言一样的(正常)函数参数行为(传递值的副本),那么只需在传递到函数之前克隆对象:

function run()
{
    var test = [];
    test.push(1);

    console.log('before: '+test); // 1

    changeVariable(_.clone(test)); // (Note: I am using lodash _.clone() function)
 
    console.log('after: '+test); // 1 
}


function changeVariable(test2) {
  var test1 = test2;
  test1.push(2); 
  console.log('inside func:', test1);  // inside func: [1,2]
}   


run();    

解决方案 28:

function passByCopy ([...array], {...object})
{
   console .log ("copied objects", array, object)
}

passByCopy ([1,2,3], {a:1, b:2, c:3})

function passByReference (array, object)
{
   console .log ("same objects", array, object)
}

passByReference ([1,2,3], {a:1, b:2, c:3})

解决方案 29:

JavaScript 变量按值传递。例如:

function process(a){
    a = 20
    console.log(a)
}


var a = 10

process(a) // prints 20
console.log(a) // print 10

但是 javascript 对象是通过引用传递的。例如:

function process(obj){
    obj.a = 'aa'
    console.log(obj)
}

let obj ={
    a: 'a_val',
    b: 'b_val'
}

process(obj)
console.log(obj)

输出:

 {a: 'aa', b: 'b_val' }
 { a: 'aa', b: 'b_val' }

要将对象作为按值传递函数,可以使用扩展运算符,如下所示

function process({...obj}){
    obj.a = 'aa'
    console.log(obj)
}

let obj ={
    a: 'a_val',
    b: 'b_val'
}

process(obj)
console.log(obj)

输出:

{ a: 'aa', b: 'b_val' }
{ a: 'a_val', b: 'b_val' }

希望能够解释清楚。

解决方案 30:

判断某事物是否“通过引用传递”的一个简单方法是,你是否可以编写一个“交换”函数。例如,在 C 语言中,你可以这样做:

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

如果您不能在 JavaScript 中执行等效操作,则它不是“通过引用传递”。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用