【问题标题】:Knockout.js Mapping and adding large dataset to ko.observableArrayKnockout.js 映射大型数据集并将其添加到 ko.observableArray
【发布时间】:2016-09-19 16:37:15
【问题描述】:

有很多使用底层数组将大型数据集添加到 ko.observableArray 的示例,例如:

ko.observableArray.fn.pushAll = function(valuesToPush) {
var underlyingArray = this();
this.valueWillMutate();
ko.utils.arrayPushAll(underlyingArray, valuesToPush);
this.valueHasMutated();
return this;  //optional
};

这样做的问题是我失去了我的观察者。当我使用 chrome 并在调试器中暂停时,我得到数组中的值,而不是可观察包装的函数 c()。我还必须观察其中的许多变量。

我发现以下是有效的:

var model = @Html.Raw(Json.Encode(Model));
vm.POs = ko.mapping.fromJS(model.POs);

问题是这很慢。如何使用底层数组添加,然后将 observable wrap 添加到每个变量而不会出现性能问题?

这里还有一些代码:

var vm = {
    POs: ko.observableArray([]),

    headersWithAccounting: ko.observableArray([
        {header_name: "DATE CREATED", property: "DATE_CREATED", state: ko.observable('')},
        {header_name: "DATE ISSUED", property: "DATE_ISSUED", state: ko.observable('')},
        {header_name: "USER CREATED", property: "NAME_USER", state: ko.observable('')},
        {header_name: "PO NUMBER", property: "NO_PO", state: ko.observable('')},
        {header_name: "ORDER STATUS", property: "NAME_STATUS", state: ko.observable('')},
        {header_name: "VENDOR", property: "NAME_VENDOR", state: ko.observable('')},
        {header_name: "TOTAL COST", property: "COST_TOTAL", state: ko.observable('')},
        {header_name: "CTU", property: "ID_CTU", state: ko.observable('')},
        {header_name: "ACCOUNTING CODE", property: "ACCOUNTING_CODE_NAME", state: ko.observable('')},
        {header_name: "CLOSE ORDER", property: "ACCOUNTING", state: ko.observable('')}
    ])
};

function PO() {
var self = this;

self.ID_ORDER = ko.observable();
self.DATE_CREATED = ko.observable();
self.DATE_ISSUED = ko.observable();
self.NAME_STATUS = ko.observable();
self.NAME_VENDOR = ko.observable();
self.NAME_USER = ko.observable();
self.COST_TOTAL = ko.observable();
self.ACCOUNTING_CODE_NAME = ko.observable();
self.ACCOUNTING_CODE_ID = ko.observable();
self.NO_PO = ko.observable();
self.SHOWDETAILS = ko.observable(0);
self.ID_TYPE = ko.observable(0);
self.DESCRIPTION = ko.observable('');
self.FILES = ko.observableArray();
self.POParts = ko.observableArray();
self.ACCOUNTING = ko.observable(0);
self.ID_CTU = ko.observable(0);
self.ACCOUNTING.subscribe(function(val) {
    if (vm.avoidCloseOrder() == 0) {
        $.ajax({
            type: "POST",
            url: '@Url.Action("AccountingCloseOrder", "Report")',
            dataType: 'JSON',
            data: {
                orderId: self.ID_ORDER()
            },
            success: function(msg) {
                if (msg != 'Good') {
                    window.location.href = msg;
                }
            },
            error: function (err) {
                alert("Error closing order, please try again");
            }
        });
    }
});
self.ACCOUNTING_CODE_ID.subscribe(function(val) {
    if (vm.avoidCloseOrder() == 0) {
        $.ajax({
            type: "POST",
            url: '@Url.Action("AccountingCodeChange", "Report")',
            dataType: 'JSON',
            data: {
                orderId: self.ID_ORDER(),
                accountingCodeId: self.ACCOUNTING_CODE_ID()
            },
            success: function(msg) {
            },
            error: function (err) {
                alert("Error closing order, please try again");
            }
        });
     }
  });
}

