JavaScript中的函数只能调用一次


116

我需要创建一个只能执行一次的函数,在第一次执行后,每次都不会执行。我从C ++和Java知道可以完成此工作的静态变量,但是我想知道是否有更优雅的方法来做到这一点?


〜8年后,我创建了一个功能请求,我的装饰LIB(github.com/vlio20/utils-decorators)有一个装饰,将做到这些(github.com/vlio20/utils-decorators/issues/77
vlio20

Answers:


221

如果用“将不执行”来表示“多次调用将不执行任何操作”,则可以创建一个闭包:

var something = (function() {
    var executed = false;
    return function() {
        if (!executed) {
            executed = true;
            // do something
        }
    };
})();

something(); // "do something" happens
something(); // nothing happens

回答@Vladloffe(现在已删除)的评论:使用全局变量,其他代码可以重置“已执行”标志的值(无论您为它选择什么名称)。使用闭包时,其他代码都无法意外或故意这样做。

正如这里的其他答案所指出的那样,几个库(例如UnderscoreRamda)都有一个小的实用程序函数(通常命名为once()[*]),该函数接受一个函数作为参数,并返回另一个函数,该函数恰好一次调用提供的函数,无论如何多次调用返回的函数。返回的函数还会缓存所提供的函数首先返回的值,并在后续调用中返回该值。

但是,如果您不使用这样的第三方库,但仍然想要这样的实用程序功能(而不是我上面提供的现成的解决方案),则很容易实现。我见过的最好的版本是David Walsh发布的这个版本:

function once(fn, context) { 
    var result;
    return function() { 
        if (fn) {
            result = fn.apply(context || this, arguments);
            fn = null;
        }
        return result;
    };
}

我倾向于更改fn = null;fn = context = null;。有没有理由,关闭,保持一个参考context一旦fn被调用。

[*] 但是,请注意,其他库(例如jQuery的Drupal扩展)可能具有名为的函数once(),该函数的功能完全不同。


1
效果很好,您能解释一下其背后的登录信息,var如何执行= false; 作品
Amr.Ayoub '17


抱歉,我的意思是逻辑,我从不了解布尔值var及其在这种情况下的工作方式
-Amr.Ayoub

1
@EgyCode-在某些上下文中(例如在if语句测试表达式中),JavaScript期望这些值之一,true或者false程序流根据对表达式进行求值时发现的值做出反应。有条件的运算符==总是会评估为布尔值。变量也可以包含truefalse。(有关更多信息,请参见有关booleantruefalse的文档。)
Ted Hopp

我不明白为什么每次执行新调用时都不会重置执行。有人可以解释吗?
Juanse Cora

64

将其替换为可重用的NOOP (无操作)功能。

// this function does nothing
function noop() {};

function foo() {
    foo = noop; // swap the functions

    // do your thing
}

function bar() {
    bar = noop; // swap the functions

    // do your thing
}

11
@fableal:这很优雅吗?同样,它非常干净,需要更少的代码,并且不需要为每个应禁用的功能使用新变量。专门为这种情况设计了一个“小睡”
我讨厌懒惰的2012年

1
@fableal:我只是看了hakra的答案。因此,每当您需要对新函数执行此操作时,都创建一个新的闭包和变量吗?您对“优雅”有一个非常有趣的定义。
我讨厌懒惰的2012年

2
根据asawyer的响应,您只需要执行_.once(foo)或_.once(bar),并且函数本身不需要知道只运行一次(不需要noop,也不需要执行* = noop)。
寓言

7
并不是真正的最佳解决方案。如果您将此函数作为回调传递,则仍然可以多次调用它。例如:setInterval(foo, 1000)-并且已经不起作用了。您只是覆盖当前范围中的引用。
一只猫

1
可重复使用的invalidate功能,可以与其他功能一起使用setInterval。:jsbin.com/vicipar/1/edit?js
Q20

32

调用后,指向一个函数:

function myFunc(){
     myFunc = function(){}; // kill it as soon as it was called
     console.log('call once and never again!'); // your stuff here
};
<button onClick=myFunc()>Call myFunc()</button>


或者,像这样:

var myFunc = function func(){
     if( myFunc.fired ) return;
     myFunc.fired = true;
     console.log('called once and never again!'); // your stuff here
};

// even if referenced & "renamed"
((refToMyfunc)=>{
  setInterval(refToMyfunc, 1000);
})(myFunc)


1
该解决方案更符合高度动态语言(如Javascript)的精神。当您只需要在函数使用完毕后就清空它,为什么还要设置信号量呢?
伊万·库尔迪尼亚科维奇(IvanČurdinjaković)2014年

非常好的解决方案!此解决方案的性能也比封闭方法更好。唯一的“缺点”是,如果名称更改,则需要使函数名称保持同步。
Lionel

5
这样做的问题是,如果在某个地方有另一个对该函数的引用(例如,它作为参数传递并藏在另一个变量中,例如在调用中setInterval()),则该引用将在被调用时重复原始功能。
Ted Hopp

@TedHopp - 这里有下述情况特殊处理
VSYNC

1
是的,这与Bunyk在该线程上的答案完全一样。它也类似于闭包(如我的回答),但是使用属性而不是闭包变量。两种情况都与您在此答案中的方法完全不同。
Ted Hopp

25

UnderscoreJs具有执行此功能的功能,underscorejs.org /#once

  // Returns a function that will be executed at most one time, no matter how
  // often you call it. Useful for lazy initialization.
  _.once = function(func) {
    var ran = false, memo;
    return function() {
      if (ran) return memo;
      ran = true;
      memo = func.apply(this, arguments);
      func = null;
      return memo;
    };
  };

1
once接受论点对我来说似乎很可笑。您可以同时拨打squareo = _.once(square); console.log(squareo(1)); console.log(squareo(2));1致电squareo。我了解这个权利吗?
2013年

@aschmied您是正确的-不管参数如何,第一次调用的一组参数的结果将被记忆并返回给所有其他调用,因为不再调用基础函数。在这种情况下,我不建议使用该_.once方法。参见jsfiddle.net/631tgc5f/1
asawyer 2013年

1
@aschmied或者我猜每个参数集使用单独的调用一次。我认为这不是真的要用于这种用途。
asawyer

1
如果您已经在使用_,则非常方便;我不建议依赖整个库来提供这么少的代码。
Joe Shanahan

11

谈到静态变量,这有点像闭包变量:

var once = function() {
    if(once.done) return;
    console.log('Doing this once!');
    once.done = true;
};

once(); once(); 

然后,您可以根据需要重置功能:

once.done = false;


3

您可以简单地使用“删除自身”功能

function Once(){
    console.log("run");

    Once = undefined;
}

Once();  // run
Once();  // Uncaught TypeError: undefined is not a function 

但是,如果您不希望吞下错误,那么这可能不是最佳答案。

您也可以这样做:

function Once(){
    console.log("run");

    Once = function(){};
}

Once(); // run
Once(); // nothing happens

我需要它像智能指针一样工作,如果没有来自类型A的元素,则可以执行,如果有一个或多个A元素,则该函数无法执行。

function Conditional(){
    if (!<no elements from type A>) return;

    // do stuff
}

1
我需要它像智能指针一样工作,如果没有来自类型A的元素,则可以执行,如果有一个或多个A元素,则该函数无法执行。
vlio20

@VladIoffe那不是你要的。
2012年

如果Once作为回调传递(例如),则此方法无效setInterval(Once, 100)。原始函数将继续被调用。
泰德·霍普

2

试试这个

var fun = (function() {
  var called = false;
  return function() {
    if (!called) {
      console.log("I  called");
      called = true;
    }
  }
})()

2

来自一个叫Crockford的家伙... :)

