原始类型(数字、字符串等)按值传递,但对象是未知的,因为它们都可以按值传递(以防我们认为持有对象的变量实际上是对对象的引用) 和按引用传递(当我们认为对象的变量保存对象本身时)。
尽管最后并不重要,但我想知道通过约定呈现参数的正确方法是什么。是否有 JavaScript 规范的摘录,它定义了关于此的语义应该是什么?
原始类型(数字、字符串等)按值传递,但对象是未知的,因为它们都可以按值传递(以防我们认为持有对象的变量实际上是对对象的引用) 和按引用传递(当我们认为对象的变量保存对象本身时)。
尽管最后并不重要,但我想知道通过约定呈现参数的正确方法是什么。是否有 JavaScript 规范的摘录,它定义了关于此的语义应该是什么?
这在 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
根本不是引用,则更改obj1.item
不会obj1
对函数的外部产生影响。num
会100
,obj2.item
会读"changed"
。相反,num
留下10
并obj2.item
保持"unchanged
“。相反,情况是传入的项目是按值传递的。但是按值传递的项目本身就是一个引用。从技术上讲,这称为共享调用。
实际上,这意味着如果您更改参数本身(如使用num
和obj2
),则不会影响输入参数的项目。但是,如果您更改参数的内部结构,它将向上传播(与 一样obj1
)。
它总是按值传递,但对于对象,变量的值是一个引用。因此,当您传递一个对象并更改其成员时,这些更改会在函数之外持续存在。这使它看起来像通过引用传递。但是如果你真的改变了对象变量的值,你会看到改变不会持续存在,证明它确实是按值传递的。
例子:
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
变量不“持有”对象;它包含一个引用。您可以将该引用分配给另一个变量,现在两者都引用同一个对象。它总是按值传递(即使该值是引用......)。
没有办法改变作为参数传递的变量所持有的值,如果 JavaScript 支持按引用传递,这将是可能的。
我的两分钱……我是这么理解的。(如果我错了,请随时纠正我)
是时候扔掉你所知道的关于按值/引用传递的所有内容了。
因为在 JavaScript 中,它是按值传递还是按引用或其他方式传递都无关紧要。重要的是传递给函数的参数的变异与分配。
好的,让我尽力解释我的意思。假设您有几个对象。
var object1 = {};
var object2 = {};
我们所做的是“赋值”...我们已经将 2 个单独的空对象分配给变量“object1”和“object2”。
现在,假设我们更喜欢 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 ......为什么??因为我们没有改变我们的字符串对象。我们所做的只是将 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 中将参数传递给函数也意味着赋值。它们是相同的,并且这两个变量以完全相同的方式连接(也就是说它们不是,除非您将它们分配给同一个对象)。
“修改变量”影响不同变量的唯一时间是底层对象发生变异时(在这种情况下,您没有修改变量,而是修改了对象本身。
区分对象和基元是没有意义的,因为它的工作方式与您没有函数而只是使用等号分配给新变量的方式完全相同。
唯一的问题是当您传递给函数的变量名称与函数参数名称相同时。发生这种情况时,您必须将函数内部的参数视为函数私有的全新变量(因为它是)
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'
这些短语/概念最初是在 JS 被创建之前很久就被定义的,它们并没有准确地描述 javascript 的语义。我认为尝试将它们应用于 JS 会导致更多的混乱。
所以不要纠结于“通过引用/值传递”。
考虑以下:
所以如果我必须给它一个名字,我会说“传递指针” ——我们不处理 JS 中的指针,但底层引擎会处理。
// 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