javascript - JavaScript代理模式介绍

标签 javascript web-applications javascript-events proxy-pattern

我研究过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,它具有两个属性:fnx。我们还具有变量xfn(这是返回this.x的函数)和nothing(其返回所传递的内容)。

如果评估a.x,则得到1。如果评估x,则得到2。很简单,是吗?现在,如果我们评估nothing(a.x),则得到1。这也很简单。但是,重要的是要认识到与属性1关联的值a.x并没有以任何方式连接到对象a。它独立存在,可以简单地作为值传递。

在JavaScript中,函数的工作方式相同。可以将作为属性的函数(通常称为“方法”)作为简单引用进行传递。但是,这样做会使它们与对象断开连接。当您在函数内部使用this关键字时,这一点很重要。

this关键字引用“调用对象”。这是在评估函数时与该函数关联的对象。设置函数调用对象的三种基本方法:


如果使用点运算符(例如a.fn())调用该函数,则将相关对象(在示例中为a)设置为调用对象。
如果使用函数的callapply属性调用该函数,则可以显式设置调用对象(我们将在稍后说明为什么有用)。
如果没有通过方法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作为属性,因此我们可以使用点运算符直接调用它。我们可以跳过callapply的所有混淆用法。我们只需要确保函数何时被调用,就可以使用点运算符来调用它。我们可以在上面看到如何做,但是在实践中,它要简单得多且直观得多:

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.fnfn不再相同,并且可以不再可以互换使用。但是原因现在应该很明显了!

我知道这基本上是一个JavaScript培训的一周,只有几页文字,但这非常简单。一旦了解了这些概念,许多JavaScript模式的功能,实用性和功能就会变得非常容易理解。

希望事情变得更清楚!

更新:感谢@pimvdb指出了我不必要的[].slice.call(arguments, 0)使用。我删除了它,因为它是不必要的。

关于javascript - JavaScript代理模式介绍,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12412438/

相关文章:

javascript - 中等缩放实现在 Angular 13 中不起作用

javascript - 制作字典,其键等于所有 ASCII 字符

php - 如何将 php web 应用程序转换为桌面应用程序并保留数据库

javascript - Google Adsense javascript代码阻止了我的其他js脚本

javascript - 关于javascript中事件执行的问题

javascript - 如何在纯JS中获取没有ID且多次存在的元素

php - 如何使用 Highcharts 导出整个页面或 html 内容而不仅仅是图表?

javascript - 简单的 Web 开发概念我找不到关于 +update 的任何信息

ios - 在设备中查看网络应用程序时如何推荐 native 应用程序?

javascript - 当悬停的元素被另一个元素覆盖时,不会触发 mouseout