我可以命名一个JavaScript函数并立即执行它吗?


89

我有很多这样的:

function addEventsAndStuff() {
  // bla bla
}
addEventsAndStuff();

function sendStuffToServer() {
  // send stuff
  // get HTML in response
  // replace DOM
  // add events:
  addEventsAndStuff();
}

由于DOM已更改,因此重新添加事件是必要的,因此以前附加的事件不见了。由于它们也必须一开始就附加(duh),因此它们具有很好的DRY功能。

此设置没有任何问题(或存在吗?),但是我可以对其进行一些平滑处理吗?我想创建该addEventsAndStuff()函数并立即调用它,因此它看起来并不那么业余。

以下两个都以语法错误响应:

function addEventsAndStuff() {
  alert('oele');
}();

(function addEventsAndStuff() {
  alert('oele');
})();

有没有人?



2
不,@ CristianSanchez。
马克西玛Alekz

恕我直言,您的第二个代码块很难阅读。在第一个代码块中,每个读者都清楚可以在init之后调用该函数。读者往往会忽略结尾处的结束和过度括号。
kaiser

Answers:


123

您在问题中发布的示例没有任何问题。另一种执行方法可能看起来很奇怪,但是:

var addEventsAndStuff;
(addEventsAndStuff = function(){
    // add events, and ... stuff
})();

有两种方法可以在JavaScript中定义函数。函数声明:

function foo(){ ... }

和函数表达式,这是除上述以外的任何定义函数的方式:

var foo = function(){};
(function(){})();
var foo = {bar : function(){}};

...等等

可以命名函数表达式,但是它们的名称不会传播到包含范围。表示此代码有效:

(function foo(){
   foo(); // recursion for some reason
}());

但这不是:

(function foo(){
    ...
}());
foo(); // foo does not exist

因此,为了命名函数并立即调用它,您需要定义一个局部变量,将函数作为表达式分配给它,然后调用它。


1
“可以命名函数表达式当心命名函数表达式。它们应该按照您所描述的那样工作,但是它们不能在IE9之前的IE上运行-您将获得两个函数,并且函数名称泄漏。详细信息:kangax.github.com/nfe(此问题最终在IE9中修复。)
TJ Crowder

@TJ-感谢您的参考。我没有意识到,因为我从不使用IE来测试最终产品:)我假设IE9在将其设置为IE7 / 8模式时不会模拟此错误?我认为它仍使用IE9 JS引擎... Meh
Mark Kahn

如果像我一样,使用此方法遇到jshint问题,则可以call()从此处使用该方法:stackoverflow.com/a/12359154/1231001
cfx

在某些情况下,这种方法似乎很有用,但是除了难以阅读之外,它似乎具有几乎相同的文本量。
弗拉基米尔(Vladimir)

15

这是一个很好的简写(不需要在函数分配的情况下声明任何变量):

var func = (function f(a) { console.log(a); return f; })('Blammo')

r函数将返回哪个?该function r还是var r?只是好奇。好的解决方案。虽然不必是一个位置=)
鲁迪(Rudie

我已重新命名,更清晰,但return r会一直function r为的是它的范围。一直在写Scala,所以一直试图在单行上获得一些东西。
詹姆斯·高里(James Gorrie)

@JamesGorrie,出色的速记,但是,我在控制台中尝试了两次,似乎func,func(),func()()都返回了f()的函数声明,但是我们能否按预期的那样通过func()输出'Blammo' :)?
mike652638

@ mike652638我已经在控制台中运行了它,并且控制台记录了blammo吗?
James Gorrie

5

此设置没有任何问题(或存在吗?),但是我可以对其进行一些平滑处理吗?

看一下使用事件委托。在那儿,您可以在不会消失的容器上实际监视事件,然后使用event.target(或event.srcElement在IE上)找出事件的实际发生位置并正确处理。

这样,您只需附加一次处理程序,即使您换出内容,它们也将继续工作。

这是不使用任何帮助程序库的事件委托的示例:

(function() {
  var handlers = {};

  if (document.body.addEventListener) {
    document.body.addEventListener('click', handleBodyClick, false);
  }
  else if (document.body.attachEvent) {
    document.body.attachEvent('onclick', handleBodyClick);
  }
  else {
    document.body.onclick = handleBodyClick;
  }

  handlers.button1 = function() {
    display("Button One clicked");
    return false;
  };
  handlers.button2 = function() {
    display("Button Two clicked");
    return false;
  };
  handlers.outerDiv = function() {
    display("Outer div clicked");
    return false;
  };
  handlers.innerDiv1 = function() {
    display("Inner div 1 clicked, not cancelling event");
  };
  handlers.innerDiv2 = function() {
    display("Inner div 2 clicked, cancelling event");
    return false;
  };

  function handleBodyClick(event) {
    var target, handler;

    event = event || window.event;
    target = event.target || event.srcElement;

    while (target && target !== this) {
      if (target.id) {
        handler = handlers[target.id];
        if (handler) {
          if (handler.call(this, event) === false) {
            if (event.preventDefault) {
              event.preventDefault();
            }
            return false;
          }
        }
      }
      else if (target.tagName === "P") {
        display("You clicked the message '" + target.innerHTML + "'");
      }
      target = target.parentNode;
    }
  }

  function display(msg) {
    var p = document.createElement('p');
    p.innerHTML = msg;
    document.body.appendChild(p);
  }

})();

现场例子

