【问题标题】:How to properly create Javascript prototypes such that class variables don't persist between instantiations?如何正确创建 Javascript 原型,使类变量不会在实例化之间持续存在?
【发布时间】:2015-08-18 22:33:30
【问题描述】:

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

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

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

基本上,每次单击链接时,元素上的data-route 属性都应该被拾取一个事件监听器,一个新的Route 对象应该被实例化(并传递有关预期路线的信息);最后Router 类实际上应该“启动”路线,即。发出 ajax 请求或做一些文档内的东西,无论如何。

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

我真的不明白为什么

**我没有删除任何会影响我的问题的内容;真的可以进一步削减它,但我意识到问题的完整性依赖于原始代码的合理复制。

【问题讨论】:

  • 您确实应该尝试将您的代码简化为可以在此处发布的最小示例,并删除对外部库的任何依赖。您可以在此处发布可运行的 sn-ps({} 右侧第二个图标)。
  • 我强烈建议您看一下 Kyle Simpson 的 OLOO 模式,以消除一些试图强制 JavaScript 成为 OO 语言的困惑。 stackoverflow.com/questions/29788181/…
  • 是的,我也会这么认为,但过去曾为此受到批评,并告诉它是这样做的。但我当然可以......我会发布第二个,剥离代码笔
  • 在实例化时设置的属性持续存在”。他们没有。但是,您的 .url 属性 未在实例化时设置,无论是在您的构造函数中还是在您的 init 方法中。

标签: javascript prototype


【解决方案1】:

你有两个问题。

按价值与按参考

在 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 字段。一个在对象本身上,另一个在原型上。

【讨论】:

    【解决方案2】:

    我非常感谢在 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。但是,它们的初始化方式使它们都指向同一个对象,因此可以通过任一指针来修改 那个 对象。

    完成这项工作的关键是每次创建新对象时为.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() 然后看看是否只调用一次有意义还是每次都需要一个新的实例。

    【讨论】:

    • 这是简洁、连贯且准确的信息。关于代码示例,我完全同意;我过去总是将我的代码削减到证明问题所需的最低限度,但在 cmets 中多次警告说这是错误的 SO 形式,因为我应该“发布我的原始代码,以便确保答案是相关的” .作为一个明确的 SO 用户,您能对此发表评论吗?
    • @Jonline:关键是将其修剪为最小的工作示例。也就是说,如果我将该代码复制/粘贴到空白 HTML 文件中,它将证明您的问题。将其修剪到您认为可能包含错误的最小区域是错误的形式。
    • 为什么这个答案被接受了?这并不能解决问题。
    • @slebetman:不是真的。 route_data 继续作为引用传递并在 RouterRoute 类字段上进行更改。如果你测试它,你会确认它。
    • 我将其标记为正确,因为对理论的解释很好,而且足以帮助我在实际情况下看到问题。由于我的 codepen 是从原始上下文修改的,因此我没有使用 @slebetman 的代码,而是实施了“课程”(这实际上是我的基本理解问题)。不过,为了公平对待到达的访客,@Skarllot 的回答更加彻底,因为确实需要拨打$.extend() 来修复我发布的代码。为这部剧道歉。
    猜你喜欢
    • 1970-01-01
    • 2013-05-30
    • 1970-01-01
    • 2021-06-07
    • 2015-09-30
    • 2012-08-30
    • 2022-12-14
    • 2019-08-05
    • 1970-01-01
    相关资源
    最近更新 更多