我经常在 JavaScript 中看到类似下面的内容:
$("#sendButton").click(function() {
sendForm();
}
为什么有必要将调用包装在sendForm()
函数内部?我认为这样做会更具可读性和更少的打字。
$("#sendButton").click(sendForm);
每种方法的优点/缺点是什么?谢谢!
我经常在 JavaScript 中看到类似下面的内容:
$("#sendButton").click(function() {
sendForm();
}
为什么有必要将调用包装在sendForm()
函数内部?我认为这样做会更具可读性和更少的打字。
$("#sendButton").click(sendForm);
每种方法的优点/缺点是什么?谢谢!
通常有两种情况,您希望使用前者而不是后者:
如果您需要在调用函数之前对参数进行任何后处理。
如果你在一个对象上调用一个方法,如果你使用第二种形式,作用域(这个引用)会有所不同
例如:
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
现在可能答案太多了,但两者之间的区别在于 的值this
,即范围,进入sendForm
。(论点也会有所不同。)让我解释一下。
根据 JavaScript 规范,调用这样的函数:sendForm();
调用没有上下文对象的函数。这是给定的 JavaScript。
但是,当您将函数作为参数传递时,例如:$(...).click(sendForm)
,您只需传递对该函数的引用以供以后调用。您还没有调用该函数,而只是像对象引用一样传递它。仅当函数跟随它们时才调用函数( and()
除外,稍后讨论)。在任何情况下,如果最终有人调用此函数,那么有人可以选择调用该函数的范围。call
apply
在我们的例子中,那个人是 jQuery。当您将函数传递给 时$(...).click()
,jQuery 稍后将调用该函数并将范围 (this) 设置为单击事件的 HTML 元素目标。你可以试试:$(...).click(function() { alert(this); });
,会得到一个代表 HTML 元素的字符串。
因此,如果你给 jQuery 一个匿名函数的引用,上面写着sendForm()
,jQuery 将在调用该函数时设置作用域,然后该函数将在sendForm
没有作用域的情况下调用。本质上,它将清除this
. 试试看:$(...).click(function() { (function() { alert(this); })(); });
。在这里,我们有一个匿名函数调用匿名函数。我们需要内部匿名函数周围的括号,以便 () 应用于该函数。
相反,如果你给 jQuery 一个对命名函数的引用sendForm
,jQuery 将直接调用这个函数并给它它承诺总是给的范围。
所以你的问题的答案现在变得更加明显:如果你需要this
在开始工作时指向点击的元素目标,请sendForm
使用.click(sendForm)
. 否则,两者都可以正常工作。你可能不需要this
,所以跳过匿名函数。
对于那些好奇的人,可以通过使用 JavaScript 标准来强制作用域,apply
或者call
(请参阅此处了解两者之间的差异)。使用点运算符时也会分配范围,例如 in: obj.func
,它要求 JavaScript 调用this
指向的函数obj
。(所以理论上你可以obj
在调用函数时强制成为作用域,方法是:obj.foo = (reference to function); obj.foo(); delete obj.foo;
但这是使用 apply 的一种非常丑陋的方式。
jQuery 使用Functionapply
调用具有范围的单击处理程序,也可以在函数调用上强制参数,事实上 jQuery 确实将参数传递给它的单击处理程序。sendForm
因此,这两种情况之间还有另一个区别:当您从匿名函数调用并且不传递任何参数时,参数(不仅是作用域)会丢失。
在这里,您定义了一个可以内联调用多个函数的匿名事件处理程序。调试很脏而且很难,但是人们这样做是因为他们很懒,而且他们可以。
它也可以像您的第二个示例(我如何定义事件处理程序)一样工作:
$("#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");
});
如果您所做的只是调用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
如果您在回调密集的环境中工作,明智的做法是将重要功能与回调触发器本身分开,以避免回调地狱并提高源代码的可维护性和可读性。
这只是锁定范围。当您将其包装sendForm()
在关闭当前范围的匿名函数中时。换句话说,this
遗嘱将被保留。如果您只是通过sendForm
,那么任何调用都this
将来自调用范围。
这是一个学习 javascript 范围和质疑约定的好问题。
不,第二个例子是完全有效的。
99.9% 的 jQuery 示例使用第一种表示法,这并不意味着您需要忘记基本的 JavaScript 语法。