如何正确创建 Javascript 原型以使 class 变量不会在实例化之间持续存在?

How to properly create Javascript prototypes such that class variables don't persist between instantiations?

我在 Javascript 原型制作方面遇到了很多困难;我似乎不明白什么时候应该在原型和实例化中声明一个属性。特别是,我不明白,为什么在我的附加代码中(构建一个小路由器 class 的基本练习,将文档更像是一个应用程序而不是页面),在实例化时设置的属性持续存在并因此累积我打算完全成为独立的对象。

这主要是一个学习练习,但我缩减了一些原始代码以帮助进行上下文混淆*。

代码在这里:http://codepen.io/anon/pen/xGNmWM

基本上,每次单击 link 时,元素上的 data-route 属性应该被拾取一个事件侦听器,一个新的 Route 对象应该被实例化(并传递有关预定路线的信息);最后 Router class 实际上应该 "launch" 路线,即。提出 ajax 请求或做一些文档内的事情,无论如何。

现在,Route.url.url 属性应该是,在我明显错误的理解中,每次都重新创建,然后通过传递的数据通知。不知何故,此属性会持续存在,因此会累积每次点击传递的信息。

我真的不明白为什么

**我没有删除任何会影响我的问题的内容;实际上它可以被削减得更多,但我意识到问题的完整性依赖于原始代码的合理传真。

你有两个问题。

按值与按引用

在Javascript中,基本类型,如数字、布尔值和字符串,被传递给另一个函数或按值设置到另一个变量。这意味着它的值被复制(克隆)。

对象类型,如对象、数组和函数,被传递给另一个函数或通过引用设置到另一个变量。这意味着这种类型的变量只是对内存内容的引用。然后,当您将对象设置为变量时,只会复制其内存地址。

当您传递 "route_data" 时,它的引用被复制。然后 Route 构造函数正在处理 Router 持有的相同变量。 如果您在传递对象之前克隆对象,问题就解决了。

...
var route_data = this.route_data[ route_name ];
route_data = $.extend(true, {}, route_data);  // Clone object using jQuery
var route = new Route( route_name, route_data, request_obj);
...

原型

Javascript具有原型继承,这意味着每个对象都指向其原型,即另一个对象。

var obj = { name: 'John' };
console.log(obj.__proto__);

所有对象的根原型默认是Javascript对象。如上例所述。

function Person(name) {
    this.name = name;
}

Person.prototype = {
    getName: function() {
        return this.name;
    }
}

var obj = new Person('John');

console(obj.getName());
console(obj.__proto__);
console(obj.__proto__.__proto__);

当您使用 new 时,将创建一个新的空对象并将其作为 this 绑定到指定函数。此外,它的对象原型将指向被调用函数原型上指定的对象。

get 操作中,Javascript 引擎将搜索整个原型链,直到找到指定的字段。

但是在 set 操作中,如果指定字段在当前对象上不存在,将创建一个新字段。

当您在 Route 上定义 url 字段时,这应该是静态的,因为当您尝试更改它时,会创建一个新字段。

如果您验证 Route 实例,您会注意到您创建了重复的 url 字段。一个关于对象本身,另一个关于原型。

我真的很感激在 SO 而不是 codepen 上发布的最小代码示例。这本来可以让我节省一些时间来阅读你的代码(毕竟你不会为此付钱给我)。

总之,这是有问题的:

Route.prototype = {
    // etc..
    url : {url: false, type: "get", defer: false}
    // etc..
}

基本上,您正在做的是:

var global_shared_object = {url: false, type: "get", defer: false};
Route.prototype.url = global_shared_object;

你看到问题了吗?现在当你这样做时:

var route1 = new Route();
var route2 = new Route();

route1route2.url属性都指向同一个对象。所以修改 route1.url.url 也会修改 route2.url.url.

需要注意的是route1.urlroute2.url是不同的变量。或者更确切地说是不同的指针。修改 route1.url 不会修改 route2.url。但是,它们的初始化方式使它们都指向同一个对象,因此可以从任一指针修改 that 对象。

这项工作的关键是每次创建新对象时都为 .url 创建一个新对象。这可以在构造函数中完成,也可以在您的 .init() 方法中完成:

Route = function (name, route_data, request_obj) {
    this.url = {url: false, type: "get", defer: false};
    this.init(name, route_data, request_obj);
}

隐含的教训:

从中吸取的教训是对象文字语法实际上是一个对象构造函数。不仅仅是语法。

// this:
{};
// is the same as this:
new Object();

所以每当你看到一个对象字面量时,在你的脑海中你应该思考 new Object() 然后看看只调用它一次是否有意义,或者你是否每次都需要一个新的实例。