为什么需要在函数体中包装函数调用

6

我经常在JavaScript中看到以下类似的代码:

$("#sendButton").click(function() {
    sendForm();
}

为什么需要将对sendForm()的调用封装在一个函数中?我认为这样做会更易读且少打字。
$("#sendButton").click(sendForm);

每种方法都有优缺点,谢谢!

2
这并不是必要的,可能只是出于习惯而已。 - Musa
2
请记住,您的两个版本中this的值将不同(除非sendForm是一个绑定函数),尽管第一个版本中的this并不是很有用。 - the system
6个回答

7

通常有两种情况需要使用前者而不是后者:

  1. 如果你需要在调用函数之前对参数进行任何后处理。

  2. 如果你正在调用对象的方法,则如果使用第二种形式,作用域(this引用)将会不同。

例如:

MyClass = function(){
    this.baz = 1;    
};

MyClass.prototype.handle = function(){
    console.log(this.baz);    
};

var o = new MyClass();

$('#foo').click(o.handle);
$('#foo').click(function(){
    o.handle();
});

控制台输出:

undefined
1

2
可能已经有太多答案了,但两者之间的区别在于this的值,即进入sendForm的范围。 (参数也不同。)让我解释一下。
根据JavaScript规范,像这样调用函数:sendForm();会使用没有上下文对象的函数。这是JavaScript赋予的。
然而,当您将函数作为参数传递时,例如:$(...).click(sendForm),您只需传递对该函数的引用以供稍后调用。您现在还没有调用该函数,只是像对象引用一样传递它。只有在跟随它们的()(除了稍后讨论的callapply)时,才会调用函数。无论如何,如果某人最终调用此函数,则该人可以选择要使用的范围来调用该函数。
在我们的情况下,那个人就是jQuery。当您将函数传递给$(...).click()时,jQuery将稍后调用该函数并将作用域(this)设置为单击事件的HTML元素目标。您可以尝试一下:$(...).click(function() { alert(this); });,将得到表示HTML元素的字符串。
因此,如果您向jQuery提供一个匿名函数的引用,该函数说sendForm(),则jQuery将在调用该函数时设置作用域,并且该函数将调用没有范围的sendForm。实质上,它将清除this。试一试:$(...).click(function() { (function() { alert(this); })(); });。在这里,我们有一个匿名函数调用另一个匿名函数。我们需要在内部匿名函数周围加上括号,以便()应用于该函数。
如果您向jQuery提供名为sendForm的命名函数的引用,则jQuery将直接调用此函数并给出其承诺始终给出的作用域。
因此,现在您的问题的答案变得更加明显:如果您需要this指向单击开始工作时的元素目标,请使用.click(sendForm)。否则,两者都同样有效。您可能不需要this,因此跳过匿名函数即可。
对于那些好奇的人,可以使用JavaScript标准的applycall请参见此处以了解两者之间的区别)来强制范围。当使用点运算符时,如:obj.func,作用域也会被分配,这要求JavaScript将一个函数调用中的this指向obj。(因此理论上您可以通过执行以下操作来强制obj成为调用函数时的作用域:obj.foo =(对函数的引用); obj.foo(); delete obj.foo; 但这是一种相当丑陋的使用方式。)
函数apply由jQuery用于具有范围的调用单击处理程序,还可以在函数调用中强制参数,并且实际上jQuery确实将参数传递给其单击处理程序。因此,两种情况之间还存在另一个区别:当您从匿名函数中调用sendForm并不传递参数时,不仅作用域而且参数也会丢失。

1

在这里,您正在定义一个匿名事件处理程序,它可以调用多个内联函数。这很不规范且难以调试,但人们这样做是因为他们懒惰并且能够这样做。

它也可以像您第二个示例(我如何定义事件处理程序)一样工作:

$("#sendButton").click(sendForm)

通过在内联定义事件处理程序,您可以将事件数据传递给多个函数,并且可以获得事件对象的 this 作用域:

$("#sendButton").click(function(event) {
    sendForm();
    doSomethingElse(event);
    andAnotherThing(event);
    // say #sendButton is an image or has some data attributes
    var myButtonSrc = $(this).attr("src");
    var myData = $(this).data("someData");
});

1

如果你只是调用 sendForm 函数,那么最终两个示例之间并没有太大的区别。

$("#sendButton").click(function(event) {
    if(event.someProperty) { /* ... */ }
    else { sendForm({data: event.target, important: 'yes'}); }
}

然而,在上述情况下,我们可以处理从click()传递给回调函数的参数,但是如果sendForm函数已经可以处理此功能,那么如果你确实只是做这件事,那么没有理由为什么不将sendForm作为回调参数。

function sendForm(event) {
    // Do something meaningful here.
}

$("#sendButton").click(sendForm);

请注意,在程序中处理不同层次的逻辑是由您决定的;您可以将某些通用功能封装在一个sendForm函数中,然后有一个sendFormCallback,您将其传递给这些类型的函数,它们会处理事件/回调处理的中间业务,然后调用sendForm本身。
如果您正在使用回调密集型环境,则明智的做法是将重要功能与回调触发器本身分开,以避免callback hell,并促进源代码的可维护性和可读性。

1

这只是为了锁定作用域。当您将sendForm()包装在闭合当前作用域的匿名函数中时,换句话说,this将与其一起保留。如果您只传递sendForm,则对this的任何调用都将来自调用作用域。
这是一个学习JavaScript作用域和质疑惯例的好问题。


0

不,第二个例子是完全有效的。

99.9%的jQuery示例使用第一种符号表示法,但这并不意味着您需要忘记基本的JavaScript语法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接