使用对象继承时的Knockout映射问题

Problems with Knockout mapping when using object inheritance

本文关键字:Knockout 映射 问题 对象 继承      更新时间:2023-09-26

当使用KO映射插件与子类对象时,我不太明白一些奇怪的行为。

用例:

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

  • 我想生成一个stub类来生成KO可观察对象的属性,这样如果我更改服务器代码存根类会自动更新为新的可观察对象属性-我需要这个,所以我可以创建全新的对象没有映射从现有数据。

  • 我想要能够创建一个子类的存根,例如:计算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>

这显示的是一个正确数量的项目列表,但只使用最后一项的数据- result:

  • Ocelot: Hello Ocelot
  • Ocelot: Hello Ocelot
  • Ocelot: Hello Ocelot
  • Ocelot: Hello Ocelot
  • Ocelot: Hello Ocelot

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

我的主要语言是c#,我发现Javascript的继承非常难以理解;这是怎么回事?据我所知,没有原型,它的工作原理,因为基本构造函数调用只是一个n.s.其他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的理解是这样的。名称在那时是未定义的,导致该语句具有' ko.observable()'的值并且与没有条件的代码相同?

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

  • 你可以修改原型来在视图模型之间共享通用的、不可观察的属性和方法。

    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中构建视图模型层次结构。

我认为问题是您的测试是否存在一个名称属性。因为它存在于原型中,它存在。如果使用

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

对于您的test-and-assign,它应该做您所期望的。