【问题标题】:Knockout js computed running balance infinite loopsKnockout js 计算运行平衡无限循环
【发布时间】:2013-10-20 23:01:34
【问题描述】:

拥有一组可观察的银行交易,其中包含金额等。

我试图保持作为可计算的运行平衡,但我似乎陷入了无限循环和各种不良情况。

这是我能想到的最简单的小提琴 - http://jsfiddle.net/Nnyxx/2/

JS:

var transactions = [{Amount: -100}, {Amount: 125}, {Amount: 10}, {Amount: 25}, {Amount: -125}, {Amount: 400}];

var ViewModel = function() {
    this.OpeningBalance = ko.observable(1000);
    this.RunningBalance = ko.observable(this.OpeningBalance());
    this.ClosingBalance = ko.observable(this.RunningBalance());

    this.UpdateRunningBalance = ko.computed({
        read: function() {
            return this.RunningBalance();
        },
        write: function(amount) {
            this.RunningBalance(this.RunningBalance() + amount);

            return amount;
        }
    }, this);

    this.Transactions = ko.observableArray(ko.mapping.fromJS(transactions)());
}

var model = new ViewModel();

ko.applyBindings(model);

HTML:

<table>
    <thead>
        <tr>
            <th width="150"></th>
            <th width="150">Money Out</th>
            <th width="150">Money In</th>
            <th>Balance</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td colspan="3"><strong>Opening Balance</strong></td>
            <th>
                <span data-bind="text: OpeningBalance"></span>
            </th>
        </tr>
        <!-- ko foreach: Transactions -->
        <tr>
            <td></td>
            <!-- ko if: Amount() < 0 -->
            <td data-bind="text: Amount"></td>
            <td></td>
            <!-- /ko -->
            <!-- ko if: Amount() > 0 -->
            <td></td>
            <td data-bind="text: Amount"></td>
            <!-- /ko -->
            <td>
                <span data-bind="text: $root.UpdateRunningBalance(Amount())"></span>
            </td>
        </tr>
        <!-- /ko -->
        <tr>
            <td colspan="3"><strong>Closing Balance</strong></td>
            <th>
                <span data-bind="text: ClosingBalance"></span>
            </th>
        </tr>
    </tbody>
</table>

它仍然不完整,因为我最终围绕如何让期初余额显示和重新计算。

流动余额需要是可观察的,因此如果期初余额或交易发生变化,它将重新计算。

期末余额也需要是最终的流动余额。