请注意,如果您单击动态添加到页面的消息,那么即使没有代码可以挂接正在添加的新段落上的事件,您的单击也会被注册和处理。还要注意,您的处理程序如何只是映射中的条目,而您在上有一个处理程序document.body可以执行所有调度。现在,您可能将其植根于比更具针对性的目标document.body,但您明白了。另外,在上文中,我们基本上是通过调度的id,但是您可以根据需要进行复杂或简单的匹配。

诸如jQueryPrototypeYUIClosure其他任何一种之类的现代JavaScript库都应提供事件委托功能,以平滑浏览器差异并清晰地处理边缘情况。jQuery确实做到了,它livedelegate功能,允许您使用全套的CSS3选择器(然后一些)的指定处理。

例如,这是使用jQuery的等效代码(除非我确定jQuery可以处理上述即时可用的原始版本无法处理的极端情况):

(function($) {

  $("#button1").live('click', function() {
    display("Button One clicked");
    return false;
  });
  $("#button2").live('click', function() {
    display("Button Two clicked");
    return false;
  });
  $("#outerDiv").live('click', function() {
    display("Outer div clicked");
    return false;
  });
  $("#innerDiv1").live('click', function() {
    display("Inner div 1 clicked, not cancelling event");
  });
  $("#innerDiv2").live('click', function() {
    display("Inner div 2 clicked, cancelling event");
    return false;
  });
  $("p").live('click', function() {
    display("You clicked the message '" + this.innerHTML + "'");
  });

  function display(msg) {
    $("<p>").html(msg).appendTo(document.body);
  }

})(jQuery);

即时复制


我不想使用Event.target,因为那不是正确的目标。那就对了。如果在IMG内部单击DIVDIV具有事件,Event.target则将是,IMG并且无法DIV很好地获取对象。编辑Btw我讨厌jQuery的.live“事件”
Rudie 2011年

@Rudie:Re event.target:您所描述的没有错,是正确的。如果你点击了img里面div,你正在观看的divevent.targetimg(和thisdiv)。再看看上面的内容。您可以在被单击的元素(img例如,)和您正在查看的元素(在上面,一直到document.body)之间的链中的任意位置附加处理程序。回复live:我非常希望delegate包含的版本更多live。我在live上面使用它是因为它与我在原始示例中所做的最接近。最佳
TJ Crowder

4

您可能想要创建一个帮助函数,如下所示:

function defineAndRun(name, func) {
    window[name] = func;
    func();
}

defineAndRun('addEventsAndStuff', function() {
    alert('oele');
});

2
为什么这是一个不好的解决方案?因为函数是在全局名称空间中注册的?谁对此投了反对票?
2011年

伟大的想法:))
马克西玛Alekz

4

您的代码包含一个错字:

(function addEventsAndStuff() {
  alert('oele');
)/*typo here, should be }*/)();

所以

(function addEventsAndStuff() {
  alert('oele');
 })();

作品。干杯!

基于注释的[编辑]:这应该运行并一次性返回该函数:

var addEventsAndStuff = (
 function(){
  var addeventsandstuff =  function(){
    alert('oele');
  };
  addeventsandstuff();
  return addeventsandstuff;
 }()
);

愚蠢的括号看起来都一样!谢谢!的确加油!
鲁迪2011年

是的...嗯...实际上这是行不通的。该函数立即执行,是的,但是未在任何地方注册,因此再次调用它->ReferenceError
Rudie 2011年

@Rudie:刚刚发现KomodoEdit终于完成了我一直希望Aptana Studio进行的所有工作(但是一直都中断了),而且它的高度可配置性,快速性和有用的插件打包在一起。它突出显示了匹配的括号:您甚至可以给那些匹配的括号使用牢固的红色警告颜色!试试看,它是免费的:activestate.com/komodo-edit
KooiInc 2011年

Dude ...(显然,我是在SO Web编辑器中键入的,而不是在桌面编辑器中键入的。)
Rudie 2011年

而且还有很多额外的输入内容,并记住了输入内容和输入方式。我喜欢原始的(不起作用的)解决方案,但是新的解决方案太难了=)
Rudie 2011年

2

使用ES6更简单:

var result = ((a, b) => `${a} ${b}`)('Hello','World')
// result = "Hello World"
var result2 = (a => a*2)(5)
// result2 = 10
var result3 = (concat_two = (a, b) => `${a} ${b}`)('Hello','World')
// result3 = "Hello World"
concat_two("My name", "is Foo")
// "My name is Foo"

那我又怎么称呼它呢?叫什么名字?您只保留结果,而不保留函数。
鲁迪

仅在需要中间调用并且不想重新运行它时,才在某些情况下使用此方法。无论如何,我将编辑答案以显示如何能够再次调用它,您如何看待?@Rudie
Alberto

1
我的问题是关于命名和重新运行它。concat_two可以完美地工作,但是它总是在全局范围内定义它,因此它不能与一起使用"use strict"。这对我来说并不重要,但这是一个很小的缺点。
鲁迪'18

1

如果您要创建一个函数并立即执行-

// this will create as well as execute the function a()
(a=function a() {alert("test");})();

// this will execute the function a() i.e. alert("test")
a();

1
当心,当您使用命名函数作为右侧值时(如上所述),您使用的是命名函数表达式,不幸的是,尽管该表达式应该有效,但在各种实现中都存在问题(主要是-quelle shock-IE) )。详细信息:kangax.github.com/nfe
TJ Crowder

0

尝试这样做:

var addEventsAndStuff = (function(){
    var func = function(){
        alert('ole!');
    };
    func();
    return func;
})();

1
这是关闭的,但是var foo = (function() {...})();在分配之前调用该函数。括号需要包括赋值。(您想分配然后打电话。)
David

这也需要大量输入,并记住要输入的内容。然后,我宁愿使用当前通话。
2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.