function POPart() {
    var self = this;

    self.CATEGORY = ko.observable();
    self.SUBCATEGORY = ko.observable();
    self.DESCRIPTION = ko.observable();
    self.PARTNO = ko.observable();
    self.QTY_ORDERED = ko.observable();
    self.QTY_RECEIVED = ko.observable();
    self.COST = ko.observable();
}

function FILE() {
    var self = this;

    self.LOCATION = ko.observable();
}

现在问题在于带有敲除绑定的剃须刀代码:

<div class="row">
    <div class="col-md-12">
        <div data-bind="foreach:POs">
            <table class="table-responsive">
                <thead data-bind="if: $index() == 0 || ($index() > 0 && vm.POs()[$index()-1].SHOWDETAILS() == 1)">
                    <tr data-bind="foreach:vm.headersWithAccounting">
                        <th>
                            <span data-bind="click:$root.sortPOs.bind(property), text:header_name" style="cursor:pointer"></span><i data-bind="css: state"></i>
                        </th>
                    </tr>
                </thead>
                <tbody class="clickabletbody">
                    <tr>
                        <td data-bind="click:$parent.showDetailsFor">
                            <div data-bind="text:DATE_CREATED"></div>
                        </td>
                        <td data-bind="click:$parent.showDetailsFor">
                            <div data-bind="text:DATE_ISSUED"></div>
                        </td>
                        <td data-bind="click:$parent.showDetailsFor">
                            <div data-bind="text:NAME_USER"></div>
                        </td>
                        <td data-bind="click:$parent.showDetailsFor">
                            <div data-bind="text:NO_PO"></div>
                        </td>
                        <td data-bind="click:$parent.showDetailsFor">
                            <div data-bind="text:NAME_STATUS"></div>
                        </td>
                        <td data-bind="click:$parent.showDetailsFor">
                            <div data-bind="text:NAME_VENDOR"></div>
                        </td>
                        <td data-bind="click:$parent.showDetailsFor">
                            <div data-bind="text:COST_TOTAL"></div>
                        </td>
                        <td data-bind="click:$parent.showDetailsFor">
                            <div data-bind="text:ID_CTU"></div>
                        </td>
                        <td>
                            @Html.DropDownList("ddlVendor", new SelectList(Model.ACCOUNTING_CODE_SELECTLIST, "Value", "Text"), "--Select Accounting Code--", new { @class = "form-control", data_bind = "value:ACCOUNTING_CODE_ID" })
                        </td>
                        <td>
                            <input type="checkbox" style="height:30px; width: 30px;" data-bind="checked:ACCOUNTING, enable:(NAME_STATUS() == 'ACCOUNTING' || NAME_STATUS() == 'CLOSED')" />  //PROBLEM RIGHT HERE!!!!
                        </td>
                    </tr>
                </tbody>
            </table>
            <table data-bind="if:SHOWDETAILS, fadeVisible:SHOWDETAILS" style="background-color:antiquewhite" class="table-responsive">
                <!-- ko if:(ID_TYPE() == 2 || ID_TYPE() == 3) -->
                <thead>
                    <tr>
                        <th>
                            DESCRIPTION
                        </th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>
                            <div data-bind="text:DESCRIPTION"></div>
                        </td>
                    </tr>
                </tbody>
                <!-- /ko -->
                <!-- ko if:(ID_TYPE() == 1) -->
                <thead>
                    <tr>
                        <th>
                            CATEGORY
                        </th>
                        <th>
                            SUBCATEGORY
                        </th>
                        <th>
                            DESCRIPTION
                        </th>
                        <th>
                            PART NO
                        </th>
                        <th>
                            QTY ORDERED
                        </th>
                        <th>
                            QTY RECEIVED
                        </th>
                        <th>
                            COST
                        </th>
                    </tr>
                </thead>
                <tbody data-bind="foreach:POParts">
                    <tr>
                        <td>
                            <div data-bind="text:CATEGORY"></div>
                        </td>
                        <td>
                            <div data-bind="text:SUBCATEGORY"></div>
                        </td>
                        <td>
                            <div data-bind="text:DESCRIPTION"></div>
                        </td>
                        <td>
                            <div data-bind="text:PARTNO"></div>
                        </td>
                        <td>
                            <div data-bind="text:QTY_ORDERED"></div>
                        </td>
                        <td>
                            <div data-bind="text:QTY_RECEIVED"></div>
                        </td>
                        <td>
                            <div data-bind="text:COST"></div>
                        </td>
                    </tr>
                </tbody>
                <!-- /ko -->
            </table>
            <table data-bind="if:SHOWDETAILS, fadeVisible:SHOWDETAILS" style="background-color:antiquewhite" class="table-responsive">
                <thead>
                    <tr>
                        <th>
                            Files
                        </th>
                    </tr>
                </thead>
                <tbody data-bind="foreach:FILES">
                    <tr>
                        <td>
                            <a data-bind="attr: {href: LOCATION, target: '_blank'}" class="btn btn-primary btn-md">Download File</a>
                        </td>
                    </tr>
                </tbody>
            </table>
            <div data-bind="if:SHOWDETAILS"><hr /></div>
        </div>
        <!-- /ko -->
    </div>
