Kyle Simpson从你不知道JS:Async&性能

时间:2015-05-21 20:16:03

标签: javascript

如果有人可以详细解释这个功能的作用? 这部分是做什么的:fn = orig_fn.bind.apply(orig_fn,

感谢。

function asyncify(fn) {
        var orig_fn = fn,
            intv = setTimeout( function(){
                intv = null;
                if (fn) fn();
            }, 0 )
        ;

        fn = null;

        return function() {
            // firing too quickly, before `intv` timer has fired to
            // indicate async turn has passed?
            if (intv) {
                fn = orig_fn.bind.apply(
                    orig_fn,
                    // add the wrapper's `this` to the `bind(..)`
                    // call parameters, as well as currying any
                    // passed in parameters
                    [this].concat( [].slice.call( arguments ) )
                );
            }
            // already async
            else {
                // invoke original function
                orig_fn.apply( this, arguments );
            }
        };
    }

1 个答案:

答案 0 :(得分:4)

代码似乎是一种非常复杂的说法:

function asyncify(cb) {
    return function() {
        setTimeout(function() {
            cb.apply(this, arguments);
        }, 0);
    }
}

但是我应该强调'似乎'。也许我在上面的来回中错过了一些重要的细微差别。

对于bind.apply来说,解释起来有点棘手。两者都是每个函数的方法,允许您使用指定的上下文(this)调用它,在apply的情况下,它接受作为数组的参数。

当我们申请" bind绑定自身是正在应用的函数 - 不是应用的对象,可能是任何东西。因此,如果我们像这样重写它,可能更容易开始理解这一行:

Function.prototype.bind.apply(...)

Bind有这样的签名:.bind(context, arg1, arg2...)

参数是可选的 - 通常它们用于currying,这是bind的主要用例之一。在这种情况下,作者希望将原始函数绑定到(1)当前this上下文,(2)" asyncified"函数被调用。因为我们事先不知道需要传递多少个参数,所以我们必须使用apply,其中参数可以是数组或实际的arguments对象。这是对本节的非常详细的重写,可能有助于阐明发生的事情:

var contextOfApply = orig_fn;
var contextWithWhichToCallOriginalFn = this;

var argumentArray = Array.prototype.slice.call(arguments);

argumentArray.unshift(contextWithWhichToCallOriginalFn);

// Now argument array looks like [ this, arg1, arg2... ]
// The 'this' is the context argument for bind, and therefore the
// context with which the function will end up being called.

fn = Function.prototype.bind.apply(contextOfApply, argumentArray);

<强>实际上...

我可以解释一下我提供的简单版本有何不同。在再次回顾它时,我发现了那个缺失的细微差别导致其作者在顶部经历了那种奇怪的舞蹈。它实际上不是制作另一个功能的功能,而是始终异步&#34;。这是一个只能确保它是异步一次的函数 - 它可以防止在创建回调的同一时刻执行回调,但此后它会同步执行。

我仍然可以用更友好的方式写这个,我想:

function asyncify(cb) {
    var inInitialTick = true;

    setTimeout(function() { inInitialTick = false; }, 0);

    return function() {
        var self = this;
        var args = arguments;

        if (inInitialTick)
            setTimeout(function() { cb.apply(self, args); }, 0);
        else
            cb.apply(self, args);
    }
}

现在我应该注意到上述内容实际上并没有按照它所说的去做。事实上,使用超时和同步执行函数的次数使用此版本或原始版本是随机的。那是因为setTimeout是setImmediate的一个蹩脚(但有时候很好)的替代品,这显然是这个函数真正需要的东西(但如果它需要在Moz和Chrome中运行,可能也没有)。 / p>

这是因为传递给setTimeout的毫秒值是一个&#34;软目标&#34;。它实际上不会为零;事实上,如果我记得正确,它总是至少4毫秒,这意味着可以通过任何数量的刻度。

想象一下,你处在一个神奇的仙境里,ES6的东西在起作用,并且对于是否实现像setImmediate这样基本的实用程序没有任何奇怪的想法,它可以像这样重写,然后就可以预测了行为,因为与setTimeout不同,setImmediate确实确保在下一个tick上执行而不是稍后执行:

const asyncify = cb => {
    var inInitialTick = true;

    setImmediate(() => inInitialTick = false);

    return function() {
        if (inInitialTick)
            setImmediate(() => cb.apply(this, arguments));
        else
            cb.apply(this, arguments);
    }
};

实际上......

还有一个区别。在原文中,如果在&#34;当前刻度期间调用,实际上是任意数量的连续刻度&#34;它仍将只执行一个初始时间,最后一组参数。这实际上闻起来有点像它可能是非预期的行为,但没有上下文我只是在猜测;这可能正是预期的。这是因为在第一次超时完成之前的每次调用中,都会覆盖fn。这种行为有时被称为限制,但在这种情况下,不像&#34; normal&#34;限制,它只会在创建后大约4ms的时间内发生一段未知的时间,之后将无节制和同步。祝所有调试Zalgo的人都好运了:)