【问题讨论】:

    标签: knockout.js computed-observable


    【解决方案1】:

    我认为你在这里遗漏了一些重要的东西。

    在 Knockout 中没有运行平衡之类的东西。 屏幕上显示的每个数据点都必须绑定到视图模型中的实时值。

    没有“仅在构建当前行时存在”的值,类似于 PHP 在循环期间构建 HTML 时所做的事情。如果您需要 10 个“运行值”行,请在视图模型中设置 10 个不同的值。

    比较一下:

    function Transaction(amount, previousBalance) {
        this.amount = amount;
        this.balance = previousBalance + amount;
    }
    
    function Account(openingBalance, transactions) {
        var self = this;
    
        // properties
        self.openingBalance = openingBalance;
        self.transactions = ko.observableArray();
        self.closingBalance = ko.computed(function () {
            var transactions = self.transactions(),
                lastTransaction = transactions[transactions.length - 1];
            return lastTransaction ? lastTransaction.balance : openingBalance;
        });
    
        // methods
        self.addTransaction = function (amount) {
            var previousBalance  = self.closingBalance();
            self.transactions.push(new Transaction(amount, previousBalance));
        };
    
        // init    
        ko.utils.arrayForEach(transactions, function (t) {
            self.addTransaction(t.Amount);
        });
    }
    
    var transactions = [{Amount: -100}, {Amount: 125}, {Amount: 10}, 
                        {Amount: 25}, {Amount: -125}, {Amount: 400}];
    
    ko.applyBindings(new Account(1000, transactions));
    

    (在这里观看直播http://jsfiddle.net/Nnyxx/5/)

    注意每个表行如何在视图模型中实际存在的当前运行值的相应值。

    Knockout 不是客户端页面处理库。它是一个数据绑定库。它只能显示页面生命周期内真正存在的数据。

    此外,并非 Knockout 中的每个值都需要包装在可观察值中,只有您希望在页面生命周期内更改的值。 Account.openingBalance 或 Tansaction.amount 是不太可能更改的属性,它们可以不包装。

    无关,但尽量遵循 JS 大小写的约定:仅对构造函数和构造函数使用 PascalCaseNames,所有其他变量(成员函数、属性、局部变量)将得到 camelCaseNames。

    还有一件事 - 如果您使用货币值,请注意 IEEE 754 浮点运算的陷阱。在您在其他任何地方使用它们之前,所有操作都应该被适当地四舍五入。如果您在没有任何中间舍入的情况下拖动运行值(如上面的代码示例中),您最终可能会得到由于内部数字表示问题而关闭的值。

    【讨论】:

    • 太棒了,感谢您的帮助。适当地注意到有关语法和舍入的 cmets。
    • 很高兴为您提供帮助。您可以查看 extenders 来管理自动自动舍入的货币价值。
    【解决方案2】:

    您也可以通过使用普通函数而不是计算的 observable 来做到这一点。

    见this fiddle

    JS:

    var transactions = [{
        Amount: -100
    }, {
        Amount: 125
    }, {
        Amount: 10
    }, {
        Amount: 25
    }, {
        Amount: -125
    }, {
        Amount: 400
    }];
    
    var ViewModel = function () {
        var self = this;
        self.Transactions = ko.observableArray(ko.mapping.fromJS(transactions)());
        self.OpeningBalance = ko.observable(1000);
        self.runningBalance = self.OpeningBalance();
    
        self.RunningBalance = function (index) {
            return ko.computed(function () {
                var total = parseInt(self.OpeningBalance());
                for (i = 0; i <= index; i++) {
                    total += parseInt(self.Transactions()[i].Amount());
                }
                return total;
            });
        };
        self.ClosingBalance = ko.computed(function () {
            var total = parseInt(self.OpeningBalance());
            ko.utils.arrayForEach(self.Transactions(), function (item) {
                total += parseInt(item.Amount());
            });
            return total
        });
    
    }
    
    var model = new ViewModel();
    
    ko.applyBindings(model);
    

    HTML

    Opening balance
    <input data-bind="value: OpeningBalance" />
    <table>
        <thead>
            <tr>
                <th width="150"></th>
                <th width="150">Money Out</th>
                <th width="150">Money In</th>
                <th>Balance</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td colspan="3"><strong>Opening Balance</strong>
    
                </td>
                <th>    <span data-bind="text: OpeningBalance"></span>
    
                </th>
            </tr>
            <!-- ko foreach: Transactions -->
            <tr>
                <td></td>
                <!-- ko if: Amount() < 0 -->
                <td data-bind="text: Amount"></td>
                <td></td>
                <!-- /ko -->
                <!-- ko if: Amount()> 0 -->
                <td></td>
                <td data-bind="text: Amount"></td>
                <!-- /ko -->
                <td>    <span data-bind="text: $root.RunningBalance($index())"></span>
    
                </td>
            </tr>
            <!-- /ko -->
            <tr>
                <td colspan="3"><strong>Closing Balance</strong>
    
                </td>
                <th>    <span data-bind="text: ClosingBalance"></span>
    
                </th>
            </tr>
        </tbody>
    </table>
    

    【讨论】:

    • 是的,我最初就是这样做的,直到我需要更改期初余额,这反过来又会更改交易的运行余额。
    猜你喜欢
    • 2013-07-03
    • 2018-09-22
    • 2017-06-20
    • 2020-01-20
    • 2017-12-01
    • 1970-01-01
    • 2018-07-12
    • 2015-01-06
    • 1970-01-01
    相关资源
    最近更新 更多