</div>

问题出在复选框上,chrome控制台显示错误: knockout-3.4.0.js:72 未捕获类型错误:无法处理绑定“启用:函数 (){return (NAME_STATUS() =='ACCOUNTING'|| NAME_STATUS() =='CLOSED')}” 消息:NAME_STATUS 不是函数

这是因为在 value 中它不再是一个带有敲除绑定的函数,它只是一个 value,因此它不是一个函数,这个错误是正确的。我失去了这一点,因为使用底层数组只推送 javascript 值,而不是映射可观察函数。

200 个条目映射当前使用 observables 大约需要 10 秒,如果你问我,这非常荒谬。当我有 1000+ 时会发生什么。即使我一开始只加载其中的 50 个并使用 ajax 在幕后收集其余部分,每次我获得更多数据时,它都会延迟页面几秒钟,直到将它们全部加载。不知道如何解决这个问题。

编辑:

我刚刚有一个 AHA 时刻并解决了丢失绑定问题。 232 个条目现在大约需要 4 秒。仍然想更快地获得它,但这是我改变的。

function PO(data) {
    var self = this;

    self.ID_ORDER = ko.observable(data.ID_ORDER);
    self.DATE_CREATED = ko.observable(data.DATE_CREATED);
    self.DATE_ISSUED = ko.observable(data.DATE_ISSUED);
    self.NAME_STATUS = ko.observable(data.NAME_STATUS);
    self.NAME_VENDOR = ko.observable(data.NAME_VENDOR);
    self.NAME_USER = ko.observable(data.NAME_USER);
    self.COST_TOTAL = ko.observable(data.COST_TOTAL);
    self.ACCOUNTING_CODE_NAME = ko.observable(data.ACCOUNTING_CODE_NAME);
    self.ACCOUNTING_CODE_ID = ko.observable(data.ACCOUNTING_CODE_ID);
    self.NO_PO = ko.observable(data.NO_PO);
    self.SHOWDETAILS = ko.observable(0);
    self.ID_TYPE = ko.observable(data.ID_TYPE);
    self.DESCRIPTION = ko.observable(data.DESCRIPTION);

    self.FILES = ko.observableArray();

    if (data.FILES != null) {
        for (var i = 0; i < data.FILES.length; i++) {
            self.FILES.push(new FILE(data.FILES[i]));
        }
    }

    self.POParts = ko.observableArray();

    if (data.POParts != null) {
        for (var i = 0; i < data.POParts.length; i++) {
            self.POParts.push(new POPart(data.POParts[i]));
        }
    }

    self.ACCOUNTING = ko.observable(data.ACCOUNTING);

    self.ID_CTU = ko.observable(data.ID_CTU);

    self.ACCOUNTING.subscribe(function(val) {
        if (vm.avoidCloseOrder() == 0) {
            $.ajax({
                type: "POST",
                url: '@Url.Action("AccountingCloseOrder", "Report")',
                dataType: 'JSON',
                data: {
                    orderId: self.ID_ORDER()
                },
                success: function(msg) {
                    if (msg != 'Good') {
                        window.location.href = msg;
                    }
                },
                error: function (err) {
                    alert("Error closing order, please try again");
                }
            });
        }
    });

    self.ACCOUNTING_CODE_ID.subscribe(function(val) {
        if (vm.avoidCloseOrder() == 0) {
            $.ajax({
                type: "POST",
                url: '@Url.Action("AccountingCodeChange", "Report")',
                dataType: 'JSON',
                data: {
                    orderId: self.ID_ORDER(),
                    accountingCodeId: self.ACCOUNTING_CODE_ID()
                },
                success: function(msg) {
                },
                error: function (err) {
                    alert("Error closing order, please try again");
                }
            });
        }
    });
}

