【问题标题】:Problems with Knockout mapping when using object inheritance使用对象继承时出现 Knockout 映射的问题
【发布时间】:2015-05-28 22:37:34
【问题描述】:

在将 KO 映射插件与子类对象一起使用时,我不太会遇到一些奇怪的行为。

用例:

  • 服务器有许多用于 AJAX 的简单模型类 API。

  • 我想生成一个存根类来生成 KO observables 对于这些类的属性,因此如果我更改服务器代码 存根类会自动更新为新的 observable 属性 - 我需要这个,所以我可以创建全新的对象而无需映射 现有数据。

  • 然后我希望能够创建一个存根的子类,例如 计算 UI、操作方法、客户端状态等。这些额外的 属性不需要映射回服务器 - 事实上它们不能 是,因为服务器模型将拥有它们(或者它们会在存根中)

我无法正常工作。

这里是一些精简的代码:

function ItemModelBase(data, mapping)
{
    if(data)
        ko.mapping.fromJS(data, mapping, this);

    this.Name = this.Name || ko.observable();
}

function ItemModel(data, mapping)
{
    var _this = this;

    ItemModelBase.call(this, data, mapping);

    this.Salutation = ko.computed(function() { return 'Hello ' + _this.Name(); });
}

// Comment this out and it works
ItemModel.prototype = new ItemModelBase;

function Model()
{
    var _this = this;

    var _mapping = {
        'Items': {
            create: function(o) { return new ItemModel(o.data); }
        }
    };

    this.Items = ko.observableArray();

    this.load = function()
    {
        ko.mapping.fromJS({ 
            Items: [
                { Name: 'Aardvark' },
                { Name: 'Bat' },
                { Name: 'Cheetah' },
                { Name: 'Duiker' },
                { Name: 'Ocelot' }
            ]
        }, _mapping, _this);                          
    };
}

var model = new Model();
ko.applyBindings(model);
model.load(); 

使用视图:

<ul data-bind="foreach: Items">
    <li>
        <span data-bind="text: Name"></span>: 
        <span data-bind="text: Salutation"></span>
    </li>
</ul>

这显示的是正确数量的项目列表,但仅使用最后一项的数据 - 结果:

  • 豹猫:你好豹猫
  • 豹猫:你好豹猫
  • 豹猫:你好豹猫
  • 豹猫:你好豹猫
  • 豹猫:你好豹猫

如果我不对基础对象进行子类化,即注释掉 ItemModel.prototype = new ItemModelBase; 行,它会按预期工作。

我的主要语言是 C#,我发现 Javascript 的继承非常难以理解;这里发生了什么?据我所知,没有原型它可以工作,因为基本构造函数调用只是 A.N.其他向当前实例添加属性负载的 JS 调用。

这适用于这种情况,但大概意味着如果我向基类原型添加一些常用函数,我将无法使用它们?

我想我需要一个主要的问题,所以它是:为什么只显示最后一个项目,我该怎么办?

JFfiddle:https://jsfiddle.net/whelkaholism/d427mssa/

编辑:如果我包含继承然后创建一个新模型,则该新模型也具有最后一项的属性。真的不明白,但它看起来像是在为原型而不是实例分配值?

console.log(ko.toJSON(new ItemModel()));

结果:

{"Name":"Ocelot","Salutation":"Hello Ocelot"}

编辑:Tomalak 发布了一个似乎已被删除的答案,这很遗憾,因为虽然我仍然不知道发生了什么,但它确实提供了修复。

问题是这样的:this.Name = this.Name || ko.observable();

如果我将可观察定义移到映射上方,它会起作用:

function ItemModelBase(data, mapping)
{
    this.Name = ko.observable();
    // Exhibits the same bug as original code
    // this.Name = this.Name || ko.observable();

    if(data)
        ko.mapping.fromJS(data, mapping, this);       
}

