现在的JavaScript匿名函数中,`return function()`是什么意思?(最佳实践)

16

注意:已更新并重写

这个问题已被重新编辑和更新。请原谅下面的过时引用。

最近我看到了很多 JavaScript 代码,但其中很多让我感觉不正确。在这种情况下,我应该建议什么更好的代码模式?我将重现我见过的代码以及每个代码块的简短描述:

代码块 #1

这段代码永远不应该评估内部函数。程序员会感到困惑,因为代码应该可以运行。

$(document).ready( function() { 
  return function() { 
    /* NOPs */
  }
});

代码块 #2

程序员很可能想要实现一个自执行函数。他们没有完全完成实现(在嵌套括号的末尾缺少())。另外,因为他们在外部函数中没有做任何操作,所以嵌套的自执行函数可以直接内联到外部函数定义中。

事实上,我不确定他们是否打算使用自执行函数,因为代码还是错误的。但显然他们想要一个自执行函数。

$(document).ready( (function() { 
  return function() { 
    /* NOPs */
  }
}));

代码块 #3

程序员似乎再次尝试使用自调用函数,然而在这种情况下这是过度设计了。

$(document).ready( function() { 
  (return function() { 
    /* NOPs */
  })()
}); 

代码块 #4

一个示例代码块

$('#mySelector').click( function(event) { 
  alert( $(this).attr('id') );

  return function() { 
    // before you run it, what's the value here?
    alert( $(this).attr('id') );
  }
});

注解:

我感到沮丧是因为这导致了人们不理解的“creep bugs”,改变了他们无法理解的作用域,并且通常会使代码变得非常奇怪。这一切都来自某些教程吗?如果我们要教人编写代码,能不能教他们正确的方法?

你会建议一个准确的教程来向他们解释为什么他们正在使用的代码是错误的吗?你会建议他们学习哪种模式呢?


所有引起我提出这个问题的示例都是在SO上作为问题发布的。下面是我最近发现的一个具有此行为的片段。您会注意到,我没有发布问题的链接,因为该用户似乎是一个新手。

$(document).ready(function() {
 $('body').click((function(){
  return function()
  {
   if (counter == null) {
    var counter = 1;
   }
   if(counter == 3) {
     $(this).css("background-image","url(3.jpg)");
     $(this).css("background-position","10% 35%");
     var counter = null;
   }
   if(counter == 2) {
     $(this).css("background-image","url(2.jpg)");
     $(this).css("background-position","10% 35%");
     var counter = 3;
   }
   if(counter == 1) {
     $(this).css("background-image","url(1.jpg)");
     $(this).css("background-position","40% 35%");
     var counter = 2;
   }


  }
 })());
});

以下是我提出的重写他们代码的方法:

var counter = 1;
$(document).ready(function() {
    $('body').click(function() {
        if (counter == null) {
            counter = 1;
        }
        if (counter == 3) {
            $(this).css("background-image", "url(3.jpg)");
            $(this).css("background-position", "10% 35%");
            counter = 1;
        }
        if (counter == 2) {
            $(this).css("background-image", "url(2.jpg)");
            $(this).css("background-position", "10% 35%");
            counter = 3;
        }
        if (counter == 1) {
            $(this).css("background-image", "url(1.jpg)");
            $(this).css("background-position", "40% 35%");
            counter = 2;
        }
    });
});
请注意,我并没有说我的代码比其他任何代码更好。我只是去掉了匿名的中间函数。实际上我知道这段代码原本为什么不能实现他们想要的功能,而我也不打算重写每一个人遇到的代码,但我希望至少给这位用户提供可用的代码。
我认为提供一个真正的代码示例会受到赞赏。如果你真的想要这个特定问题的链接,请用这个昵称给我发送邮件。他得到了几个非常好的答案,其中我的答案最多只能算中等水平。

6
我从未见过那样的代码。 - SLaks
4
你的实际问题是什么? - SLaks
1
NOPs代表什么? - fearofawhackplanet
1
@FearOfAWhackPlanet ~ NOP ~ 通常被称为无操作。http://en.wikipedia.org/wiki/NOP ~~ 换句话说,它是一个占位符。 - jcolebrand
你需要想办法提出一个实际的技术问题。例如:“我看到很多代码X,觉得它们有问题。在这种情况下,我应该建议什么更好的代码模式?或者,如果这是一个有效的模式,我如何确定何时使用这个模式?” 更新 - Joel Coehoorn
显示剩余9条评论
7个回答

