JavaScript是否通过引用传递?

JavaScript是通过引用传递还是通过值传递?

下面是一个来自JavaScript: The Good Parts的例子。我对矩形函数的my参数感到非常困惑。它实际上是undefined,并在函数内部重新定义。没有原始参考文献。如果我从函数参数中删除它,内部区域函数就不能访问它。

它是一个闭包吗?但是没有返回函数。

var shape = function (config) {
var that = {};
that.name = config.name || "";
that.area = function () {
return 0;
};
return that;
};


var rectangle = function (config, my) {
my = my || {};
my.l = config.length || 1;
my.w = config.width || 1;
var that = shape(config);
that.area = function () {
return my.l * my.w;
};
return that;
};


myShape = shape({
name: "Unhnown"
});


myRec = rectangle({
name: "Rectangle",
length: 4,
width: 6
});


console.log(myShape.name + " area is " + myShape.area() + " " + myRec.name + " area is " + myRec.area());
262140 次浏览

原语通过值传递,对象通过“引用的副本”传递。

具体来说,当你传递一个对象(或数组)时,你是在(无形地)将一个引用传递给该对象,并且有可能修改该对象的内容,但如果你试图覆盖引用,它不会影响调用者持有的引用的副本-即引用本身是按值传递的:

function replace(ref) {
ref = {};           // this code does _not_ affect the object passed
}


function update(ref) {
ref.key = 'newvalue';  // this code _does_ affect the _contents_ of the object
}


var a = { key: 'value' };
replace(a);  // a still has its original value - it's unmodfied
update(a);   // the _contents_ of 'a' are changed

你可以这样想:

当你在ECMAscript中创建一个对象对象时,这个对象会以一个神秘的ECMAscript通用的地方形式形成,没有人能够得到。你所得到的只是一个参考到这个神秘的地方的对象。

var obj = { };

即使obj也只是对对象的引用(它位于那个特殊的美妙位置),因此,你只能传递这个参考。实际上,任何访问obj的代码段都会修改很远很远的对象

与C语言一样,最终,所有内容都是通过值传递的。不像C语言,你不能实际备份和传递变量的位置,因为它没有指针,只有引用。

它所引用的都是对象,而不是变量。有几种方法可以达到相同的结果,但它们必须手工完成,而不仅仅是在调用或声明站点上添加关键字。

在实践中,阿尔尼塔克是对的和容易理解,但最终在JavaScript中,一切都是通过值传递的。

什么是“价值”?物体的?它是对象引用。

当你传入一个对象时,你得到这个值的一个副本(因此Alnitak描述的“引用的副本”)。如果你改变了这个值,你不会改变原来的对象;您正在更改该引用的副本。

JavaScript是按值传递的。

对于原语,则传递原语的值。对于对象,对象的引用值;是过去了。

带对象的示例:

var f1 = function(inputObject){
inputObject.a = 2;
}


var f2 = function(){
var inputObject = {"a": 1};
f1(inputObject);
console.log(inputObject.a);
}

调用f2会打印出"a"值为2而不是1,因为引用被传递并且"a"更新了引用中的值。

使用primitive的示例:

var f1 = function(a){
a = 2;
}
var f2 = function(){
var a = 1;
f1(a);
console.log(a);
}

调用f2会打印出"a"值为1。

“Global"JavaScript变量是窗口对象的成员。您可以将引用作为窗口对象的成员访问。

var v = "initialized";


function byref(ref) {
window[ref] = "changed by ref";
}


byref((function(){for(r in window){if(window[r]===v){return(r);}}})());
// It could also be called like... byref('v');
console.log(v); // outputs changed by ref

注意,上面的例子不适用于函数中声明的变量。

我的两分钱....JavaScript是通过引用还是值传递参数无关紧要。真正重要的是赋值还是变异。

我写了一个更长的更详细的解释在这个链接

当你传递任何东西(无论是一个对象还是一个原语)时,JavaScript所做的只是在函数内部分配一个新变量……就像使用等号(=)一样。

这个参数在函数内部的行为和你用等号赋值一个新变量的行为是完全一样的。举几个简单的例子。

var myString = 'Test string 1';


// Assignment - A link to the same place as myString
var sameString = myString;


// If I change sameString, it will not modify myString,
// it just re-assigns it to a whole new string
sameString = 'New string';


console.log(myString); // Logs 'Test string 1';
console.log(sameString); // Logs 'New string';

如果我将myString作为参数传递给一个函数,它的行为就好像我简单地将它赋给了一个新变量。现在,让我们做同样的事情,但是用一个函数而不是简单的赋值

function myFunc(sameString) {


// Reassignment... Again, it will not modify myString
sameString = 'New string';
}


var myString = 'Test string 1';


// This behaves the same as if we said sameString = myString
myFunc(myString);


console.log(myString); // Again, logs 'Test string 1';

当你将对象传递给函数时,你可以修改对象的唯一原因是你没有重新赋值…相反,对象可以更改或突变....同样,它的工作方式是一样的。

var myObject = { name: 'Joe'; }


// Assignment - We simply link to the same object
var sameObject = myObject;


// This time, we can mutate it. So a change to myObject affects sameObject and visa versa
myObject.name = 'Jack';
console.log(sameObject.name); // Logs 'Jack'


sameObject.name = 'Jill';
console.log(myObject.name); // Logs 'Jill'


// If we re-assign it, the link is lost
sameObject = { name: 'Howard' };
console.log(myObject.name); // Logs 'Jill'

如果我将myObject作为参数传递给一个函数,它的行为就好像我简单地将它赋给了一个新变量。同样的事情,同样的行为,但是是一个函数。

