以字面对象和函数声明的方式声明Knockout视图模型的区别

198

在 Knockout.js 中,我看到视图模型的声明方式如下:

var viewModel = {
    firstname: ko.observable("Bob")
};

ko.applyBindings(viewModel );

或者:

var viewModel = function() {
    this.firstname= ko.observable("Bob");
};

ko.applyBindings(new viewModel ());

这两者有什么区别吗?

我在knockoutjs的谷歌小组中找到了这个讨论,但没有得到很满意的答案。

我可以想象一种情况,如果我想用一些数据初始化模型,比如:

var viewModel = function(person) {
    this.firstname= ko.observable(person.firstname);
};

var person = ... ;
ko.applyBindings(new viewModel(person));

但是如果我不这样做,选择哪种样式有关系吗?


我不认为有什么区别。通常我使用构造函数模式,因为我经常有一些方法,我更喜欢在prototype上声明(例如从服务器获取数据并相应地更新视图模型的方法)。但是你显然仍然可以将它们声明为对象字面量的属性,所以我真的看不出有什么区别。 - James Allardice
4
这与 knockout 没有关系,而与在 JavaScript 中实例化自定义对象的便捷性有关。 - zzzzBov
1
@Kev 如果viewModel是一个构造函数,你需要像这样写成大写字母:var PersonViewModel = function(){...}; - Elisabeth
2个回答

256

使用函数定义视图模型有几个优点。

主要的优点是您可以立即访问一个值为等于被创建实例的 this。这意味着您可以执行:

var ViewModel = function(first, last) {
  this.first = ko.observable(first);
  this.last = ko.observable(last);
  this.full = ko.computed(function() {
     return this.first() + " " + this.last();
  }, this);
};

因此,即使从不同的作用域调用,您的计算可观察对象仍可以绑定到适当的this值。

对于对象字面量,您需要执行以下操作:

var viewModel = {
   first: ko.observable("Bob"),
   last: ko.observable("Smith"),
};

viewModel.full = ko.computed(function() {
   return this.first() + " " + this.last();
}, viewModel);
在这种情况下,你可以直接在计算的可观察对象中使用viewModel,但它会立即被评估(默认情况下),因此不能在对象字面量内部定义它,因为viewModel在对象字面量结束之后才被定义。许多人不喜欢将视图模型的创建分散到多个调用中。
另一种模式你可以使用来确保 this 总是适当的是,在函数中设置一个变量等于适当值的this,然后使用它代替。就像这样:
var ViewModel = function() {
    var self = this;
    this.items = ko.observableArray();
    this.removeItem = function(item) {
         self.items.remove(item);
    }
};

如果您在个别项目的范围内调用 $root.removeItem,那么this 的值实际上将是绑定在该级别的数据(即该项)。在这种情况下使用 self 可以确保它从整个视图模型中被移除。

另一个选择是使用 bind,它由KO添加并得到现代浏览器的支持。在这种情况下,它看起来像:

var ViewModel = function() {
    this.items = ko.observableArray();
    this.removeItem = function(item) {
         this.items.remove(item);
    }.bind(this);
};

在这个主题上还有很多可以说的话,也有许多模式可以探索(比如模块模式和揭示模块模式),但基本上使用函数可以给你更多的灵活性和控制对象的创建方式,以及能够引用实例私有变量的能力。


1
很棒的答案。我经常使用一个函数(使用透露模式)来处理像viewmodels这样的复杂对象。但是对于简单的模型,我使用一个函数,这样我就可以在一个地方处理所有内容。 - John Papa
1
@JohnPapa - 我刚刚在观看你的PluralSight视频,主题是knockout(我已经看了一半了 - 巧合的是,我刚刚看到了关于对象字面量和函数的部分)。非常好的视频,帮助我理解了很多。仅凭这个视频就值得一个月的订阅费用。 - Kev
@Kev - 谢谢。很高兴你从中获得了价值。有些人可能不会关心那个模块,因为它并不是真正的Knockout概念,更多的是JavaScript模式。但我发现随着我在Knockout上的进一步学习,这些概念确实帮助我创建更清晰、更稳定的代码。无论如何,很高兴你喜欢它 :) - John Papa
你的第二个例子中,self.items = ko.observableArray(); 这行代码不应该存在吧?你使用它了,是正确的吗? - JackNova
1
在构造函数中,selfthis是相同的,因此两者等效。在removeItem函数中,self变得更有用,因为当在子项的上下文中执行时,this不再是当前实例。 - RP Niemeyer

12

我使用了一种不同但类似的方法:

var viewModel = (function () {
  var obj = {};
  obj.myVariable = ko.observable();
  obj.myComputed = ko.computed(function () { return "hello" + obj.myVariable() });

  ko.applyBindings(obj);
  return obj;
})();

有几个原因:

  1. 不使用 this,这会在 ko.computed 等中使用时造成混淆。
  2. 我的 viewModel 是单例模式,我不需要创建多个实例(例如 new viewModel())。

这是揭示模块模式,如果没有错误的话。回答很好,但问题不是关于这个模式的。 - Phil
@paul:很抱歉要求查看旧线程。你说过“我的viewModel是单例的,我不需要创建多个实例(即new viewModel())”,但是并不清楚你想表达的“我不需要创建多个实例”有什么优势,请你提供更多用法以便于理解你的方法的优点。谢谢。 - Mou
在我看来,将ViewModel声明为“function”的原因之一是因为您需要多次执行它。然而,在我的例子中,它是一个立即调用的匿名函数,因此它不会被创建多次。它与上面示例中的对象字面量非常相似,但提供了更多的隔离性。 - paulslater19

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接