9
你的第一个例子很奇怪,我甚至不确定作者想要的效果是否能够实现。第二个例子只是在不必要的括号中包裹了第一个例子。第三个例子使用了自执行函数,但由于匿名函数本身就创建了它自己的作用域(可能也会有闭包),我不确定它有什么好处(除非作者在闭包中指定了其他变量 - 继续阅读)。
自执行函数采用模式 (function f () { /* do stuff */ }()) 并且在立即执行时被求值,而不是在调用时才被求值。所以像这样的内容:
var checkReady = (function () {
    var ready = false;
    return {
        loaded: function () { ready = true; },
        test: function () { return ready; }
    };
}())
$(document).ready(checkReady.loaded);

创建一个闭包,将返回的对象绑定到变量ready(该变量对闭包外部不可见)。这样就可以通过调用checkReady.test()来检查文档是否已经加载(根据jQuery的定义)。这是一种非常强大的模式,具有很多合法的用途(命名空间、记忆化、元编程),但在您的示例中并不是必需的。
编辑:啊,我误解了你的问题。没有意识到你在指出糟糕的实践,而不是询问模式的澄清。关于你所提到的最终形式,更重要的是:
(function () { /* woohoo */ }())
(function () { /* woohoo */ })()
function () { /* woohoo */ }()

评估结果大致相同-但第一种形式最不可能产生任何意外后果。


谢谢!你和我想的一样。我不知道那个代码块是干什么用的,但已经有人识别出来了,这很有帮助。但我同意,我看到使用它的人可能并不知道它的作用或原因。我认为他们没有得到正确的教导。 - jcolebrand
此外,第一个代码块实际上是从这个网站的一个新成员的问题中提取的代码(因此我不想点名今天引起我的兴趣并促使我提出问题的那个人),但我已经看到它很多次了,所以我认为这不是偶然事件,而是被教授的。 - jcolebrand
我不仅要指出糟糕的编程实践,还想羞辱那些教授新手程序员这些糟糕实践的人。我的意思是,看,我整天都在使用匿名函数,因为它们确实有用。但是,我不会鼓励人们像第一个代码块展示的那样去做,因为他们不会理解它。 - jcolebrand
JavaScript,不管好坏,有很多盲目模仿的程序员。也就是说,他们看到别人做了什么,就模仿那些东西,但并不真正理解底层习惯用法。即使是其他有才华的开发者也会陷入这个陷阱。Douglas Crockford 的这句话似乎很贴切:“JavaScript 是我所知道的唯一一个人们在使用之前不觉得需要学习的编程语言。” - C-Mo
我认为这是本书中讨论的模式之一http://www.amazon.com/JavaScript-Good-Parts-Douglas-Crockford/dp/0596517742——尽管可能没有被充分讨论。 - Joel Coehoorn

1

我认为很多人都会同意你的观点,有时候试图过于聪明反被聪明误并不好。如果不需要某种语言结构,就不要使用它,特别是如果它容易引起混淆。

但话又说回来,有时确实需要使用这些东西。

(function(){
    var x = 'test';
    alert(x);
})()

alert(typeof(x)); //undefined

这里,x变量被锁定在函数的作用域中。

(我在http://helephant.com/2008/08/javascript-anonymous-functions/上找到了上面的例子)

因此,您可以使用JavaScript对作用域进行奇怪的操作;这是一种方法。我同意您的观点,即许多js代码比它需要的更复杂。


之前的评论已被删除,谢谢。信息很有用 ;) - jcolebrand

1

我不确定你是否完全重现了你的例子,但是针对这个问题

// I picked document ready from jQuery cos you ALL know it, I'm sure ;) 
$('#mySelector').click( function(event) { // this line is all boilerplate, right? 
  alert( $(this).attr('id') ); 

  return function() { // <-- WHAT THE ???? IS THIS ????  ? 
    // before you run it, what's the value here? 
    alert( $(this).attr('id') ); 
  } 
}); 

以及你的第一个示例,返回的匿名函数从未执行。如果你在这里返回false而不是函数,那么默认的动作将被阻止。现在,我猜测它被浏览器评估为事件处理程序返回了真值

