Knockout计算被触发太多次

时间:2014-01-20 12:13:19

标签: javascript jquery knockout.js

我有一个viewModel和一个根据if条件呈现数据的模板:

<!-- ko template: { data: selectedFolder, if: isTemplateVisible, name: 'selectedFoldersProperties-template' } --><!-- /ko -->

我认为模板连续呈现4次,其中一个原因是isTemplateVisibleko.computed

如果我将if: isTemplateVisible更改为if: selectedFolder,则模板会连续呈现2次。

我有一个jsfiddle演示。

按下按钮后,您会看到“点击”输出4次。

为什么函数被调用了很多次?

<button id="button" type="button">
    Set folder
</button>

<div>
    <!-- ko template: { data: selectedFolder, if: isTemplateVisible, name: 'selectedFoldersProperties-template' } --><!-- /ko -->
</div>

<script type="text/html" id="selectedFoldersProperties-template">
    <span data-bind="text: FolderName"></span>

    <ul data-bind="foreach: $root.getFiles($data)">
        <li>
            <span data-bind="text: FileName"></span>
        </li>
    </ul>
</script>

var viewModel = {
    selectedFolder: ko.observable(null),
    getFiles: function(folderData) {
        console.log("hit");
        return [
            { FileName: "File 1" },
            { FileName: "File 2" }
        ];   
    }
};
viewModel.isTemplateVisible = ko.computed(function(){
    return this.selectedFolder();
}, viewModel);

ko.applyBindings(viewModel);

document.getElementById("button").onclick = function() {
    viewModel.selectedFolder({
        FolderName: "Folder 1"
    });
};

2 个答案:

答案 0 :(得分:3)

您的isTemplateVisible依赖于selectedFolder,因此您可以重新渲染模板。

isTemplateVisible更改为不首先计算:

viewModel.isTemplateVisible = function(){
    return this.selectedFolder();
};

然后更改模板上的if绑定以执行值out:

if: isTemplateVisible()

然后由于依赖性,你的tmeplate将不会运行两次。

Here is the working fiddle


<强>替代地

你可以通过删除iftemplateisvisible东西来简单地完成整个过程:

<button id="button" type="button">
    Set folder
</button>

<div>
    <!-- ko  if: selectedFolder -->
    <!-- ko template: { data: selectedFolder, name: 'selectedFoldersProperties-template' } --><!-- /ko -->
    <!-- /ko -->
</div>

<script type="text/html" id="selectedFoldersProperties-template">
    <span data-bind="text: FolderName"></span>

    <ul data-bind="foreach: $root.getFiles($data)">
        <li>
            <span data-bind="text: FileName"></span>
        </li>
    </ul>
</script>

和viewmodel:

var viewModel = {
    selectedFolder: ko.observable(null),
    getFiles: function(folderData) {
        console.log("hit");
        return [
            { FileName: "File 1" },
            { FileName: "File 2" }
        ];   
    }
};
ko.applyBindings(viewModel);

document.getElementById("button").onclick = function() {
    viewModel.selectedFolder({
        FolderName: "Folder 1"
    });
};

Here is fiddle for the second solution


进一步说明您遇到此问题的原因

If you want to delve deeper in what's going wrong I have created another fiddle which is time stamping your template:

所发生的事情是,在相同的注释绑定上,你有一个observable和一个相互依赖的计算器,因此它们会导致你的模板只渲染两次,只有一次。

所以你要么我建议需要在不同的绑定上分开它们,所以当你评估另一个时你已经解决了,或者你消除了其中一个。

如果您将绑定更改为不具有相互依赖性,例如:

if: selectedFolder(), data: selectedFolder

然后循环不会发生,因为你没有两件事彼此依赖并且负责渲染模板。

基于此,Throttle也不会帮助你,因为所有它都会延迟一个observable的值变化,这会延迟你的第一次渲染,然后是交叉引用引起的相互依赖循环。


getFiles函数由于是绑定上的自执行函数而运行两次。当它被放置在页面上时它会调整一次,而当轮到它在foreach中运行时它会再次调整。

This can be demonstrated here您可以在其中看到您的getFiles函数的调用者都是您模板中的function (){return $root.getFiles() }

如果在没有括号的情况下将该函数更改为foreach: $root.getFiles并使getFiles成为observableArray,以便通过knockout解决它,那么您将不会遇到两次执行问题。

答案 1 :(得分:1)

那是因为你有foreach绑定。

此代码<ul data-bind="foreach: $root.getFiles($data)">将导致计算4次。

希望明确