【问题标题】:Where does a variable point when I set it equal to a global variable?当我将变量设置为等于全局变量时,它指向哪里?
【发布时间】:2015-12-30 17:44:15
【问题描述】:

这是一个简单的例子:

  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

所以这里有两点需要注意:

  1. 一个(看似局部的)变量 - g - 当设置为全局 对象 并更新时,也会更新全局对象 - window.gamelogic 。 (更新 g 也更新了 window.gamelogic)。

  2. A 局部 int,b(设置为全局 int,points)在全局变量更改时不会更新它。 (更新 b 没有更新 window.gamelogic.points

基于第一点,人们会认为当一个 var 指向一个全局对象时,您实际上只是创建了另一个指向该全局对象的相同内存位置的指针。这可以解释为什么更新 g 也会更新 window.gamelogic

然而,b 不更新 window.gamelogic.points 似乎反驳了这个论点。

这是怎么回事?

【问题讨论】:

  • 它与全球性无关。 JS 中的对象是引用类型。从所有引用中都可以看到对象的任何突变。基元是不可变的,因此它们不具有此特性。
  • 记住,JS 是一种“按值”的语言,所以这有助于理解原语;它正在分配一个副本。对象赋值也是“按值”,但由于类型本身是引用类型,因此您实际上永远无法直接访问该对象,因此复制的是引用,而不是对象本身。
  • 另一个重要的部分是对象是可变的。
  • 另请阅读stackoverflow.com/questions/518000/… 中的第二个答案及以后...不过,我想找到一个更好的副本,因为那里的最佳答案绝对糟糕。
  • @squint,我想我需要将第二条评论绣在靠垫上。谢谢你说清楚:)

标签: javascript


【解决方案1】:

在 JavaScript 中,变量(和属性)包含 。值可以有许多不同的类型(数字、字符串、布尔值),其中之一是对象引用,它是对对象的引用,但不是实际的对象本身.考虑对象引用的一种简单方法是,它只是一个数字,就像一个非常大的数组的索引,告诉我们对象在哪里。 (这不是字面意思,但它是一种有用的思考方式。)或者在非编程术语中,乔可能有一张纸,上面写着“123 Any St.”。上面写着,那是乔家所在的地方。论文是一个变量(或属性); “123 任何街”是一个值(在这种情况下是一个对象引用),而房子是一个对象。

对象不是值,因此它们不能存储在变量或属性中(或作为函数参数传递)。只有对它们的引用

当您将值分配给变量或属性(或将其作为参数传递给函数)时,您是在将值从源复制到其中。所以a = b 复制值从ba。当b 包含对象引用时,被复制的是引用,而不是对象;然后ab 都指向同一个对象。这就像玛丽拿出一张纸 (a) 并抄下乔的纸 (b) 上的内容。现在两张纸都说明了乔的房子在哪里。 房子没有被复制,只是告诉我们它在哪里的信息。

考虑到这一点,让我们看看您的代码。当你这样做时

window.gamelogic = {};

它创建一个对象并将其引用(一个值)复制到属性gamelogic。以下是当时记忆中内容的粗略草图,省略了很多不必要的细节:

+-------------------+ | (省略的东西)| +-----------+ 窗口:ref429--->|游戏逻辑:ref758 |------>| | +-------+ +------------+

然后你这样做:

var g = gamelogic;

which (挥手) 创建了一个变量(我稍后会解释挥手)并将gamelogic 中的 分配(复制)给它。由于该值是对象引用,ggamelogic 现在指向同一个位置;也就是说,它们引用同一个对象:

+-------------------+ | (省略的东西)| 窗口:ref429--->|游戏逻辑:ref758 |---+ +-------------------+ | +-----------+ +-->| | | +-----------+ g: ref758--------------------------------+

那你就做

g.points = 1;

在该对象上创建一个名为 points 的属性并将值 1 复制到其中:

+-------------------+ | (省略的东西)| 窗口:ref429--->|游戏逻辑:ref758 |---+ +-------------------+ | +-----------+ +-->|点数:1 | | +-----------+ g: ref758--------------------------------+

让我们强调一下我们在这里所做的事情:我们没有以任何方式更改g 中的值,它仍然和原来一样:对gamelogic 也引用的对象的引用。我们所做的是更改了该对象的状态(通过向其添加属性)。这是关于对象的关键之一:它们具有可以更改的状态。当该状态发生变化时,当您查看它时,您拥有对它的引用的哪个副本并不重要;无论如何,您都会看到相同的对象,其(更新的)状态。

