【问题标题】:Binding a function that is to be used inside a Function constructor绑定要在 Function 构造函数中使用的函数
【发布时间】:2014-06-05 16:58:44
【问题描述】:

我打算创建一个在调用回调之前调用的“预处理”函数。换句话说,调用回调应该遵循以下模式:预处理函数 -> 回调。为了“插入”这样一个预处理函数,我可以简单地创建一个闭包,在闭包内重写回调,以便调用预处理函数,然后在重写回调结束时调用原始回调。

var end = function(init) {
    /*
        In here, init is processed.
        Init contains multiple callbacks.
        One callback is chosen to be invoked.
    */
    init.callback();
};

var closure = function(init) {
    var old = init.callback;
    init.callback = function() {
        /*
            Do the preprocessual stuff
        */
        console.log("The preprocessual functionality has now taken place.");
        return old.apply(this, Array.prototype.slice.call(arguments));
    };

    return end.apply(this, Array.prototype.slice.call(arguments));
};

closure({
    /*among other properties*/
    callback: function() {
        console.log("The preprocessual callback must have been invoked when 'end' invokes me.");
    }
});

但是,我有多个回调,而我只有一个预处理函数。每次调用这些回调之前都应该调用相同的预处理函数。为了不必为每个单独的可能回调编写预处理回调,我在闭包中创建了一个循环,将变量 old 分配给下一个回调,然后使用 Function 构造函数重写回调。

一切仍然有效。但是,我不再能够在它最初可以访问的回调函数中使用非全局变量。以下崩溃,声称未定义variable(根据https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function)。

(function() {
    var end = function(init) {
        /*
            In here, init is processed.
            Init contains multiple callbacks.
            One callback is chosen to be invoked.
        */
        init.callback();
    };

    var closure = function(init) {
        var old = init.callback;
        init.callback = new Function(
            "\
                /*\
                    Do the preprocessual stuff\
                */\
                console.log(\"The preprocessual functionality has now taken place.\");\
                return " + old + ".apply(this, Array.prototype.slice.call(arguments));\
            "
        );

        return end.apply(this, Array.prototype.slice.call(arguments));
    };

    var variable = "value";

    closure({
        /*among other properties*/
        callback: function() {
            console.log("The preprocessual callback must have been invoked when 'end' invokes me.");
            console.log(variable);
        }
    });
})();

然后我想,让我们尝试bind 我在回调函数中需要的变量。然后我遇到了一个很奇怪的问题。出于某种原因,将范围/参数绑定到回调函数(与 Function 构造函数无关)会导致奇怪的错误。此类错误的一个小例子:

这行得通

var callback = function() {
    console.log(arguments);
};

callback = new Function(
    "\
    return " + callback + ".apply(this, Array.prototype.slice.call(arguments));\
    "
);

callback(1, 2, 3);

这不起作用

var callback = function() {
    console.log(arguments);
}.bind(this);

callback = new Function(
    "\
    return " + callback + ".apply(this, Array.prototype.slice.call(arguments));\
    "
);

callback(1, 2, 3);

如果我将回调分配给另一个变量(例如 old)并在 Function 构造函数中使用 old 并在 Function 构造函数中使用完全不同的绑定函数也没关系构造函数。任何绑定函数(无论是否使用变量引用)都会给我错误:“SyntaxError: missing ] after element list”。

事实上,即使这样也失败了

callback = new Function(
    "\
    return " + (function() {}.bind(this)) + ".apply(this, Array.prototype.slice.call(arguments));\
    "
);

callback(1, 2, 3);

我不明白为什么会这样。有用的帮助将不胜感激。

根据要求,实际用例:

var
    ajax = function(init) {
        for (var i = 0, callbacks = ["success", "error"]; i < callbacks.length; i++) {
            if (init.hasOwnProperty(callbacks[i] + "Callback")) {
                init[callbacks[i] + "Callback"] = new Function("responseText",
                    "\
                    /*\
                        Preprocessual callback takes place here (among other things, messages from the server are inserted in the document)\ 
                    */\
                    \
                    return " + init[callbacks[i] + "Callback"] + ".apply(this, Array.prototype.slice.call(arguments));\
                    "
                );
            }
        }

        // This is the actual ajax function, which can operate independently of the project (in contrary, the preprocessual callback needs to know about where to insert messages in the document)
        return cregora.ajax.apply(this, Array.prototype.slice.call(arguments));
    }
