与 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++ 中的指针不同——它们不保存直接内存地址,并且它们上没有指针算法。尽管如此,指针类比比引用类比更适用,并且更容易消除混淆。