好的,继续:

g.array = ["foo","bar"];

它创建一个数组(它是一个对象),并在我们的对象上创建一个名为array 的属性,并将数组引用的值复制到该属性中:

+-------------------+ | (省略的东西)| 窗口:ref429--->|游戏逻辑:ref758 |---+ +-------------------+ | +---------------+ +------------+ +-->|点数:1 | | 0:“富”| | |数组:ref804 |---->| 1:“酒吧”| g: ref758--------------------------------+ +------------- --+ +----------+

然后你做:

var b = g.points;

which (挥手) 创建一个变量并将g.points (1) 中的值复制到其中:

+-------------------+ | (省略的东西)| 窗口:ref429--->|游戏逻辑:ref758 |---+ +-------------------+ | +---------------+ +------------+ +-->|点数:1 | | 0:“富”| | |数组:ref804 |---->| 1:“酒吧”| g: ref758--------------------------------+ +------------- --+ +----------+ 乙:1

然后

b = b + 1;

b 获取值1,将1 添加到其中,并将新值(2)存储在b 中。 g.points 完全不受影响:

+-------------------+ | (省略的东西)| 窗口:ref429--->|游戏逻辑:ref758 |---+ +-------------------+ | +---------------+ +------------+ +-->|点数:1 | | 0:“富”| | |数组:ref804 |---->| 1:“酒吧”| g: ref758--------------------------------+ +------------- --+ +----------+ 乙:2

以上重点是:

  • 变量和属性(以及函数参数)包含
  • 具有类型,例如字符串、数字、布尔值或对象引用
  • 对象引用只是表示对象所在位置的值。
  • 对象不是值; 对对象的引用是值。
  • 对象具有可以更改的状态。*

(* 如果他们允许的话。可以创建一个不允许更改其状态的对象;那些被称为“不可变”对象。它可以非常、非常方便且功能强大在 JavaScript 中,您可以使用 Object.freeze 和类似的方法来执行此操作,因为默认情况下对象非常松散,您可以通过分配向它们添加属性。在许多其他语言中,它更基本:您只是不定义任何公共可以更改的字段,并且不定义任何更改对象状态的公共方法。)


关于“创建变量”的挥手,我忽略了那里的两个细节,因为它们当时并不重要:

  1. 在 JavaScript 中,var 声明是在代码开始运行之前处理的,因此变量 gb 都是在分步代码运行的第一行之前创建的。最初,它们的值是undefined

  2. 因为您在全局范围内使用了var,所以bg 成为了全局对象的属性,这就是window 所指向的。其实window本身就是全局对象的一个​​属性。在 ES5 之前,所有全局变量都是全局对象的属性。 (在 ES6/ES2015 中,我们有一个新的全局变量类别:使用 letconstclass 创建的全局变量。)

所以从技术上讲,我们的第一个图表应该是这样的:

+----------------------------+ | +-------------------+ | | | (省略的东西)| | +-->|窗口:ref429 |--+ +------------+ |游戏逻辑:ref758 |------>| | | g: 未定义 | +-----------+ | b: 未定义 | +-------------------+

...但是,好吧,这似乎没什么用处。 :-)

【讨论】:

  • 。我喜欢有时,你会看一个写得很好的答案并想'嗯,这是基本的东西,但让我略过'......然后......你得到 draaawwn 并学习,嗯, 相当有点。谢谢你这个很棒的、写得很好的答案:-)
【解决方案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。


* 有人可能会认为原语是按值传递或复制的,但由于它们是不可变的,因此您无法区分。

