【问题标题】:TypeScript and Knockout binding to 'this' issue - lambda function needed?TypeScript 和 Knockout 绑定到“this”问题 - 需要 lambda 函数?
【发布时间】:2012-10-27 23:17:27
【问题描述】:

我一直在使用 TypeScript 和 KnockoutJS 创建一个 htmlHelper 函数来编辑电子邮件列表。

电子邮件列表是一个名为 emails 的 Knockout ObservableArray,我对每个项目都有一个链接来删除它们。这是 HTML 片段:

<ul data-bind="foreach: emails" >
    <li>
        <a href="#" data-bind="click: $parent.deleteItem">Delete</a>
        &nbsp;<span data-bind="text: $data"></span>
    </li>
</ul>

删除链接绑定到$parent.deleteItem这是viewmodel中的一个方法:

// remove item
public deleteItem(emailToDelete: string) {
    // remove item from list
    this.emails.remove(emailToDelete);
}

在执行 deleteItem 方法之前,这一切都有效。调用此方法时的“this”是数组中的项,而不是视图模型。因此 this.emails 是一个空引用并且失败。

我知道 TypeScript 支持 Lambda 语法,但我找不到正确的编写方法(那里的例子很少)。

或者我可以采取其他方法吗?

【问题讨论】:

  • 我没有使用过 TypeScript,但是在 Javascript 中通常的解决方案是将视图模型实例引用保存到一个变量,例如var self = this,然后用它来引用emails 对象,例如self.emails.remove(emailToDelete)。也许你知道 ts 语法可以做到这一点。

标签: lambda knockout.js typescript


【解决方案1】:
declare class Email { }
declare class ObservableArray {
    remove(any): void;
}

class MyViewModel {
    public emails : ObservableArray;

    constructor() {
        Rebind(this);
    }

    public deleteItem(emailToDelete: Email) {
        this.emails.remove(emailToDelete);
    }
}

function Rebind(obj : any)
{
    var prototype = <Object>obj.constructor.prototype;
    for (var name in prototype) {
        if (!obj.hasOwnProperty(name)
                && typeof prototype[name] === "function") {
            var method = <Function>prototype[name];
            obj[name] = method.bind(obj);
        }
    }
}

你可能想要一个用于 Function.bind() 的 polyfill:

// Polyfill for Function.bind(). Slightly modified version of
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
if (typeof Function.prototype.bind !== "function") {
    Function.prototype.bind = function(oThis) {
        if (typeof this !== "function") {
            // closest thing possible to the ECMAScript 5 internal IsCallable function
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var aArgs = <any[]> Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function() {},
            fBound = function() {
                return fToBind.apply(this instanceof fNOP && oThis ? this: oThis, aArgs.concat());
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();

        return fBound;
    };
}

【讨论】:

  • 哇..谢谢。相当复杂的解决方案!我认为这是您用来解决此问题的通用方法?
  • 当构造函数被调用时,它将所有方法绑定到实例。
  • 你可能需要一个 polyfill 来绑定(对于 IE8)
【解决方案2】:

您可以通过在类构造函数中声明方法体来获得正确的“this”闭包

class VM {
    public deleteItem: (emailToDelete: string) => void;

    constructor() {
        this.deleteItem = (emailToDelete: string) => {
            // 'this' will be pointing to 'this' from constructor
            // no matter from where this method will be called
            this.emails.remove(emailToDelete);
        }
    }        
}

更新:

似乎从 Typescript 0.9.1 版开始,您可以通过使用 lambda 字段初始值设定项来获得相同的结果:

class VM {
    public deleteItem = (emailToDelete: string) => {
        this.emails.remove(emailToDelete);
    }        
}

【讨论】:

  • 感谢:只有这个方法需要这个修复,因为所有其他方法似乎都可以工作。
  • 还要注意你必须写this.deleteItem = (emailToDelete: string) =&gt; {...}。你不能使用this.deleteItem = function(emailToDelete: string) {...}语法作为this这里是指内部函数的上下文。
  • 请注意,这不会创建“prototype.function”。从性能的角度来看,这不是很好
  • @Richard 只有在 OP 将拥有数百个该对象的实例时才会真正成为问题,可能永远不会超过一个视图模型,因此不应该担心。永远不要担心性能,直到你不得不担心性能:)
  • 这个对我来说很好
【解决方案3】:

虽然我更喜欢 Markus 的解决方案,但这是我之前用来解决此问题的方法:

public fixThis(_this, func) {
    return function () {
        return _this[func].apply(_this, arguments);
    };
}

<a href="#" data-bind="click: fixThis($parent, 'deleteItem')">Delete</a>

请注意,可以通过在方法名称之后添加其他参数来将它们传递给方法:

fixThis($parent, 'deleteItem', arg1, arg2);

【讨论】:

    【解决方案4】:

    给人们戴手套!只需像这样绑定 $parent:

    <a href="#" data-bind="click: $parent.deleteItem.bind($parent)">Delete</a>
    

    【讨论】:

      【解决方案5】:

      我受到bind 答案的启发并想出了这个,我认为它更容易阅读。

      &lt;a href="#" data-bind="click: function () {$parent.deleteItem()}"&gt;Delete&lt;/a&gt;

      将方法包装在 lambda/匿名函数中。不要忘记 ()。

      【讨论】:

        【解决方案6】:

        我的最终解决方案是一个基类,它在构造函数上将所有原型函数重新绑定到自身。很像 Markus Jarderot 的解决方案。

        class BaseClass {
            constructor() {
                for (var i in this) {
                    if (!this.hasOwnProperty(i) && typeof (this[i]) === 'function' && i != 'constructor') {
                        this[i] = this[i].bind(this);
                    }
                }
            }
        }
        

        优点:

        • 所有子类都被强制调用超级构造函数,这是我想要的行为。
        • 执行重新绑定代码时,对象中只有原型函数(变量稍后添加)。
        • 它避免了在每个对象上创建大函数。调用 bind 时,每个对象只会创建一个小的代理函数。
        • 通过不在构造函数上放置函数来更好地组织类代码。
        • 任何函数都可以用作回调,当从事件调用函数时,您无需更改代码。
        • 您没有绑定函数两次的风险。
        • 最好只绑定一次函数,而不是每次执行点击/事件绑定时在视图中进行。

        PS:
        您仍然需要绑定 polyfill。
        我正在使用打字机 0.9.5

        【讨论】:

        • 优秀的解决方案。感谢分享!
        【解决方案7】:

        为了增加我的 2 美分,还有一种肮脏的方式,它利用 Typescript 编译器创建的变量 _this 来保持对此的引用:

        public deleteItem(emailToDelete: string) {
            var that = eval('_this');
            // remove item from list
            that.emails.remove(emailToDelete); // remove? in JS,  really? 
        }
        

        【讨论】:

          【解决方案8】:

          使用类似这样的数据绑定:

          data-bind="click:$parent.deleteItem.bind($parent)"
          

          如下所示将this分配给that

          public deleteItem(itemToDelete) 
          {
              var that = this;
              // remove item from list
              that.emails.remove(itemToDelete); 
          }
          

          【讨论】:

            猜你喜欢
            • 2015-02-06
            • 2015-05-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-11-18
            • 1970-01-01
            • 2018-01-03
            • 1970-01-01
            相关资源
            最近更新 更多