function once(func) {
    return function () {
        var f = func;
        func = null;
        return f.apply(
            this,
            arguments
        );
    };
}

1
如果您认为这很棒,那就TypeError: Cannot read property 'apply' of null太好了。那就是您第二次调用返回的函数。
泰德·霍普

2

可重复使用的invalidate功能,适用于setInterval

var myFunc = function (){
  if (invalidate(arguments)) return;
  console.log('called once and never again!'); // your stuff here
};

const invalidate = function(a) {
  var fired = a.callee.fired;
  a.callee.fired = true;
  return fired;
}

setInterval(myFunc, 1000);

在JSBin上尝试:https ://jsbin.com/vicipar/edit ? js,console

来自Bunyk答案的变化


1

这是一个JSFiddle示例-http: //jsfiddle.net/6yL6t/

和代码:

function hashCode(str) {
    var hash = 0, i, chr, len;
    if (str.length == 0) return hash;
    for (i = 0, len = str.length; i < len; i++) {
        chr   = str.charCodeAt(i);
        hash  = ((hash << 5) - hash) + chr;
        hash |= 0; // Convert to 32bit integer
    }
    return hash;
}

var onceHashes = {};

function once(func) {
    var unique = hashCode(func.toString().match(/function[^{]+\{([\s\S]*)\}$/)[1]);

    if (!onceHashes[unique]) {
        onceHashes[unique] = true;
        func();
    }
}

您可以这样做:

for (var i=0; i<10; i++) {
    once(function() {
        alert(i);
    });
}

它只会运行一次:)