如果将其改为这样:

// I picked document ready from jQuery cos you ALL know it, I'm sure ;) 
$('#mySelector').click( function(event) { // this line is all boilerplate, right? 
  alert( $(this).attr('id') ); 

  return function() { // <-- WHAT THE ???? IS THIS ????  ? 
    // before you run it, what's the value here? 
    alert( $(this).attr('id') ); 
  } 
}() ); 

外部函数将在我们附加事件处理程序时执行。第一个警报也会在此时发生,以及在相同范围内进行的任何其他准备工作。结果是生成在 'onclick' 期间触发的实际方法,即内部函数,该函数还可以在准备阶段访问数据。

我可以看到在某些情况下这是合适的,尽管它似乎相当复杂。至于前面的例子,我会假设您的程序员不知道自己在做什么。


我认为假设是“初学者程序员”和“货物崇拜者”。但你知道吗?我没有测试过一些代码示例,所以我之前声称它们可以运行,现在我不确定这是正确的。我认为你是对的,我应该测试一下是否真正执行了代码。我认为这取决于它如何被包装(自调用函数应该可以运行)。 - jcolebrand
我同意。新程序员采用jQuery表单并运行它,而不考虑实际发生了什么。 - lincolnk

1

代码块#3是唯一一个稍微合理的 - 您永远不会从事件处理程序返回函数。然而,自执行匿名函数有许多用途,例如命名空间或私有变量。尽管如此,在事件处理程序(它本身就是匿名函数)中大多数这些用途都是不必要的。但是,它们可以用于避免闭包的副作用:

$(document).ready(function() {
    for (var i = 9; i <= 10; i++) {
        $('#button'+i).click(function() {
            alert('Button '+i+' clicked');
        });
    }
});

这将会对所有按钮触发 "Button 10 clicked" 的警报,但是这个会正常工作:

$(document).ready(function() {
    for (var i = 9; i <= 10; i++) {
        (function() {
            var j = i;
            $('#button'+j).click(function() {
                alert('Button '+j+' clicked');
            });
        })();
    }
});

1

匿名函数的用途包括:

  • 将逻辑传递给另一个函数
  • 声明单次使用的函数
  • 声明一个不添加变量到作用域的函数
  • 为变量提供作用域
  • 动态编程

您可以在以下链接中了解更多关于这些主题的内容:javascript 匿名函数


1
抱歉兄弟,我已经掌握了匿名函数。我整天都在使用它们。这不是我的问题。 - jcolebrand
好的,我来翻译。他们是从这里学习它们的:http://www.learn-javascript-tutorial.com/AdvancedTechniques.cfm#anonymous-functions - Michael Goldshteyn
1
很抱歉,你还是没有理解我的问题。怎么回事? - jcolebrand
问题已经完全改写。是否要删除或修改?如果需要,我会删除我的评论。 - jcolebrand

0

对于代码块一,它看起来有点像我见过的用于具有私有成员的对象的模式。考虑:

function MyObject()  {
   var privateMember = 1;

   return {
        "Add":function(x) {return x + privateMember;},
        "SetPrivateMember":function(x) {privateMember = x;},
        "GetPrivateMember":privateMember
   };
}

如果我们谈论的是创建具有私有成员的对象,我会接受这个作为有效的。然而,在这种情况下,代码块是关于响应事件的,所以这不是使用此结构的有效原因。这样说通了吗? - jcolebrand

0

抱歉,不行。在嵌套的“在这里做些事情”中,第一个代码块使用this的变量与一个缺乏经验的闭包编码人员所期望的this不同。请再次阅读我的代码。 - jcolebrand
我从未说过你不知道匿名函数是什么。我特别指的是它们隐藏作用域并维护状态的能力,这些状态只能由闭包修改,而不会被其他代码意外地修改。 - CaffGeek
@Chad ~问题完全重新表述。是否考虑删除或修改?如果是,我会删除我的评论。 - jcolebrand
@drachenstern,不好意思,我的评论仍然有效。我认为你的代码重构是错误的。使用全局变量而不是闭包是危险的。 - CaffGeek
我并没有说我的重构代码是正确的。我说它比他原来的好。我不是为了卓越而重新编写它。 - jcolebrand
显示剩余2条评论

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