我研究过JavaScript代理模式,但仍然不了解,我可以从中受益。因此,我想为您提供两个示例,并请您指出它们之间的区别。
请看下面的代码:
这两个addEventListener
调用之间有什么区别?其中之一以常规方式调用handleDrop
。另一个使用代理模式。
使用代理模式方法将获得什么?
我测试了两个函数,它们都成功调用了handleDrop
。
DndUpload.prototype.buildDropZone = function ()
{
var self = this,
this.dropZone.addEventListener('drop', function (e) { self.handleDrop.call(self, e) }, false);
this.dropZone.addEventListener('drop', self.handleDrop, false);
DndUpload.prototype.handleDrop = function (e)
{
alert("test");
...
};
}
您可以为我提供很好的参考,其中包含JavaScript中代理模式的非常清晰的说明。
提前致谢。
最佳答案
因此,您在示例中所描述的不仅仅是对代理模式的演示,而不仅仅是对“调用对象”及其在JavaScript中的工作方式的混淆。
在JavaScript中,函数是“一流的”。从本质上讲,这意味着功能与其他任何数据一样都是数据。因此,让我们考虑以下情况:
var fn = (function () { return this.x; }),
a = {
x : 1,
fn : fn,
},
x = 2,
nothing = (function (z) { return z; });
因此,我们有一个对象
a
,它具有两个属性:fn
和x
。我们还具有变量x
,fn
(这是返回this.x
的函数)和nothing
(其返回所传递的内容)。如果评估
a.x
,则得到1
。如果评估x
,则得到2
。很简单,是吗?现在,如果我们评估nothing(a.x)
,则得到1
。这也很简单。但是,重要的是要认识到与属性1
关联的值a.x
并没有以任何方式连接到对象a
。它独立存在,可以简单地作为值传递。在JavaScript中,函数的工作方式相同。可以将作为属性的函数(通常称为“方法”)作为简单引用进行传递。但是,这样做会使它们与对象断开连接。当您在函数内部使用
this
关键字时,这一点很重要。this
关键字引用“调用对象”。这是在评估函数时与该函数关联的对象。设置函数调用对象的三种基本方法:如果使用点运算符(例如
a.fn()
)调用该函数,则将相关对象(在示例中为a
)设置为调用对象。如果使用函数的
call
或apply
属性调用该函数,则可以显式设置调用对象(我们将在稍后说明为什么有用)。如果没有通过方法1或方法2设置任何调用对象,则使用全局对象(在浏览器中,通常称为
window
)。因此,回到我们的代码。如果我们调用
a.fn()
,它将被评估为1
。这是可以预期的,因为由于使用点运算符,函数中的this
关键字将设置为a
。但是,如果我们简单地调用fn()
,它将返回2
,因为它引用了全局对象的x
属性(这意味着使用了我们的全局x
)。现在,事情变得棘手了。如果您呼叫:
nothing(a.fn)()
怎么办?您可能会对结果为2
感到惊讶。这是因为将a.fn
传递到nothing()
会传递对fn
的引用,但不会保留调用对象!这与您的编码示例中的概念相同。如果您的函数
handleDrop
使用this
关键字,您会发现它具有不同的值,具体取决于您使用的处理程序形式。这是因为在第二个示例中,您正在传递对handleDrop
的引用,但是与我们的nothing(a.fn)()
示例一样,到调用它的时间,调用对象引用也丢失了。因此,让我们为难题添加其他内容:
var b = {
x : 3
};
您会注意到,虽然
b
具有x
属性(因此满足fn
使用this
的要求),但它没有引用fn
的属性。因此,如果要在fn
设置为this
的情况下调用b
函数,似乎我们需要向b
添加新属性。但是相反,我们可以在apply
上使用前面提到的fn
方法将b
显式设置为调用对象:fn.apply(b); //is 3
通过创建新的函数“包装器”,可以将其用于“永久地”将调用对象绑定到函数。它并不是真正的永久绑定,它只是创建一个新函数,并使用所需的调用对象来调用旧函数。这样的工具通常是这样写的:
Function.prototype.bind = function (obj) {
var self = this;
return function() {
return self.apply(obj, arguments);
};
};
因此,在执行该代码之后,我们可以执行以下操作:
nothing(a.fn.bind(a))(); //is 1.
没什么棘手的。实际上,
bind()
属性内置于ES5中,其工作原理与上面的简单代码非常相似。实际上,我们的bind
代码是一种非常复杂的方法,可以做一些我们可以更简单地完成的事情。由于a
具有fn
作为属性,因此我们可以使用点运算符直接调用它。我们可以跳过call
和apply
的所有混淆用法。我们只需要确保函数何时被调用,就可以使用点运算符来调用它。我们可以在上面看到如何做,但是在实践中,它要简单得多且直观得多:nothing(function () { return a.fn(); })(); //is 1
一旦了解了如何将数据引用存储在闭包范围中,函数如何是一类对象以及调用对象的工作方式,所有这些都将变得非常易于理解和直观。
至于“代理”,它们也利用相同的概念来连接功能。因此,假设您要计算
a.fn
的调用次数。您可以通过插入代理来做到这一点,就像这样(利用上面的bind
代码中的一些概念):var numCalls = (function () {
var calls = 0, target = a.fn;
a.fn = (function () {
calls++;
return target.apply(a, arguments);
});
return (function () {
return calls;
});
}());
因此,现在,无论何时调用
numCalls()
,它都会返回调用a.fn()
的次数,而无需实际修改a.fn
的功能!这很酷。但是,必须记住,您确实更改了a.fn
所引用的功能,因此回头看我们代码的开头,您会发现a.fn
与fn
不再相同,并且可以不再可以互换使用。但是原因现在应该很明显了!我知道这基本上是一个JavaScript培训的一周,只有几页文字,但这非常简单。一旦了解了这些概念,许多JavaScript模式的功能,实用性和功能就会变得非常容易理解。
希望事情变得更清楚!
更新:感谢@pimvdb指出了我不必要的
[].slice.call(arguments, 0)
使用。我删除了它,因为它是不必要的。
关于javascript - JavaScript代理模式介绍,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12412438/