1

最初设定:

var once = function( once_fn ) {
    var ret, is_called;
    // return new function which is our control function 
    // to make sure once_fn is only called once:
    return function(arg1, arg2, arg3) {
        if ( is_called ) return ret;
        is_called = true;
        // return the result from once_fn and store to so we can return it multiply times:
        // you might wanna look at Function.prototype.apply:
        ret = once_fn(arg1, arg2, arg3);
        return ret;
    };
}

1

如果您使用Node.js或通过browserify编写JavaScript,请考虑使用“一次” npm模块

var once = require('once')

function load (file, cb) {
  cb = once(cb)
  loader.load('file')
  loader.once('load', cb)
  loader.once('error', cb)
}

1

如果您希望将来能够重用该功能,则根据上面的ed Hopp的代码,它可以很好地工作(我意识到原来的问题并不需要此额外功能!):

   var something = (function() {
   var executed = false;              
    return function(value) {
        // if an argument is not present then
        if(arguments.length == 0) {               
            if (!executed) {
            executed = true;
            //Do stuff here only once unless reset
            console.log("Hello World!");
            }
            else return;

        } else {
            // otherwise allow the function to fire again
            executed = value;
            return;
        }       
    }
})();

something();//Hello World!
something();
something();
console.log("Reset"); //Reset
something(false);
something();//Hello World!
something();
something();

输出如下:

Hello World!
Reset
Hello World!

1

简单的装饰器,在需要时易于编写

function one(func) {
  return function () {
     func && func.apply(this, arguments);
     func = null;
  }
}

使用:

var initializer= one( _ =>{
      console.log('initializing')
  })

initializer() // 'initializing'
initializer() // nop
initializer() // nop

0

尝试使用下划线“一次”功能:

var initialize = _.once(createApplication);
initialize();
initialize();
// Application is only created once.

http://underscorejs.org/#once


不,当您开始使用参数调用它时,它太难看了。
vsync

0
var init = function() {
    console.log("logges only once");
    init = false;
}; 

if(init) { init(); }

/* next time executing init() will cause error because now init is 
   -equal to false, thus typing init will return false; */

0

如果您使用的是Ramda,则可以使用“ once”函数。

来自文档的报价:

一次功能(a…→b)→(a…→b)参数v0.1.0中已添加

接受一个函数fn并返回一个保护fn调用的函数,以使fn只能被调用一次,无论返回的函数被调用了多少次。计算的第一个值在后续调用中返回。

var addOneOnce = R.once(x => x + 1);
addOneOnce(10); //=> 11
addOneOnce(addOneOnce(50)); //=> 11

0

保持尽可能简单

function sree(){
  console.log('hey');
  window.sree = _=>{};
}

你可以看到结果

脚本结果


如果您在模块内部,请使用this代替window
Shreeketh K

0

jQuery使用方法one()只能调用一次函数:

let func = function() {
  console.log('Calling just once!');
}
  
let elem = $('#example');
  
elem.one('click', func);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
  <p>Function that can be called only once</p>
  <button id="example" >JQuery one()</button>
</div>

使用JQuery方法on()实现

let func = function(e) {
  console.log('Calling just once!');
  $(e.target).off(e.type, func)
}
  
let elem = $('#example');
  
elem.on('click', func);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
  <p>Function that can be called only once</p>
  <button id="example" >JQuery on()</button>
</div>

使用本机JS的实现:

let func = function(e) {
  console.log('Calling just once!');
  e.target.removeEventListener(e.type, func);
}
  
let elem = document.getElementById('example');
  
elem.addEventListener('click', func);
<div>
  <p>Functions that can be called only once</p>
  <button id="example" >ECMAScript addEventListener</button>
</div>


-1
if (!window.doesThisOnce){
  function myFunction() {
    // do something
    window.doesThisOnce = true;
  };
};

污染全局范围(又称窗口)是一种不好的做法
vlio20 '16

我同意您的意见,但有人可能会从中受益。
atw 2016年

这行不通。首次执行该代码时,将创建函数。然后,在调用该函数时,将执行该函数,并将global设置为false,但是下次仍可以调用该函数。
Trincot

在任何地方都不会将其设置为false。
atw 2016年

-2

这对于防止无限循环(使用jQuery)很有用:

<script>
var doIt = true;
if(doIt){
  // do stuff
  $('body').html(String($('body').html()).replace("var doIt = true;", 
                                                  "var doIt = false;"));
} 
</script>

如果您担心名称空间污染,请为“ doIt”替换一个随机的长字符串。


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.