1

我研究了 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 中代理模式的非常清晰的解释。

4

3 回答 3

10

因此,您在示例中所描述的与其说是对代理模式的演示,不如说是对“调用对象”及其在 JavaScript 中的工作方式的混淆演示。

在 JavaScript 中,函数是“一流的”。这实质上意味着函数是数据,就像任何其他数据一样。因此,让我们考虑以下情况:

var fn = (function () { return this.x; }),
    a = {
        x : 1,
        fn : fn,
    },
    x = 2,
    nothing = (function (z) { return z; });

所以,我们有一个对象a,它有两个属性:fnx。我们还有变量x, fn(它是一个返回函数this.x)和nothing(它返回它传递的任何东西)。

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

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

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

  1. 如果使用点运算符(例如)调用a.fn()函数,则将相关对象(在示例中,a)设置为调用对象。
  2. 如果使用函数callapply属性调用函数,那么您可以显式设置调用对象(我们将在稍后了解为什么这很有用)。
  3. 如果没有通过方法 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函数,似乎我们需要向 . 添加一个新属性。但是我们可以使用上述方法来显式设置为调用对象:thisbbapplyfnb

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代码实际上是一种非常复杂的方法来做一些我们可以做的更简单的事情。由于ahasfn作为属性,我们可以使用点运算符直接调用它。我们可以跳过calland的所有令人困惑的用法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). 我已将其删除,因为它是不必要的。

于 2012-09-13T19:29:13.600 回答
3

基本上,self.handleDrop直接传递在功能上等同于传递以下函数:

function() {
  return self.handleDrop.apply(this, arguments);
}

因为一切都传递给原始函数:

  • this价值_
  • 论据
  • 返回值

考虑到这一点,比较您的函数如下:

function(e) { self.handleDrop.call(self, e) }
function() { return self.handleDrop.apply(this, arguments); }

与您的代理方式的区别是:

  • 它不会通过返回值。
  • 它不会传递所有参数(只有第一个,e
  • 它不会传递this值,而是使用预定义的值:self.

现在,前两项在这里没有什么区别,因为addEventListener它不关心返回值,而且它也只传递一个参数。

但第三项很重要:它this在函数中设置了不同的值。默认情况下,this是您将事件绑定到的元素(它由浏览器设置)。使用代理方式,您可以设置另一个this值。

现在,在您的代码段中,尚不完全清楚为什么每次buildDropZone调用时都要设置原型函数。通常你只定义一次原型函数。但是当你的处理程序handleDrop使用代理方式调用时,this是指DndUpload实例,这与原型函数大体一致。

于 2012-09-13T18:36:12.227 回答
2

考虑下面的代码:

function printThis() {
  console.log(this);
}

var someObject = {
  performTest : function() {
    var self = this;

    someOtherObject.higherOrderFunction(printThis);
    someOtherObject.higherOrderFunction(function(){printThis.call(self)});
  }
}

var someOtherObject = {
  higherOrderFunction : function(f) {
      f();
  }
}

someOtherObject.higherOrderFunction(printThis)返回什么?怎么样someOtherObject.higherOrderFunction(function(){printThis.call(self)})

第一个问题的答案取决于您调用 someObject.performTest() 的对象和方式。如果我只是从全局上下文中调用 someObject.performTest() ,它可能会打印Window.

someObject无论如何,第二个总是会打印实例。

当您想要精确控制函数的执行上下文时,您称之为闭包或“代理模式”会派上用场。

注意:this在 javascript 中的行为与在其他语言中的行为不同(例如在 Java 中)。

于 2012-09-13T18:46:50.703 回答