一度しか呼び出せないJavaScriptの関数


116

一度だけ実行できる関数を作成する必要があります。最初の関数が実行された後、毎回実行されません。作業を行うことができる静的変数についてC ++とJavaから知っていますが、これを行うよりエレガントな方法があるかどうか知りたいのですが?


〜8年後、私は私のデコレータのlib(への機能要求を作成github.com/vlio20/utils-decoratorsを正確に(尽くしますデコレータ持っている)github.com/vlio20/utils-decorators/issues/77
vlio20を

回答:


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())には、関数を引数として受け入れ、指定された関数を1回だけ呼び出す別の関数を返す)があります。多くの場合、返された関数が呼び出されます。返された関数は、指定された関数によって最初に返された値もキャッシュし、後続の呼び出しでそれを返します。

ただし、そのようなサードパーティのライブラリを使用していないが、そのようなユーティリティ関数(上記で提供したnonceソリューションではなく)が必要な場合は、簡単に実装できます。私が見た中で最も素晴らしいバージョンは、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 2017年

@EgyCode-これは、クロージャに関するMDNドキュメントでうまく説明されています。
Ted Hopp 2017年

申し訳ありませんが、私はロジックを意味しました、ブール
変数

1
@EgyCode-特定のコンテキスト(ifステートメントテスト式など)では、JavaScriptは値の1つを期待するか、trueまたはfalse式が評価されたときに見つかった値に従ってプログラムフローが反応します。のような条件演算子は、==常にブール値に評価されます。変数はまたはのいずれtrueかを保持することもできますfalse。(詳細については、booleantruey、およびfalseyのドキュメントを参照してください。)
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:これはいかがですか 繰り返しますが、これは非常にクリーンで、必要なコードが少なく、無効にする必要があるすべての関数に新しい変数を必要としません。「NOOPは、」この種の状況のために正確に設計されています。
私は

1
@fableal:私はハクラの答えを見ただけです。新しい関数に対してこれを行う必要があるたびに、新しいクロージャーと変数を作成しますか?「エレガント」の非常に面白い定義があります。
私はレイジーが

2
asawyerの応答に応じて、_。once(foo)または_.once(bar)を実行するだけでよく、関数自体が1度だけ実行されることを意識する必要はありません(noopの必要はなく、 * = noop)。
伝説の

7
本当に最善の解決策ではありません。この関数をコールバックとして渡している場合でも、複数回呼び出すことができます。例:setInterval(foo, 1000)-そして、これはすでに機能しなくなりました。現在のスコープの参照を上書きしているだけです。

1
etc ..invalidateで動作する再利用可能な関数setIntervaljsbin.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

とても良い解決策です!このソリューションは、クロージャアプローチよりもパフォーマンスが優れています。唯一のマイナーな「欠点」は、名前が変更された場合に関数名を同期させる必要があることです。
ライオネル2015年

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。私はこの権利を理解していますか?
16

@aschmied正解です。最初の呼び出しの引数のセットの結果は記憶され、パラメーターに関係なく他のすべての呼び出しに対して返されます。そのような場合、私は_.onceメソッドの使用を勧めません。jsfiddle.net/631tgc5f/1を
asawyer

1
@aschmiedまたは、引数セットごとに1回個別の呼び出しを使用すると思います。これは本当にそのような用途を意図したものではないと思います。
asawyer 2016

1
既に使用している場合に便利です_。このような小さなコードをライブラリ全体に依存することはお勧めしません。
ジョー・シャナハン、

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の要素が存在しない場合は実行でき、1つ以上のA要素がある場合は関数を実行できません。

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

    // do stuff
}

1
スマートポインターのように機能させる必要があります。タイプAの要素が存在しない場合は実行でき、1つ以上のA要素がある場合は関数を実行できません。
vlio20

@VladIoffeそれはあなたが尋ねたものではありません。
Shmiddty、2012年

Onceコールバックとして渡された場合(たとえばsetInterval(Once, 100))、これは機能しません。元の関数は引き続き呼び出されます。
Ted Hopp

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回目に呼び出したときに得られるものです。
テッドホップ

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

Bunykから回答のバリエーション


1

ここにJSFiddleの例があります- :

そしてコード:

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を作成している場合は、「1回」の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 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()を使用して関数を1回だけ呼び出すことができます。

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

私はあなたに同意しますが、誰かがそれから何かを得るかもしれません。
atw

これは機能しません。そのコードが最初に実行されると、関数が作成されます。その後、関数が呼び出されると、その関数が実行され、グローバルがfalseに設定されますが、その関数は引き続き次回呼び出すことができます。
トリンコット2016

どこでもfalseに設定されていません。
atw

-2

これは(jQueryを使用して)無限ループを防ぐのに役立ちます。

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

名前空間の汚染が心配な場合は、「doIt」を長くランダムな文字列に置き換えてください。


-2

スティッキー実行を防ぐのに役立ちます

var done = false;

function doItOnce(func){
  if(!done){
    done = true;
    func()
  }
  setTimeout(function(){
    done = false;
  },1000)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.