;

(function() {
    // some scope with variables..

    ajax({
        url: "url",
        callbackThis: this,
        successCallback: function(responseText) {
            console.log("I need some variables available in this scope");
        },
        errorCallback: function() {
            console.log("I need some variables available in this scope");
        }
    });
})();

【问题讨论】:

  • 你能解释一下让你从“普通”函数到使用函数构造函数+函数体作为字符串的步骤吗?
  • @TheShellfishMeme 是的,我在 init 对象中有多个回调,所有这些回调之前都必须有相同的预处理回调。我不打算为它们中的每一个编写 99% 相同的代码,因为唯一的区别是在重写回调结束时要调用的原始回调的名称。不过,我在示例中省略了循环。
  • 这就是我无法理解您的地方。他们都需要在不同的回调上使用相同的包装器。如果不使用new Function,这样的事情绝对是可能的。这就是为什么我对问题的那一部分更好奇。看来,如果我们解决了这个问题,我们就解决了您的问题。因此,也许您还可以链接到使该更改成为必要的特定代码,我也许可以指出如何更改它。
  • @TheShellfishMeme 在帖子底部查看我的编辑

标签: javascript function binding constructor callback


【解决方案1】:

正如我所料,您实际上将问题复杂化了一点。
您可以构建一个返回适当处理程序并自动包装该函数(就像您的预处理器)的高阶函数,而不是使用函数构造函数。

var callbackWrapper = function (callback) {
    // Returns new anonymous function that acts as the handler
    return function responseHandler (responseText) {
        // Do your pre-processing
        console.log(responseText);
        callback.apply(this, Array.prototype.slice.call(arguments));
    };
};

var ajax = function(init) {
    for (var i = 0, callbacks = ["success", "error"]; i < callbacks.length; i++) {
        var callbackName = callbacks[i] + "Callback";
        if (init.hasOwnProperty(callbackName)) {
            var callback = init[callbackName];
            init[callbackName] = callbackWrapper(callback);
        }
    }

    // This is the actual ajax function, which can operate in independent of the project (for example, the preprocessual callback needs to know about where to insert messages in the document)
    return cregora.ajax.apply(this, Array.prototype.slice.call(arguments));
};


(function() {
    // some scope with variables..

    ajax({
        url: "url",
        callbackThis: this,
        successCallback: function(responseText) {
            console.log("I need some variables available in this scope");
        },
        errorCallback: function() {
            console.log("I need some variables available in this scope");
        }
    });
})();

如果您愿意,您甚至可以更改 callbackWrapper 以每次使用完全相同的 preProcessor 函数:

var callbackWrapper = (function createCallbackWrapper () {
    var preProcessor = function (responseText) {
        console.log(responseText);
    };

    return function callbackWrapper (callback) {
        // Returns new anonymous function that acts as the handler
        return function responseHandler (responseText) {
            var args = Array.prototype.slice.call(arguments);
            preProcessor.apply(this, args);
            callback.apply(this, args);
        };
    };
})();

现在绑定原始回调函数完全没有问题了。

关于这个问题的更多解释:

当你使用fn + ".apply(...)"时,JS会将原来的函数变成一个字符串。这就是为什么您将很难访问闭包变量,或者任何不在您的 var closure 函数范围或全局范围内的东西。

在您的情况下它也会失败,因为在函数上调用 .bind 后,它的字符串表示会变成 "function () { [native code] }"。 这当然不是一个有效的函数体,会给你带来很多麻烦。

转换为字符串是实际问题,而且是一个不容易解决的问题。出于这个原因,使用new Function 几乎从来都不是正确的解决方案,一旦你发现自己在使用它,你应该假设你的推理有误。如果你没有,new Function 确实是唯一的解决方案,你就会知道。

【讨论】:

  • 我真的很傻,因为没有看到解决方案是多么基本。感谢您对字符串转换的解释。
猜你喜欢
  • 2021-06-17
  • 1970-01-01
  • 2011-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多