JavaScript 中只能调用一次的函数

Posted

技术标签:

【中文标题】JavaScript 中只能调用一次的函数【英文标题】:Function in JavaScript that can be called only once 【发布时间】:2012-09-24 16:19:44 【问题描述】:

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

【问题讨论】:

~8 年后,我向我的装饰器库 (github.com/vlio20/utils-decorators) 创建了一个功能请求,以拥有一个可以做到这一点的装饰器 (github.com/vlio20/utils-decorators/issues/77) 【参考方案1】:

如果“不会被执行”的意思是“当被多次调用时什么都不做”,你可以创建一个闭包:

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

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

回复@Vladloffe 的评论(现已删除):使用全局变量,其他代码可以重置“已执行”标志的值(无论您选择什么名称)。有了闭包,其他代码就无法做到这一点,无论是有意还是无意。

正如这里的其他答案所指出的,几个库(例如 Underscore 和 Ramda)有一个小实用函数(通常命名为 once()[*]),它接受一个函数作为一个参数并返回另一个函数,该函数只调用一次提供的函数,而不管返回的函数被调用多少次。返回的函数还会缓存提供的函数首先返回的值,并在后续调用中返回。

但是,如果您不使用这样的第三方库,但仍需要实用功能(而不是我上面提供的 nonce 解决方案),那么实现起来很容易。我见过最好的版本是this one posted by 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;。一旦调用了fn,闭包就没有理由保持对context 的引用。

用法:

function something()  /* do something */ 
var one_something = once(something);

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

[*]但请注意,其他库,例如 this Drupal extension to jQuery,可能有一个名为 once() 的函数,它的作用完全不同。

【讨论】:

效果很好,能不能解释一下后面的login,var怎么执行=false;作品 @EgyCode - 这在MDN documentation on closures 中有很好的解释。 对不起,我的意思是逻辑,我从来不理解布尔变量以及在这种情况下它是如何工作的 @EgyCode - 在某些上下文中(例如在 if 语句测试表达式中),javascript 需要值 truefalse 之一,并且程序流根据找到的值做出反应表达式被评估。 == 之类的条件运算符始终计算为布尔值。变量也可以保存truefalse。 (有关详细信息,请参阅boolean、truthy 和 falsey 上的文档。) @Vaccano - 好的。现在我明白你在问什么,我很高兴你找到了解决方案。本地存储解决方案是全局变量的一种形式(尽管乍一看可能不像)。这里的许多其他答案都使用类似的东西。我认为没有理由将全局变量方法与闭包结合起来(正如 Reflective 对您问题的回答所暗示的那样。)【参考方案2】:

将其替换为可重用的 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

【讨论】:

@fableal:这怎么不优雅?同样,它非常干净,需要更少的代码,并且不需要为每个应该禁用的函数添加新变量。 "noop" 就是专门为这种情况而设计的。 @fableal:我刚刚看了哈克拉的回答。因此,每次您需要对新函数执行此操作时,都要创建一个新的闭包和变量?你对“优雅”的定义很有趣。 根据asawyer的回复,你只需要做_.once(foo)或者_.once(bar),函数本身不需要知道只运行一次(没有需要 noop 而不需要 * = noop)。 并不是最好的解决方案。如果您将此函数作为回调传递,它仍然可以被多次调用。例如:setInterval(foo, 1000) - 这已经不再起作用了。您只是覆盖当前范围内的引用。 可重用 invalidate 函数,可与 setInterval 等一起使用,.: jsbin.com/vicipar/1/edit?js,console【参考方案3】:

一旦被调用就指向一个 empty 函数:

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)

【讨论】:

这个解决方案更符合 Javascript 等高度动态语言的精神。为什么要设置信号量,一旦使用了函数就可以简单地清空它? 非常好的解决方案!该解决方案的性能也优于闭包方法。唯一的小“缺点”是如果名称更改,您需要保持函数名称同步。 这样的问题是,如果在某处有另一个对该函数的引用(例如,它作为参数传递并隐藏在某个地方的另一个变量中 - 如在对 setInterval() 的调用中),那么该引用调用时会重复原来的功能。 @TedHopp - here's 对这些情况进行特殊处理 是的,这与此线程上的 Bunyk's answer 完全相同。它也类似于闭包(如my answer),但使用属性而不是闭包变量。这两种情况都与您在此答案中的方法完全不同。【参考方案4】:

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;
    ;
  ;

【讨论】:

once 接受参数对我来说似乎很有趣。您可以使用squareo = _.once(square); console.log(squareo(1)); console.log(squareo(2)); 并获得1 来调用squareo。我理解对了吗? @aschmied 你是对的 - 第一次调用的参数集的结果将被记忆并返回给所有其他调用,无论参数如何,因为基础函数永远不会再次调用。在这种情况下,我不建议使用_.once 方法。见jsfiddle.net/631tgc5f/1 @aschmied 或者我想每个参数集单独调用一次。我不认为这真的是为了那种用途。 如果你已经在使用_,这会很方便;我不建议依赖整个库来编写这么少的代码。【参考方案5】:

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

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

once(); once(); 

如果你愿意,你可以重置一个函数:

once.done = false;

【讨论】:

【参考方案6】:

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

​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

【讨论】:

我需要它像智能指针一样工作,如果没有 A 类型的元素可以执行,如果有一个或多个 A 元素,则无法执行函数。 @VladIoffe 你问的不是这个。 如果 Once 作为回调传递(例如,setInterval(Once, 100)),这将不起作用。原来的函数会继续被调用。【参考方案7】:
var quit = false;

function something() 
    if(quit) 
       return;
     
    quit = true;
    ... other code....

【讨论】:

【参考方案8】:

试试这个

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

【讨论】:

【参考方案9】:

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

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

【讨论】:

如果您认为TypeError: Cannot read property 'apply' of null 很棒,那就太好了。这是您第二次调用返回的函数时得到的结果。【参考方案10】:

setInterval一起使用的可重用invalidate函数:

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

answer from Bunyk的变体

【讨论】:

【参考方案11】:

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

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

使用:

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

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

【讨论】:

【参考方案12】:

这是一个示例 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);
    );

它只会运行一次:)

【讨论】:

【参考方案13】:

初始设置:

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;
    ;

【讨论】:

【参考方案14】:

如果您使用 Node.js 或使用 browserify 编写 JavaScript,请考虑 "once" npm module:

var once = require('once')

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

【讨论】:

【参考方案15】:

如果您希望将来能够重用该功能,那么根据上面 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!

【讨论】:

【参考方案16】:

只开一次灯的简单示例。

function turnOnLightOnce() 
  let lightOn = false;

  return function () 
    if (!lightOn) 
      console.log("Light is not on...Turning it on for first and last time");
      lightOn = true;
    

  ;


const lightOn = turnOnLightOnce();
lightOn()  // Light is not on...Turning it on for first and last time
lightOn()
lightOn()
lightOn()
lightOn()

https://codesandbox.io/s/javascript-forked-ojo0i?file=/index.js

这是由于 JavaScript 中的闭包造成的。

【讨论】:

【参考方案17】:

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

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

http://underscorejs.org/#once

【讨论】:

不,当你开始用参数调用它时它太丑了。【参考方案18】:
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; */

【讨论】:

【参考方案19】:
if (!window.doesThisOnce)
  function myFunction() 
    // do something
    window.doesThisOnce = true;
  ;
;

【讨论】:

污染全局范围(又名窗口)是一种不好的做法 我同意你的观点,但有人可能会从中有所收获。 这不起作用。首次执行该代码时,将创建该函数。然后当函数被调用时,它会被执行并且全局设置为false,但是下次仍然可以调用该函数。 它在任何地方都没有设置为false。【参考方案20】:

如果你使用 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

【讨论】:

【参考方案21】:

尽量简单

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

你可以看到结果

【讨论】:

如果您在模块内,请使用this 而不是window【参考方案22】:

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>

【讨论】:

【参考方案23】:

为了好玩而把我的帽子扔在戒指中,增加了记忆的优势

const callOnce = (fn, i=0, memo) => () => i++ ? memo : (memo = fn());
// usage
const myExpensiveFunction = () =>  return console.log('joe'),5; 
const memoed = callOnce(myExpensiveFunction);
memoed(); //logs "joe", returns 5
memoed(); // returns 5
memoed(); // returns 5
...

【讨论】:

【参考方案24】:

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

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

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

【讨论】:

【参考方案25】:

有助于防止粘滞执行

var done = false;

function doItOnce(func)
  if(!done)
    done = true;
    func()
  
  setTimeout(function()
    done = false;
  ,1000)

【讨论】:

循环运行了 206 次,我用我的 CPU 速度计算,谢谢 loool

以上是关于JavaScript 中只能调用一次的函数的主要内容,如果未能解决你的问题,请参考以下文章

如何:编写一个只能调用一次的线程安全方法?

仅在元素完全加载时执行一次的函数

JavaScript调用函数的方法

5种 JavaScript 调用函数的方法

关于setInterval只执行一次的原因

关于setInterval只执行一次的原因