34

我一直在使用 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这是视图模型中的一个方法:

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

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

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

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

4

8 回答 8

49

您可以通过在类构造函数中声明方法主体来获得“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);
    }        
}
于 2012-11-08T08:45:22.527 回答
25

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

<a href="#" data-bind="click: $parent.deleteItem.bind($parent)">Delete</a>
于 2013-05-23T15:57:31.463 回答
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);
        }
    }
}

您可能需要一个 polyfill 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;
    };
}
于 2012-11-08T08:44:57.643 回答
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

于 2013-12-11T01:08:09.633 回答
2

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

public deleteItem(emailToDelete: string) {
    var that = eval('_this');
    // remove item from list
    that.emails.remove(emailToDelete); // remove? in JS,  really? 
}
于 2014-12-02T13:04:25.803 回答
1

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

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

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

于 2013-12-09T13:31:08.073 回答
1

使用这样的数据绑定:

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

赋值thisthat如下图

public deleteItem(itemToDelete) 
{
    var that = this;
    // remove item from list
    that.emails.remove(itemToDelete); 
}
于 2016-02-09T01:49:50.687 回答
0

虽然我更喜欢 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);
于 2012-12-27T22:48:10.610 回答