TypeScript和Knockout绑定到'this'问题 - 需要lambda函数吗?

时间:2012-11-07 20:20:34

标签: lambda knockout.js typescript

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

电子邮件列表是名为电子邮件的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语法但我找不到正确的写法(那里有几个例子)。

或者我可以采取不同的方法吗?

8 个答案:

答案 0 :(得分:48)

通过在类构造函数

中声明方法体,可以正确关闭'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 ver 0.9.1开始,你可以通过使用lambda字段初始化器来实现相同的结果:

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

答案 1 :(得分:24)

手套人!只需将$ parent绑定为:

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

答案 2 :(得分:6)

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 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;
    };
}

答案 3 :(得分: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时,每个对象只创建一个小代理函数。
  • 通过不在构造函数上放置函数来更好地组织类代码。
  • 任何函数都可以用作回调函数,从事件调用函数时无需更改代码。
  • 您没有两次绑定功​​能的风险。
  • 最好只绑定一次函数,而不是每次执行click / event绑定时都在视图中执行。

PS:
您仍然需要绑定polyfill 我正在使用typesript 0.9.5

答案 4 :(得分:2)

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

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

答案 5 :(得分:1)

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

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

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

答案 6 :(得分:1)

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

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

this分配给that,如下所示

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

答案 7 :(得分:0)

虽然我更喜欢马库斯的解决方案,但这是我以前用来解决这个问题的方法:

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);