function POPart(data) {
    var self = this;

    self.CATEGORY = ko.observable(data.CATEGORY);
    self.SUBCATEGORY = ko.observable(data.SUBCATEGORY);
    self.DESCRIPTION = ko.observable(data.DESCRIPTION);
    self.PARTNO = ko.observable(data.PARTNO);
    self.QTY_ORDERED = ko.observable(data.QTY_ORDERED);
    self.QTY_RECEIVED = ko.observable(data.QTY_RECEIVED);
    self.COST = ko.observable(data.COST);
}

function FILE(data) {
    var self = this;

    self.LOCATION = ko.observable(data.LOCATION);
}

以及推送功能:

ko.observableArray.fn.pushAll = function(valuesToPush)
{
    var underlyingArray = this();
    this.valueWillMutate();
    ko.utils.arrayForEach(valuesToPush, function(item) {
        underlyingArray.push(new PO(item));
    });
    this.valueHasMutated();
    return this;
}

有什么想法可以让这个速度超过 4 秒?

【问题讨论】:

  • 目前我们无法判断。性能问题是特定于上下文的,我们缺少您的上下文。你能尝试创建一个有代表性但最小的复制品吗? AFAICT,您可以去掉服务器端位(及其标签),进一步简化事情,以便我们更轻松地提供帮助。
  • 为什么要生成 pushAll 函数?如果第二个数组不是可观察的,为什么不self.observableArray().concat(self.newObservableArray())self.observableArray().concat(self.newArray)

标签: javascript asp.net-mvc razor knockout.js


【解决方案1】:

我并没有真正理解你所说的“我松开了我的束缚”。它可能是一个调试器人工制品(它向您展示了 observable 的值)。

我也可能是“这个”问题。

我得到了这个正在工作的 sn-p(并且您可以在填充数组时使用点击绑定)

var elementVM = (function () {
    function elementVM(message) {
        this.myText = ko.observable(message);
    }
    elementVM.prototype.changeText = function () {
        this.myText(this.myText() + " changed");
    };
  
    return elementVM;
}());

var myVM = (function() {
  var getText = function(count) {
    return "My Text " + (count);
  };
  
  var myObservableArray = ko.observableArray([new elementVM(getText(0))]);
  
  return function() {
    this.myArray = myObservableArray;
    myVM.prototype.populate = function() {
      myObservableArray.valueWillMutate();
      
      for(var i = 1; i <= 1000; ++i) 
      {
        myObservableArray().push(new elementVM("My Text " + i));
      }
      
      myObservableArray.valueHasMutated();
    };
  };
}());

var vm = new myVM();

ko.applyBindings(vm);

setTimeout(function() {
  var start = new Date();
  vm.populate();
  var stop = new Date();
  document.getElementById("pushAll").innerHTML = "pushallTiming: " + (stop - start);
}, 1000);
li {
  list-style: none;
  border: 1px solid black;
  width: auto;
  text-align: center;
}

#pushAll {
  background-color: red;
  width: auto;
  text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="pushAll"></div>
<ul data-bind="template: { name: 'my-template', foreach: myArray }"></ul>
 
<script type="text/html" id="my-template">
    <li data-bind="text: myText, click: changeText"></li>
</script>

【讨论】:

    【解决方案2】:

    实际复制到数组的时间不到 1 秒。这是 valueHasMutated() 函数,它需要几秒钟,这只是 KO 的一部分。我很高兴将数据复制到数组中不需要很长时间。我将尝试仅对 50 个条目进行分页,这将有助于更快地加载 DOM。感谢所有回复的人。

    【讨论】:

      猜你喜欢
      • 2017-05-24
      • 1970-01-01
      • 2017-09-23
      • 1970-01-01
      • 2011-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多