这是一个简单的例子:
1| window.gamelogic = {};
2| var g = gamelogic;
3| g.points = 1;
4| g.array = ["foo","bar"];
5| var b = g.points;
6| b = b + 1;
7| console.log(window.gamelogic);
8| console.log(b);
这将打印:
Object { points=1, array=[2] }
2
所以这里要注意两件事:
一个(看似本地的)变量 - g - 当设置为全局对象并更新时,也会更新golbal对象 - window.gamelogic 。 (更新 g 也更新 window.gamelogic )。
本地 int ,b(设置为全局int, points )在更改时不会更新全局变量。 (更新 b 未更新 window.gamelogic.points )
基于第一点,人们会认为当var指向全局对象时,实际上只是创建另一个指向该全局对象的相同内存位置的指针。这可以解释为什么更新 g 还会更新 window.gamelogic 。
然而, b 没有更新 window.gamelogic.points 似乎反驳了这个论点。
这里发生了什么?
答案 0 :(得分:6)
在JavaScript中,变量(和属性)包含值。值可以有许多不同的类型(数字,字符串,布尔值),其中一个是对象引用,它是对象的引用但不是实际的对象本身。想一个对象引用的简单方法是它只是一个数字,就像一个真正大的数组的索引,告诉我们对象的位置。 (这不是真的,但它是一种有用的方式来思考它。)或者在非编程术语中,乔可能会有一张纸和#34; 123 Any St。&#34 ;写在上面,这是乔的家。这篇论文是变量(或属性); " 123 Any St。"是一个值(在这种情况下是一个对象引用),而house是一个对象。
对象不是值,因此它们不能存储在变量或属性中(或作为函数参数传递)。只有引用才可以。
当您为变量或属性赋值(或将其作为参数传递给函数)时,您将从源复制值。因此a = b
将b
的值复制到a
。当b
包含对象引用时,它是被复制的引用,而不是对象;然后a
和b
都指向同一个对象。这就像玛丽拿出一张纸(a
)并抄写Joe的那张纸上的内容(b
)。现在两张纸都说乔的房子在哪里。 house 没有被复制,只是告诉我们它在哪里的信息。
考虑到这一点,让我们看看你的代码。当你这样做
window.gamelogic = {};
它创建一个对象并将其引用(值)复制到属性gamelogic
中。这是一个粗略的草图,记录了当时记忆中的内容,省略了很多不必要的细节:
+-------------------+ | (stuff omitted) | +-----------+ window:ref429--->| gamelogic: ref758 |------>| | +-------------------+ +-----------+
然后你这样做:
var g = gamelogic;
(挥手)创建一个变量(我将在稍后解释挥手)并在gamelogic
中分配(复制)值它。由于该值是对象引用,g
和gamelogic
现在指向相同的位置;也就是说,他们将引用到同一个对象:
+-------------------+ | (stuff omitted) | window:ref429--->| gamelogic: ref758 |---+ +-------------------+ | +-----------+ +-->| | | +-----------+ g: ref758--------------------------------+
然后你做
g.points = 1;
在该对象上创建一个名为points
的属性,并将值1
复制到其中:
+-------------------+ | (stuff omitted) | window:ref429--->| gamelogic: ref758 |---+ +-------------------+ | +-----------+ +-->| points: 1 | | +-----------+ g: ref758--------------------------------+
让我们重点介绍一下我们在这里所做的事情:我们没有以任何方式改变g
中的价值,它仍然与以下相同:A对gamelogic
也引用的对象的引用。我们所做的是更改了该对象的状态(通过向其添加属性)。这是关于对象的关键事项之一:它们具有可以改变的状态。当这种状态发生变化时,看到它时你所引用的副本的副本并不重要;无论如何,你都会看到同一个对象及其(更新的)状态。
好的,继续:
g.array = ["foo","bar"];
创建一个数组(它是一个对象),并在我们的对象上创建一个名为array
的属性,并将数组引用的值复制到属性中:
+-------------------+ | (stuff omitted) | window:ref429--->| gamelogic: ref758 |---+ +-------------------+ | +---------------+ +----------+ +-->| points: 1 | | 0: "foo" | | | array: ref804 |---->| 1: "bar" | g: ref758--------------------------------+ +---------------+ +----------+
然后你做:
var b = g.points;
(挥手)创建一个变量并将值从g.points
(1
)复制到其中:
+-------------------+ | (stuff omitted) | window:ref429--->| gamelogic: ref758 |---+ +-------------------+ | +---------------+ +----------+ +-->| points: 1 | | 0: "foo" | | | array: ref804 |---->| 1: "bar" | g: ref758--------------------------------+ +---------------+ +----------+ b: 1
然后
b = b + 1;
从1
获取值b
,向其添加1
,并将新值(2
)存储在b
中。 g.points
完全不受影响:
+-------------------+ | (stuff omitted) | window:ref429--->| gamelogic: ref758 |---+ +-------------------+ | +---------------+ +----------+ +-->| points: 1 | | 0: "foo" | | | array: ref804 |---->| 1: "bar" | g: ref758--------------------------------+ +---------------+ +----------+ b: 2
以上要点是:
(*如果他们允许的话。可以创建一个不允许其状态被更改的对象;这些对象被称为"不可变的"对象。它可以非常,非常方便和强大。在JavaScript中,你用Object.freeze
和类似的方法做到这一点,因为默认情况下对象非常松散,你只需通过赋值就可以为它们添加属性。在许多其他语言中,它更基本:你只是没有定义任何可以改变的公共领域,也没有定义任何改变对象状态的公共方法。)
关于那个"创建变量"挥手,我忽略了那里的两个细节,因为它们并不重要:
在JavaScript中,var
声明在代码开始运行之前处理,因此变量g
和b
都已在步骤的第一行之前创建-step代码运行。最初,其中包含值undefined
。
由于您在全局范围内使用了var
,因此b
和g
成为全局对象的属性,这是window
所指向的属性。实际上,window
本身是全局对象的属性。通过ES5,所有全局变量都是全局对象的属性。 (在ES6 / ES2015中,我们有一个新的全局变量类别:使用let
,const
或class
创建了一个全局变量。)
从技术上讲,我们的第一张图应该是这样的:
+--------------------------+ | +-------------------+ | | | (stuff omitted) | | +-->| window: ref429 |--+ +-----------+ | gamelogic: ref758 |------>| | | g: undefined | +-----------+ | b: undefined | +-------------------+
......但是,好吧,这似乎不太有用。 : - )
答案 1 :(得分:2)
这不是整数和对象之间的区别*;你正在对每个人进行不同的操作。考虑一下,没有涉及的整数,而是相同的操作:分配给变量,而不是对象的属性,即该变量的值:
var a = {};
var b = a;
var c = a;
b.x = 'hello, world!'; // a, b, and c now all refer to { x: 'hello, world!' }
c = { y: 'foo' }; // a and b still refer to { x: 'hello, world!' };
// c refers to a different object now
将此视为一切参考,并=
覆盖引用。您可以更改变量或属性引用的内容,但更改对象的属性(变量或属性可以引用)会更改对象。
请注意,这与许多其他常用语言的工作方式相同,包括C#,Java和Python。
*有人可能认为原语是按值传递或复制的,但由于它们是不可变的,所以你无法区分它们。
答案 2 :(得分:1)
与JavaScript一起使用的简单心理模型是忘记在众多书籍中讲述的内容并接受JavaScript具有指针这一事实。虽然根据语言规范,JavaScript没有指针,但传统的指针模型可以成为接近语言的有用方法。
当这样考虑时,它只有 指针用于基元的对象和值类型。当使用这个模型时,没有任何神秘感。这是细分:
window.gamelogic = {}; // creates an object, stores *pointer* to the object in window.gamelogic (global)
var g = gamelogic; // creates an pointer g, which points to the same object as gamelogic
g.points = 1; // Access object by pointer, set's object value to 1
g.array = ["foo","bar"]; // same
var b = g.points; // g.points is a primitive, so b is a copy of g.points
b = b + 1; // b is incremented indepently of g.points
JavaScript引用的声明简直令人困惑。它根本没有参考。
我将解释为什么我认为JavaScript没有引用。首先,让我们来定义一些基础知识。通常认为引用是对象的另一个名称,而指针是可用于访问它们指向的对象的独立对象。就此而言,指针不必具有实际的内存地址。有一个指针允许一个人访问它指向的对象,并且指针本身是一个可以修改的对象就足够了。
另一方面,引用不是独立的对象。相反,对于现有对象,它们是别名或第二名。指针可以更改并指向不同的对象。引用不能 - 它将始终是它所分配的对象的第二个名称。差异很微妙,但至关重要。
指针和引用都是我可以称之为'间接访问' type - 即允许间接访问底层对象的类型。
因此,让我们考虑以下示例:
function foo(datum) {
datum = datum2;
}
var dat = datum1;
// Initialize dat
foo(dat);
// what is dat now? (1)
foo()中会发生什么?我们都知道点(1)中的dat
仍将保持datum1的值,尽管foo()中似乎有所改变。我们可以假设整个对象(dat)按值传递给foo()
- 也就是说,复制到独立对象并提供给foo()
- 这样的操作保留了原始{{} 1}}来自任何修改。
但是,众所周知,如果我们要修改foo如下:
dat
我们知道在第(1)点,dat.property也是42.(假设它之前未设置为此值)。这意味着,关于将整个dat object 传递给foo的建议是错误的 - 修改副本不会影响原始版本。我们还会注意到原始的datum1对象也将属性设置为42 - 这意味着传入函数与简单赋值没有什么不同。为简单起见,我们甚至可以从图片中删除函数调用,并使用以下代码:
function foo(datum) {
datum.property = 42;
}
那么,var datum1 = Object();
var datum2 = Object();
// datum1 and datum2 are differently initialized
var dat = datum1;
dat = datum2; // datum1 and datum2 are unchanged
dat.property = 42; // now datum2 property is 42, datum1 is unchanged
dat = datum1; // datum2 is unchanged, still has 42
dat.property = 42; // datum1.property is 42
是什么?它不是对象本身,因为修改它会改变其他对象(如果是的话就不会这样)。它是我前面提到的间接访问类型的某种方式,因此它是指针或引用。让我们看看它是如何发挥作用的。我们假设它是一个参考。
如果它可以作为参考, dat = datum1; //(1) dat = datum2; //(2) 会改变datum1(并使其等于datum2)。由于引用是别名,line(1)会将dat建立为datum1的别名,而line(2)会将别名(datum1)更改为与datum2相同。不是这里的情况。
让我们检查指针逻辑是否适用。让我们假设,dat是一个指针。第一行(dat
)确实很合适 - dat是一个指针,它停止指向datum1,现在指向datum2。到现在为止还挺好。接下来呢?
让我们看一下dat = datum1; dat = datum2
。如果我们假设dat是一个指针,那么查看dat.property = 42;
唯一合理的方法是假设(。)是一个成员解引用运算符。也就是说,取消引用左侧指针的操作员访问右侧的解除引用对象的成员。像这样阅读,我们可以清楚地看到指针类比成立 - 解除引用dat.
指向datum1,而datum1的dat
更改为42.相同的逻辑仍然适用。
所以指针比喻比参考更好!为了进一步说服,让我们考虑一下JS中的对象可以是未定义的事实。这对于引用没有意义 - 因为它们是第二个名称,无对象的第二个名称是什么意思(没有实体不能有第二个或第一个名称),而指针使一切都清楚 - 当然,指针,作为独立的对象,可以指向任何东西。
当然,这些指针与C / C ++中的指针不同 - 它们没有直接的内存地址,并且没有指向它们的指针。尽管如此,指针类比保持更好,并且比引用更容易清除混淆。
答案 3 :(得分:0)
g
引用gamelogic
引用的同一对象,因此g
引用的对象所做的任何更改都会反映在gamelogic
中。
这里,
var b = g.points;
b = b + 1;
您已将b
设置为字面值1
,然后将其添加1,因此它现在为2并且它不会影响g.points
。