打字稿jQuery的回调调用时“这个”范围界定问题打字稿jQuery的回调调用时“这个”范围界定问题

2019-05-08 22:38发布

我不知道最好的方法来处理“这个”在打字稿的作用域。

下面是我在转换到打字稿代码的通用模式的一个例子:

class DemonstrateScopingProblems {
    private status = "blah";
    public run() {
        alert(this.status);
    }
}

var thisTest = new DemonstrateScopingProblems();
// works as expected, displays "blah":
thisTest.run(); 
// doesn't work; this is scoped to be the document so this.status is undefined:
$(document).ready(thisTest.run); 

现在,我可以改变调用...

$(document).ready(thisTest.run.bind(thisTest));

......这确实工作。 但它有点可怕。 这意味着代码可以编译所有在某些情况下正常工作,但如果我们忘记范围绑定会破。

我想办法在类内做到这一点,所以在使用该类时,我们并不需要担心什么“本”的作用范围。

有什么建议?

更新

正在使用的脂肪箭头作品的另一种方法:

class DemonstrateScopingProblems {
    private status = "blah";

    public run = () => {
        alert(this.status);
    }
}

那是一种有效的方法?

Answer 1:

你有几个选择这里,每一个都有自己的权衡。 不幸的是没有明显的最佳解决方案,这将真正取决于应用。

自动绑定类
至于你的问题所示:

class DemonstrateScopingProblems {
    private status = "blah";

    public run = () => {
        alert(this.status);
    }
}
  • 好/坏:这将创建每类的实例方法,每一个附加的封闭。 如果此方法通常只在普通的方法调用中使用,这是矫枉过正。 但是,如果它使用了很多的回调位置,它的效率更高的类实例来捕捉this背景下,而不是每个调用点创建时调用一个新的闭包。
  • 好:不可能的外部呼叫者忘记处理this背景下
  • 好:类型安全的打字稿
  • 好:没有额外的工作,如果函数参数
  • 错误:派生类无法拨叫用这种方式基类的方法super.
  • 坏:方法是“预绑定”和它的精确语义不创建类和消费者之间的额外的非类型安全的合同。

Function.bind
此外,如下所示:

$(document).ready(thisTest.run.bind(thisTest));
  • 好/坏:对面内存/性能权衡比较,第一种方法
  • 好:没有额外的工作,如果函数参数
  • △:打字稿,这个目前还没有类型安全
  • 坏:只有在ECMAScript中5可用的,如果这对你很重要
  • 缺点:你必须键入实例名称两次

脂肪箭头
在打字稿(这里示出具有用于说明原因,某些伪参数):

$(document).ready((n, m) => thisTest.run(n, m));
  • 好/坏:对面内存/性能权衡比较,第一种方法
  • 好:在打字稿,这个有100%的类型安全
  • 好:工作中的ECMAScript 3
  • 好:你只需要输入一次实例名称
  • 缺点:你必须输入参数两次
  • 缺点:不带可变参数的参数工作


Answer 2:

需要一些初始设置,但与它的光无敌,字面一个字的语法回报另一种解决方案是使用方法装修通过吸气到JIT绑定的方法。

我创建了一个GitHub上的回购 ,以展示这个想法的实现(这是一个有点冗长,以适应一个答案,其代码40行,包括注释),你会简单地用如下:

class DemonstrateScopingProblems {
    private status = "blah";

    @bound public run() {
        alert(this.status);
    }
}

我还没有看到这在任何地方提到的是,但它完美的作品。 此外,也没有明显的缺点,以这种方法:这个装饰的实现- 包括一些类型检查的运行时类型安全 -是平凡和简单,并配备了最初的方法调用之后基本上是零开销。

主要部分被限定在类的原型,它是第一个呼叫之前立即执行以下吸气剂:

get: function () {
    // Create bound override on object instance. This will hide the original method on the prototype, and instead yield a bound version from the
    // instance itself. The original method will no longer be accessible. Inside a getter, 'this' will refer to the instance.
    var instance = this;

    Object.defineProperty(instance, propKey.toString(), {
        value: function () {
            // This is effectively a lightweight bind() that skips many (here unnecessary) checks found in native implementations.
            return originalMethod.apply(instance, arguments);
        }
    });

    // The first invocation (per instance) will return the bound method from here. Subsequent calls will never reach this point, due to the way
    // JavaScript runtimes look up properties on objects; the bound method, defined on the instance, will effectively hide it.
    return instance[propKey];
}

完整的源


这个想法也可采取一步,通过在一类装饰这样做代替,迭代的方法和限定一个通为他们每个人的上述属性描述符。



Answer 3:

Necromancing。
有不通过吸气要求箭头函数(箭头函数是慢30%),或JIT的方法一个明显简单的解决方案。
该解决方案是绑定在构造函数中的这上下文。

class DemonstrateScopingProblems 
{
    constructor()
    {
        this.run = this.run.bind(this);
    }


    private status = "blah";
    public run() {
        alert(this.status);
    }
}

您可以使用此方法来自动绑定所有功能在构造方法的类:

class DemonstrateScopingProblems 
{

    constructor()
    { 
        this.autoBind(this);
    }
    [...]
}


export function autoBind(self: any)
{
    for (const key of Object.getOwnPropertyNames(self.constructor.prototype))
    {
        const val = self[key];

        if (key !== 'constructor' && typeof val === 'function')
        {
            // console.log(key);
            self[key] = val.bind(self);
        } // End if (key !== 'constructor' && typeof val === 'function') 

    } // Next key 

    return self;
} // End Function autoBind


Answer 4:

在你的代码,你有没有尝试过将最后一行如下?

$(document).ready(() => thisTest.run());


文章来源: TypeScript “this” scoping issue when called in jquery callback