需要帮助了解此代码中的闭包使用情况

时间:2014-01-30 03:33:56

标签: javascript closures

以下是我编写的用于管理canvas元素上的平板电脑手势的一些代码的简化代码段

首先是一个函数,它接受一个元素和一个回调字典并注册事件,并添加其他功能,如'hold'手势:

function registerStageGestures(stage, callbacks, recieverArg) {
    stage.inhold = false;
    stage.timer = null;

    var touchduration = 1000;
    var reciever = recieverArg || window;

    stage.onLongTouch = function(e) {
        if (stage.timer) clearTimeout(stage.timer);
        stage.inhold = true;
        if (callbacks.touchholdstart) callbacks.touchholdstart.call(reciever, e);
    };

    stage.getContent().addEventListener('touchstart', function(e) {
        e.preventDefault();
        calcTouchEventData(e);
        stage.timer = setTimeout(function() {
            stage.onLongTouch(e);
        }, touchduration);
        if (callbacks.touchstart) callbacks.touchholdstart.call(reciever, e);
    });

    stage.getContent().addEventListener('touchmove', function(e) {
        e.preventDefault(); 
        if (stage.timer) clearTimeout(stage.timer);

        if (stage.inhold) {
            if (callbacks.touchholdmove) callbacks.touchholdmove.call(reciever, e);

        } else {
            if (callbacks.touchmove) callbacks.touchmove.call(reciever, e);
        }
    });

    stage.getContent().addEventListener('touchend', function(e) {
        e.preventDefault();
        if (stage.timer) clearTimeout(stage.timer);
        if (stage.inhold) {
            if (callbacks.touchholdend) callbacks.touchholdend.call(reciever, e);
        } else {
            if (callbacks.touchend) callbacks.touchend.call(reciever, e);
        }
        stage.inhold = false;
    });
}

稍后我在同一页面中的几个元素(由'View'对象表示)上调用registerStageGestures。类似的东西:

function View() {
    var self=this;
    ..

    function InitView() {
       ...

       registerStageGestures(kineticStage, {
        touchstart: function(e) {
            // do something
        },
        touchmove: function(e) {
            // do something
        },
        touchendunction(e) {
            // do something
        },
        touchholdstart: function(e) {
            // do something
        },
        touchholdmove: function(e) {
            // do something
        },
        touchholdend: function(e) {
            // do something
        },
    }, self);

一切正常,但是我对在registerStageGestures的实现中有两件事感到疑惑:

首先,是否有必要让舞台上的inbut,timer和onLongTouch成员?或者如果它们是registerStageGestures中的本地变量,闭包会使一切正常吗?

其次,是否有必要使用'.call(接收器,'语法?)调用回调函数?我这样做是为了确保回调代码将在View的上下文中运行,但我不确定是否需要?

非常感谢任何输入

谢谢!

2 个答案:

答案 0 :(得分:1)

这里有一些简单的答案。

首先,关闭:

Closure基本上表示无论在函数内部 中定义了什么,都可以访问该函数内容的其余部分。
所有这些内容都保证保持活着(在垃圾箱外),直到没有剩下的物品,这些物品在里面被创造出来。

一个简单的测试:

var testClosure = function () {
    var name = "Bob",
        recallName = function () { return name; };

    return { getName : recallName };
};


var test = testClosure();
console.log(test.getName()); // Bob

因此,内部创建的任何内容都可以被创建的任何函数访问(或者在函数[,...]中创建的函数内部创建)。

var closure_2x = function () {
    var name = "Bob",
        innerScope = function () {
            console.log(name);
            return function () {
                console.log("Still " + name);
            }
        };

    return innerScope;
};


var inner_func = closure_2x();

var even_deeper = inner_func(); // "Bob"
even_deeper(); // "Still Bob"

这不仅适用于内部创建的变量/对象/函数,也适用于内部传递的函数参数。

参数无法访问内部工作(除非传递给方法/回调),但内部工作将记住参数。

因此,只要您的函数在与您的值(或子范围)相同的范围内创建,就可以访问。

.call比较棘手。

你知道它的作用(用你传递的对象替换函数内部的this)...

......但为什么以及何时,在这种情况下更难。

var Person = function (name, age) {
    this.age = age;
    this.getAge = function () {
        return this.age;
    };
};

var bob = new Person("Bob", 32);

这看起来很正常 老实说,这可能看起来很像Java或C#,但有一些调整。

bob.getAge();  // 32

也像Java或C#一样工作。

doSomething.then(bob.getAge);

?嗯?

我们现在已经将Bob的方法作为一个函数单独传递给函数。

var doug = { age : 28 };
doug.getAge = bob.getAge;

现在我们已经给doug一个直接使用bob s methid的引用 - 不是副本,而是指向实际方法的指针。

doug.getAge(); // 28

嗯,这很奇怪。

作为回调传递出来的是什么?

var test = bob.getAge;
test(); // undefined

正如你所说,其原因在于背景......

但具体原因是因为JS中的函数内的this没有预编译或存储...
每次调用函数时,this都会动态计算出来。

如果你打电话

obj.method();

this === obj;

如果你打电话

a.b.c.d();

this === a.b.c;

如果你打电话

var test = bob.getAge;
test();

...

this等于window 在“严格模式”中,这不会发生(你很快就会得到错误)。

test.call(bob); //32

恢复平衡!

...大多

仍然有一些捕获。

var outerScope = function () {
    console.log(this.age);
    var inner = function () {
        console.log("Still " + this.age);
    };

    inner();
};

outerScope.call(bob);

// "32"
// "Still undefined"

当你想到它时,这是有道理的...... 我们知道如果一个函数在被调用的时刻计算出this - 范围与它无关......

...我们没有将inner添加到对象...

this.inner = inner;
this.inner();

会工作得很好(但现在你只是搞砸了一个外部对象)......

因此innerthis视为window

解决方案是使用.call,或.apply,或 使用函数范围和/或关闭

var person = this,
    inner = function () { console.log(person.age); };

兔子洞越来越深,但我的手机正在死...

答案 1 :(得分:1)

  

首先,是否需要保留,计时器和onLongTouch成员   舞台?或者闭包会使一切运转良好   registerStageGestures中的本地变量?

registerStageGestures()而言,var inholdvar timerfunction onLongTouch(e) {...}。就够了内部函数自动访问其外部函数成员的机制称为“闭包”。如果其他一些代码需要访问这些设置作为stage.inhold的属性,您只需要设置stage.timerstage.onLongTouchstage

  

其次,是否有必要使用'.call(receiver,')调用回调函数。   句法 ?我这样做是为了确保回调代码将在   视图的上下文,但我不确定是否需要它?

可能,取决于这些回调的编写方式。在内部调用使用.call()的函数时,有时会使用.apply()this。在这两种情况下,传递的第一个参数定义要解释为this的对象。因此,javascript为您提供了定义通用方法的方法,没有先验关于调用这些方法时将应用的对象的假设。类似地,您可以调用对象的方法,使其作用于另一个对象。

修改

为了完整性,请注意即使函数中没有this.apply()也非常有用,因为它允许将多个参数指定为单个数组的元素,例如无处不在jQuery.when.apply(null, arrayOfPromises)...