【讨论】:

  • @SergeyA:你能解释一下真实参考和非真实参考之间的区别吗?
  • @SergeyA:这取决于您所说的“真实参考”是什么意思。我认为 JavaScript 中没有“真正的指针”是有争议的。
  • @SergeyA:我很确定 Ryan 是在谈论“价值引用”,而不是“变量引用”(您似乎指的是)。编程中不只有一种“引用”。
  • @SergeyA:我说的是 C# 具有引用类型的引用。我没有引用(例如)它的 ref 也没有 C++ 风格的引用。
  • @FelixKling ,所有这些 Java/JavaScript (不熟悉 C#,假设那里相同)“参考”都是营销大头贴。有人提出了指针不安全的想法,而不是提供了一种没有指针的语言 - 所以该语言现在是安全的。实际上所做的是一种没有引用,只有指针,但缺少指针算术或运算符地址的语言。我拒绝购买。
【解决方案3】:

与 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 没有引用。首先,让我们定义一些基础知识。通常认为 references 是对象的另一个名称,而 pointers 是独立的对象,可用于访问它们所指向的对象。就此而言,指针不必包含实际的内存地址。只要有一个指针就可以访问它所指向的对象,并且该指针本身就是一个可以修改的对象。

另一方面,引用不是独立的对象。相反,它们是现有对象的别名,或第二个名称。可以更改指针并指向不同的对象。引用不能 - 它始终是分配给它的对象的第二个名称。区别是微妙的,但至关重要。

指针和引用都是我可以称之为“间接访问”的类型——即允许间接访问底层对象的类型。

那么,让我们考虑下面的例子:

function foo(datum) {
    datum = datum2;
}

var dat = datum1;
// Initialize dat
foo(dat);
// what is dat now? (1)

在 foo() 中会发生什么?我们都知道点 (1) 中的dat 仍将保持 datum1 的值,尽管在 foo() 中似乎已更改。我们可以假设整个 object (dat) 是按值传递给foo() - 也就是说,复制到独立对象并给foo() - 这样的操作保留原始dat 不受任何修改.

但是,众所周知,如果我们要修改 foo 如下:

function foo(datum) {
    datum.property = 42;
}

我们将知道在点 (1) 处 dat.property 也是 42。(假设它之前没有设置为这个值)。这意味着,关于将整个 dat object 传递给 foo 的建议是错误的 - 修改副本不会影响原始文件。我们还会注意到原始 datum1 对象的属性也设置为 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 是什么?它不是对象本身,因为修改它会更改其他对象(如果是,则不会是这种情况)。它是我前面提到的间接访问类型的某种方式,所以它要么是指针,要么是引用。让我们看看它是怎么玩的。我们假设它是一个参考。

不过,如果它是一个参考, 数据=基准1; // (1) 数据=基准2; // (2) 会改变 datum1(并使其等于 datum2)。由于引用是别名,因此第 (1) 行将建立 dat 为 datum1 的别名,并且第 (2) 行会将别名 (datum1) 更改为与 datum2 相同。这里不是这样。

让我们检查指针逻辑是否适用。让我们假设,dat 是一个指针。首先到行 (dat = datum1; dat = datum2) 非常适合 - dat 是一个指针,它停止指向 datum1,现在指向 datum2。到目前为止,一切都很好。接下来呢?

让我们看看dat.property = 42;。如果我们假设 dat 是一个指针,那么查看dat. 的唯一合理方法是假设 (.) 是一个成员解引用运算符。也就是说,取消引用左侧指针的运算符访问右侧取消引用对象的成员。像这样读,我们可以清楚地看到指针类比成立 - 取消引用dat 指向 datum1,并且 datum1 的 property 更改为 42。同样的逻辑进一步应用仍然成立。

所以指针类比比引用更有效!为了进一步说服,让我们考虑到 JS 中的对象可以是未定义的这一事实。这对引用没有意义 - 因为它们是第二个名字,所以无对象的第二个名字可能意味着什么(无实体不能有第二个或名字),而指针使一切都清楚 - 当然,一个指针,作为独立对象,可以指向任何东西。

当然,这些指针与 C/C++ 中的指针不同——它们不保存直接内存地址,并且它们上没有指针算法。尽管如此,指针类比比引用类比更适用,并且更容易消除混淆。

【讨论】:

  • 我不会说 JS 有“指针”。该语言中的任何内容都不能与其他语言中使用实际指针的方式相同。
  • @squint,它没有指针算法,也没有地址获取运算符。除此之外,指针心智模型对于理解这些问题非常有帮助——否则这些问题是真正的难题。
  • 编辑您的答案以反映这一点可能会有所帮助 - 例如“虽然 JS 没有指针,但传统的指针模型 可以 是处理语”。或者让它更清晰的东西。
  • @Andy,这是一个很棒的词选择。我很乐意接受。
  • “让我们考虑到 JS 中的对象可以是未定义的事实” 这是错误的。 undefined 是一个原始值,就像true 一样。它不是对象或指针。
【解决方案4】:

g 引用了 gamelogic 引用的同一对象,因此对 g 引用的对象的任何更改都会反映在 gamelogic 中。

这里,

 var b = g.points;
 b = b + 1;

您已将 b 设置为字面值 1,然后将其加 1,因此它现在为 2,并且不会影响 g.points

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 1970-01-01
    • 1970-01-01
    • 2021-11-14
    • 1970-01-01
    • 2017-04-03
    • 2016-12-16
    相关资源
    最近更新 更多