原始类型(Number,String等)按值传递,但是对象是未知的,因为它们可以是值传递的(如果我们认为持有对象的变量实际上是对象的引用)并且通过引用传递(当我们认为对象的变量保存对象本身时)。
虽然最后并不重要,但我想知道提交传递约定的参数的正确方法是什么。是否有JavaScript规范的摘录,它定义了与此相关的语义?
原始类型(Number,String等)按值传递,但是对象是未知的,因为它们可以是值传递的(如果我们认为持有对象的变量实际上是对象的引用)并且通过引用传递(当我们认为对象的变量保存对象本身时)。
虽然最后并不重要,但我想知道提交传递约定的参数的正确方法是什么。是否有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.item
对...没有影响 obj1
功能之外。num
将会 100
,和 obj2.item
会读 "changed"
。相反,情况是传入的项目是按值传递的。但是通过值传递的项目是 本身 一个参考。 从技术上讲,这称为 呼叫由共享。
实际上,这意味着如果您更改参数本身(如同 num
和 obj2
),这不会影响送入参数的项目。
但如果你改变了 INTERNALS 参数,将传播回来(如同 obj1
)。
它总是按值传递,但对于对象,变量的值是引用。因此,当您传递一个对象并更改它时 会员,这些变化在功能之外持续存在。这样做 看 喜欢通过引用传递。但是,如果您实际更改了对象变量的值,您将看到更改不会持续存在,从而证明它确实是通过值传递的。
例:
function changeObject(x) {
x = {member:"bar"};
alert("in changeObject: " + x.member);
}
function changeMember(x) {
x.member = "bar";
alert("in changeMember: " + x.member);
}
var x = {member:"foo"};
alert("before changeObject: " + x.member);
changeObject(x);
alert("after changeObject: " + x.member); /* change did not persist */
alert("before changeMember: " + x.member);
changeMember(x);
alert("after changeMember: " + x.member); /* change persists */
输出:
before changeObject: foo
in changeObject: bar
after changeObject: foo
before changeMember: foo
in changeMember: bar
after changeMember: bar
变量不“保持”对象,它拥有引用。您可以将该引用分配给另一个变量,现在它们都引用同一个对象。它总是按值传递(即使该值是参考值......)。
没有办法改变作为参数传递的变量所持有的值,如果JS支持通过引用传递,这将是可能的。
我的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'
考虑以下:
所以, 把...忘了吧 “通过引用/值传递” 不要挂断“通过引用/值传递”,因为:
回答你的问题:指针被传递。
// 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
函数外部的对象通过提供对外部obejct的引用而传递给函数。当您使用该引用来操纵其对象时,外部对象因此受到影响。但是,如果在函数内部您决定将引用指向其他内容,则根本不会影响外部对象,因为您所做的只是将引用重定向到其他内容。
可以这样想:它总是通过价值来传递。但是,对象的值不是对象本身,而是对该对象的引用。
这是一个例子,传递一个数字(原始类型)
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}
Javascript总是如此 通过按值,一切都是有价值的。对象是值,对象的成员函数本身就是值(请记住,函数是Javascript中的第一类对象)。此外,关于Javascript中的所有内容都是一个概念 目的这是错的。字符串,符号,数字,布尔值,空值和未定义是 原语。有时他们可以利用从基础原型继承的一些成员函数和属性,但这只是为了方便,并不意味着它们本身就是对象。请尝试以下内容以供参考
x = "test";
alert(x.foo);
x.foo = 12;
alert(x.foo);
在两个警报中,您都会发现未定义的值。
在JavaScript中,值的类型 独自 控制是否将分配该值 值拷贝 或者 参考拷贝。
始终通过value-copy分配/传递原始值:
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]
,这是一个复合值。重要的是要注意两者都不是 c
也不 d
更“拥有”了 [1,2,3]
value - 两者都是对值的等同对等引用。所以,当使用任何一个引用来修改(.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
参考 x
。 x
和 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
将看到更新的值 42
。
关于按价值和参考进行复制,传递和比较的非常详细的解释 本章 “JavaScript:The Definitive Guide”一书。
在我们离开主题之前 通过操纵对象和数组 参考,我们需要澄清一点 命名法。短语“经过 参考“可以有几个含义。 对于一些读者来说,这句话指的是 一种函数调用技术 允许函数分配新值 它的论点和拥有它们 修改后的值可见 功能。这不是这个词的方式 在本书中使用。我们的意思是 只是对对象的引用 或数组 - 不是对象本身 - 被传递给一个函数。一个功能 可以使用引用来修改 对象或元素的属性 数组。但如果功能 用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'???