是否可以简化多个闭包以将变量添加到函数的范围?

时间:2015-07-06 20:38:50

标签: javascript

我正在尝试将两个变量传递给filereader回调。基本上我是循环文件并显示它们的缩略图;要做到这一点,我需要两个变量(在这种情况下,parentcanvas)。下面的代码有效,但我想确定我未来的自我不会恨我现在的自我。

我假设将两个变量传递到回调函数的范围而不覆盖this是一种相当常见的情况。

此代码使用名称空间(parent),可以避免,但我仍然想知道如何将两个变量传递给回调函数。

reader.onload = (function (parent) { // even funkier closure to access the parent, the canvas, and the file
    return function(canvas) { // funky closure to be able access the canvas and file
        return function(r) {
            canvas.sourceImage.src = r.target.result;
            parent.functions.render(canvas);
            parent.functions.preview(canvas, canvas.sourceImage.width/10, canvas.sourceImage.height/10);
        };
    }(parent.canvases[j]);
})(parent);

reader.readAsDataURL(file);

每条评论,这是完整的代码。我并不是特别想要代码审查(there's a stack for that!),但我们非常感谢您的反馈。

(function () {
    this.readFiles = function (parent) {
        var files = parent.fileInput.files;
        var j = parent.canvases.length; // so we don't overwrite old previews
        for (var  i = 0; i < files.length; i++, j++) {
            var file = files[i];
            var reader = new FileReader;

            var canvas = document.createElement('canvas');
            canvas.id = "previewCanvas" + j;
            canvas.className = parent.canvasClasses;
            canvas.width = 100;
            canvas.height = 100;
            parent.previewWrapper.appendChild(canvas);

            /* Initialize Canvases for each file */
            parent.canvases[j] = this.create(document.getElementById('previewCanvas' + j));

            reader.onload = (function (parent) { // even funkier closure to access the parent, the canvas, and the file
                return function(canvas) { // funky closure to be able access the file
                    return function(r) {
                        canvas.sourceImage.src = r.target.result;
                        parent.functions.render(canvas);
                        parent.functions.preview(canvas, canvas.sourceImage.width/10, canvas.sourceImage.height/10);
                    };
                }(parent.canvases[j]);
            })(parent);

            reader.readAsDataURL(file);
        }
    }
}).apply(Canvas.functions);

2 个答案:

答案 0 :(得分:1)

基本上你也可以摆脱大部分的闭包和其他一些代码。

除非我已经介绍过任何错误,否则这应该可以正常工作......并且你注意到的两个错误现在已经解决了,感谢抓住它们! : - )

function forEach( array, callback ) {
    Array.prototype.forEach.call( array, callback );
}

Canvas.functions.readFiles = function( parent ) {
    forEach( parent.fileInput.files, function( file ) {
        var reader = new FileReader;

        var canvas = document.createElement( 'canvas' );
        canvas.id = 'previewCanvas' + parent.canvases.length;
        canvas.className = parent.canvasClasses;
        canvas.width = 100;
        canvas.height = 100;
        parent.previewWrapper.appendChild( canvas );

        var fileCanvas = Canvas.functions.create( canvas );
        parent.canvases.push( fileCanvas );

        reader.onload = function() {
            fileCanvas.sourceImage.src = reader.result;
            parent.functions.render( fileCanvas );
            parent.functions.preview(
                fileCanvas,
                fileCanvas.sourceImage.width / 10,
                fileCanvas.sourceImage.height / 10
            );
        };
        reader.readAsDataURL( file );
    }
};

一些注意事项:

您不需要将Canvas.functions变为this的最外层IIFE。只需直接使用Canvas.functions即可。这也消除了this在回调中出现错误的可能性(在这里不会出现问题,但它有助于保持简单)。

您不需要j变量。 parent.canvases.length始终与j的值相同。

如果使用.forEach()而不是for循环,则会在.forEach()回调中声明的所有局部变量上获得闭包。这真的是你需要的唯一关闭。

正如你所指出的那样,OTOH是一个像阵列一样的&#34;像FileReader这样的对象可能没有.forEach()方法。有两种方法可以解决这个问题。一种是编写一个普通的for循环并在其中调用一个函数,例如

var files = parent.fileInput.files;
for( var i = 0;  i < files.length;  i++ ) {
    doFile( files[i] );
}

function doFile( file ) {
    ...
}

这可以通过内联函数表达来完成,但我喜欢使用单独的命名函数语句来实现这种用法 - 它看起来更清晰一点。 (这里要注意的是不要将函数语句本身放在嵌套块中,如iffor块 - 浏览器不能一致地处理 - 它需要直接在外部函数内部。

或者,使用Array.prototype.forEach.call()让您在类似数组的对象上使用forEach。我通过定义独立的forEach()函数在上面的更新代码中使用了这种方法。您可以直接将Array.prototype.forEach.call()内联,但我认为将它包装在函数中会更清晰。当然,如果您正在使用诸如Underscore或LoDash之类的库,那么您可以使用方便的迭代器,如_.each()

您不需要使用document.getElementById()来获取对当前画布的引用,因为您已在canvas变量中使用它。

您不需要onload()回调中的任何嵌套闭包。该函数已经可以访问必要的变量。

代码似乎使用名称canvas来表示两个截然不同的事情。第一个是您在顶部创建的实际canvas元素,第二个是Canvas.functions.create(canvas)的返回值。我将第二个重命名为fileCanvas以避免混淆。你可以想出一个更好的名字,但不要把它叫做canvas

onload回调中,r.targetreader是同一个对象,因此您可以直接使用它。如果你在这样的事件处理程序中使用了一个参数,我建议你调用它event(如果你想要一个简短的名字,可以e),因为这是更习惯的。

答案 1 :(得分:1)

尽管@MichaelGeary主要通过重构代码回答了我的问题,但标题问题的答案是外部函数立即执行,并且可以访问当前变量:

reader.onload = (function (parent) { 

   var canvas = parent.canvases[j];
   // put more variables ad nauseum here. 
   // They will be passed to the return function without being changed.

   return function(r) {
        canvas.sourceImage.src = r.target.result;
        parent.functions.render(canvas);
        parent.functions.preview(canvas, [...]);
        };
})(parent);

通过这种方式,您可以在回调中使用可能需要更改的计数器和其他变量。