奇怪的是离开条件它会导致相同的错误,即使我对 JS 的理解是 this.Name 在那一点上是未定义的,导致该语句具有 `ko.observable()' 的值并且与没有条件的代码?

【问题讨论】:

  • 在这里查看构造函数链接示例:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
  • 恐怕我没有看到该示例的相关性?问题在于无法链接构造函数本身,它是原型继承以及它导致的 Knockout 映射的特定问题。
  • 目前尚不清楚您希望从原型中获得什么。通常,原型包含(仅)方法。您已使其包含 Name,这是一个属性,这意味着所有没有自己的 Name 属性的实例共享一个 Name。
  • 这是一个精简的例子;无可否认,在这里分配原型是没有意义的,但在实际代码中,我可能想添加实用程序方法。但是,我认为您实际上已经确定了我的问题;我在 C# 中考虑属性访问器是可覆盖的,但是如果设置原型导致共享实际属性值,那么这绝对不是我想要的 - 我应该认为它们更像 C# 字段,我猜。

标签: javascript knockout.js


【解决方案1】:

不要尝试在原型链的任何位置创建计算或可观察对象。以下是您可以做和不能做的事情:

  • 您可以修改原型以在视图模型之间共享公共的、不可观察的 (!) 属性和方法。

    var common = {
        baz: function () {
            return ko.unwrap(this.bar) || ko.unwrap(this.foo);
        }
    };
    
    function VM1() {
        this.foo = ko.observable();
    }
    VM1.prototype = common;
    
    function VM2() {
        this.foo = ko.observable();
        this.bar = ko.observable();
    }
    VM2.prototype = common;
    
    var vm2 = new VM2();
    vm2.foo("FOO");
    vm2.baz();  // -> returns "FOO";
    
  • 您不能(阅读:不应该)在原型链中拥有其他视图模型。由于所有原型本身都是活动对象实例,因此它们的所有可观察对象都将在您的 实际 视图模型实例之间共享。这样做有意义的唯一情况是,如果您想在实例之间共享可订阅 (!) 数据。

    function VM0() {
        this.count = ko.observable(0);
    }
    VM0.prototype.increment = function () {
        this.count(this.count() + 1);
    };
    
    function VM1() {
        this.foo = ko.observable();
        this.count.subscribe(function (newCount) {
            // count has increased across all VM1 instances
        });
    }
    VM1.prototype = new VM0();
    
    var vm1 = new VM1();
    vm1.inc();  // -> triggers subscription across all VM1 instances
    
  • 您可以使用函数将常见的、自包含的功能(包括可观察对象)附加到视图模型实例。这基本上就是应用像 ItemModelBase.call(this, ...); 这样的不同构造函数会做的事情。

    function VM1() {
        this.foo = ko.observable();
    }
    
    function VM2() {
        VM1.call(this);   // creates foo observable
        this.bar = ko.observable();
    }
    
  • 您不能在同一个视图模型实例上多次调用ko.mapping(即首先映射基本属性,然后是特定属性)。映射插件将其内部状态保存在实例变量本身中 - 第二次运行将覆盖第一次运行的状态。 (当然,您仍然可以再次映射 相同的属性列表,即更新它们的值。)

所有这些都给 C# 类型的继承层次结构留下了很小的空间。除非您有要共享的通用功能,否则不要费心在 JS 中构建视图模型层次结构。

【讨论】:

  • 这真的很有帮助,谢谢,尤其是最后关于 ko.mapping 的部分。我不需要很深的继承层次结构;我想要做的是让具有简单模型对象的服务器端代码为这些生成KO存根。这意味着如果我向服务器对象添加一个新属性,我会自动将一个可观察对象添加到存根中(这是创建未从现有数据映射的全新对象所必需的)。然后我将创建一个额外的子类级别,例如UI 的计算值,状态的可观察值等。这些不需要是 ... 1/2
  • 2/2 ... 映射到服务器和从服务器映射,因此您的限制不是问题。
  • 我会用更多关于实际用例的信息来澄清这个问题,但到目前为止我得到的两个答案都非常有帮助。
  • 你真的需要放弃基于类的抽象概念,它对你没有任何好处。它助长了过度工程化,并导致将语言硬塞进并非围绕它构建的概念中。例如,JS 中没有任何 actual 构造函数。有构造函数,它们的工作是用一组定义的属性来扩充普通的空对象。原型的存在只是为了避免为 1000 个对象实例创建相同方法的 1000 个副本。
  • 是的;我真正想要的是您的答案中的“通用、独立的功能”,所以我同意了;服务器生成的一个简单的“存根”对象,上面包含所有基本的可观察对象,然后我的实际视图模型调用它的构造函数来应用所有这些对象,然后再添加更复杂的逻辑。它使一切保持整洁,这意味着如果服务器期望的模型添加/删除任何简单属性,我不必进行任何手动更改。
【解决方案2】:

我认为问题在于您对 Name 属性是否存在的测试。因为它存在于原型中,所以它存在。如果你使用

if (!this.hasOwnProperty('Name')) { this.Name = ko.observable(); }

对于您的测试和分配,它应该符合您的预期。

【讨论】:

  • 一个灯泡开始在我头顶发出微弱的光。我想我可能有一个 XY 问题的时刻;谢谢你。
猜你喜欢
  • 2013-04-19
  • 1970-01-01
  • 2021-12-27
  • 2012-06-08
  • 1970-01-01
  • 2022-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多