function myFunc(sameObject) {
// We mutate the object, so the myObject gets the change too... just like before.
sameObject.name = 'Jill';


// But, if we re-assign it, the link is lost
sameObject = {
name: 'Howard'
};
}


var myObject = {
name: 'Joe'
};


// This behaves the same as if we said sameObject = myObject;
myFunc(myObject);
console.log(myObject.name); // Logs 'Jill'

每次你把一个变量传递给一个函数,你都是在“赋值”。不管参数的名称是什么,就像你使用等号=一样。

永远记住等号=表示赋值。 将参数传递给函数也意味着赋值。 它们是相同的,两个变量以完全相同的方式连接

修改一个变量影响另一个变量的唯一情况是底层对象发生变化。

区分对象和原语是没有意义的,因为它的工作方式就像你没有一个函数,只是用等号赋值给一个新变量一样。

函数参数是按值或按共享传递的,但在JavaScript中从不通过引用 !

"值

基元类型按值传递:

var num = 123, str = "foo";


function f(num, str) {
num += 1;
str += "bar";
console.log("inside of f:", num, str);
}


f(num, str);
console.log("outside of f:", num, str);

工作分配在函数作用域中是不可见的。

这也适用于__abc0,它是一个复合数据类型,但不可变:

var str = "foo";


function f(str) {
str[0] = "b"; // doesn't work, because strings are immutable
console.log("inside of f:", str);
}


f(str);
console.log("outside of f:", str);

Call-by-Sharing

对象,也就是说所有不是基元的类型,都是通过共享传递的。保存对象引用的变量实际上只保存该引用的副本。如果JavaScript采用引用求值策略,则变量将保存原始引用。这是共享和引用之间的关键区别。

这种区别的实际后果是什么?

var o = {x: "foo"}, p = {y: 123};


function f(o, p) {
o.x = "bar"; // Mutation
p = {x: 456}; // Reassignment
console.log("o inside of f:", o);
console.log("p inside of f:", p);
}


f(o, p);


console.log("o outside of f:", o);
console.log("p outside of f:", p);

变异意味着修改现有Object的某些属性。变量所绑定的引用副本和引用该对象的引用副本保持不变。因此,突变在调用者的作用域中是可见的。

重新分配表示替换绑定到变量的引用副本。由于它只是一个副本,持有同一引用副本的其他变量不受影响。因此,重赋在调用者的作用域中不可见,就像在引用求值策略中那样。

关于ECMAScript中评估策略的进一步信息。

为了创建一个使用const的简单示例…

const myRef = { foo: 'bar' };
const myVal = true;


function passes(r, v) {
r.foo = 'baz';
v = false;
}


passes(myRef, myVal);


console.log(myRef, myVal); // Object {foo: "baz"} true

没有纯粹主义,我认为在JavaScript中通过引用模拟标量参数的最好方法是使用object,就像前面的答案告诉。

然而,我的做法有点不同:

我已经在函数调用内部进行了对象赋值,因此可以在函数调用附近看到引用形参。它增加了源代码的可读性。

在函数声明中,我像注释一样放置属性,出于同样的原因:可读性。

var r;


funcWithRefScalars(r = {amount:200, message:null} );
console.log(r.amount + " - " + r.message);




function funcWithRefScalars(o) {  // o(amount, message)
o.amount  *= 1.2;
o.message = "20% increase";
}

在上面的例子中,null清楚地指明了一个输出引用参数。

退出:

240 - 20% Increase

在客户端,console.log应该被alert取代。

★★★

另一个方法可读性更强:

var amount, message;


funcWithRefScalars(amount = [200], message = [null] );
console.log(amount[0] + " - " + message[0]);


function funcWithRefScalars(amount, message) {  // o(amount, message)
amount[0]  *= 1.2;
message[0] = "20% increase";
}

在这里,你甚至不需要创建新的虚拟名称,如上面的r

原语是按值传递的。但如果你只需要读取一个原语的值(在函数被调用时,值是不知道的),你可以传递一个函数,在你需要它的时候检索它的值。

function test(value) {
console.log('retrieve value');
console.log(value());
}


// call the function like this
var value = 1;
test(() => value);

我不能看到引用传递的例子中,人们试图证明这样。我只看到按值传递

对于保存对象引用的变量,引用是这些变量的价值,因此引用被传递,然后是按值传递

在这样的陈述中,

var a = {
b: "foo",
c: "bar"
};

“a”的值不是对象,而是(到目前为止唯一的)对它的引用。换句话说,该对象不在变量a中——它的引用在。我认为这对于只熟悉JavaScript的程序员来说似乎很难。但是对于懂Java、c#和C的人来说,这是很容易的。

对象总是按引用传递,原语总是按值传递。只要将参数保持在对象的相同地址即可。

这里有一些代码来说明我的意思(在JavaScript沙盒中试试,比如https://js.do/)。

不幸的是,你不能只保留参数的地址;还保留了所有原始成员值。

a = { key: 'bevmo' };
testRetain(a);
document.write(' after function ');
document.write(a.key);




function testRetain (b)
{
document.write(' arg0 is ');
document.write(arguments[0].key);
b.key = 'passed by reference';
var retain = b; // Retaining the original address of the parameter


// Address of left set to address of right, changes address of parameter
b = {key: 'vons'}; // Right is a new object with a new address
document.write(' arg0 is ');
document.write(arguments[0].key);


// Now retrieve the original address of the parameter for pass by reference
b = retain;
document.write(' arg0 is ');
document.write(arguments[0].key);
}

结果:

Arg0是bevmo Arg0是vons Arg0是在引用传递的函数之后通过引用传递