Yalnızca bir kez çağrılabilen JavaScript işlevi


116

Yalnızca bir kez çalıştırılabilen bir işlev oluşturmam gerekiyor, ilkinden sonra her seferinde çalıştırılmayacak. C ++ ve Java'dan işi yapabilecek statik değişkenler hakkında bilgi sahibiyim ama bunu yapmanın daha zarif bir yolu olup olmadığını bilmek istiyorum.


~ 8 yıl sonra , tam olarak bunu yapacak bir dekoratöre sahip olmak için dekoratörlerim lib'ye ( github.com/vlio20/utils-decorators ) bir özellik isteği oluşturdum ( github.com/vlio20/utils-decorators/issues/77 )
vlio20

Yanıtlar:


221

"Çalıştırılmayacak" derken, "birden fazla çağrıldığında hiçbir şey yapmayacağını" kastediyorsunuz, bir kapatma oluşturabilirsiniz:

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

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

@Vladloffe tarafından yapılan bir yoruma cevap olarak (şimdi silindi): Global bir değişkenle, diğer kod "çalıştırılan" bayrağın değerini (hangi adı seçerseniz seçin) sıfırlayabilir. Bir kapatma ile, başka bir kodun bunu yanlışlıkla veya kasıtlı olarak yapmasının bir yolu yoktur.

Buradaki diğer yanıtların da işaret ettiği gibi, birkaç kitaplığın ( Alt Çizgi ve Ramda gibi ) bir işlevi bağımsız değişken olarak kabul eden ve sağlanan işlevi nasıl olursa olsun tam olarak bir kez çağıran başka bir işlevi döndüren küçük bir yardımcı işlev işlevi (genellikle once()[*] olarak adlandırılır ) vardır. çoğu kez döndürülen işlev çağrılır. Döndürülen işlev ayrıca sağlanan işlev tarafından ilk döndürülen değeri önbelleğe alır ve sonraki çağrılarda döndürür.

Bununla birlikte, böyle bir üçüncü taraf kitaplığı kullanmıyorsanız, ancak yine de böyle bir yardımcı program işlevi istiyorsanız (yukarıda sunduğum nonce çözümden ziyade), uygulanması yeterince kolaydır. Gördüğüm en güzel versiyon , David Walsh tarafından gönderilen bu versiyon :

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

Ben değiştirmek için eğimli olacak fn = null;etmek fn = context = null;. Bir contextzamanlar fnçağrılan bir referansı sürdürmek için kapatmanın bir nedeni yok .

[*] Bununla birlikte, jQuery'nin bu Drupal uzantısı gibi diğer kitaplıkların, once()oldukça farklı bir şey yapan adlandırılmış bir işlevi olabileceğini unutmayın .


1
çok iyi çalışıyor, arkasındaki girişi açıklayabilir misiniz, nasıl yürütüldü = false; çalışmalar
Amr.Ayoub

@EgyCode - Bu, MDN belgelerinde kapatmalarla ilgili güzel bir şekilde açıklanmıştır .
Ted Hopp

üzgünüm, mantığı kastetmiştim, boolean
varlığını

1
@EgyCode - Belirli bağlamlarda (bir ififade test ifadesinde olduğu gibi), JavaScript değerlerden birini bekler trueveya falseprogram akışı, ifade değerlendirildiğinde bulunan değere göre tepki verir. Koşullu operatörler ==her zaman bir mantıksal değer olarak değerlendirilir. Bir değişken ayrıca trueveya tutabilir false. (Daha fazla bilgi için belgelerine bakın boole , truthy ve Falsey .)
Ted Hopp

Her yeni aramada neden sıfırlanmadığını anlamıyorum. Biri bunu açıklayabilir mi lütfen?
Juanse Cora

64

Yeniden kullanılabilir bir NOOP (işlem yok) işleviyle değiştirin.

// 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: Bu nasıl mantıksız? Yine çok temizdir, daha az kod gerektirir ve devre dışı bırakılması gereken her işlev için yeni bir değişken gerektirmez. Bir "noop" tam olarak bu tür durumlar için tasarlanmıştır.
I Hate Lazy

1
@fableal: Hakra'nın cevabına baktım. Öyleyse, bunu yeni bir işlev için her yapmanız gerektiğinde yeni bir kapanış ve değişken yapın? Çok komik bir "zarif" tanımınız var.
I Hate Lazy

2
Asawyer'in cevabına göre, yalnızca _.once (foo) veya _.once (bar) yapmanız gerekiyordu ve işlevlerin kendilerinin yalnızca bir kez çalıştırıldığının farkında olması gerekmiyor (noop'a gerek yok ve gerek yok) * = noop).
fableal

7
Gerçekten en iyi çözüm değil. Bu işlevi bir geri arama olarak iletiyorsanız, yine de birden çok kez çağrılabilir. Örneğin: setInterval(foo, 1000)- ve zaten bu artık çalışmıyor. Sadece mevcut kapsamdaki referansın üzerine yazıyorsunuz.
bir kedi

1
Yeniden kullanılabilir invalidateçalışır fonksiyonu setIntervalvb. Jsbin.com/vicipar/1/edit?js,console
Q20

32

Çağrıldıktan sonra boş bir işlevi işaret edin:

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>


Veya bunun gibi:

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
Bu çözüm, Javascript gibi oldukça dinamik bir dilin ruhuna çok daha uygun. Fonksiyonu kullanıldıktan sonra basitçe boşaltabilecekken neden semaforlar ayarlayasınız?
Ivan Čurdinjaković

Çok güzel çözüm! Bu çözüm aynı zamanda kapatma yaklaşımından daha iyi performans gösteriyor. Tek küçük "dezavantaj", isim değişirse işlev adını senkronize tutmanız gerekmesidir.
Lionel

5
Bununla ilgili sorun, bir yerde işleve başka bir referans varsa (örneğin, bir argüman olarak aktarılmışsa ve başka bir değişkende saklanmışsa - bir çağrıda olduğu gibi setInterval()), o zaman referans çağrıldığında orijinal işlevi tekrarlayacaktır.
Ted Hopp

@TedHopp - işte bu vakalar için özel bir tedavi
vsync

1
Evet, bu aynen Bunyk'in bu konudaki cevabı gibi . O (olduğu gibi aynı zamanda bir kapatma benzeyen Cevabıma ) ancak bir özellik yerine bir kapatma değişkeni kullanılarak. Her iki durum da bu cevaptaki yaklaşımınızdan oldukça farklıdır.
Ted Hopp

25

UnderscoreJs bunu yapan bir işleve sahiptir, altçizgi.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
onceArgümanları kabul etmek bana komik geliyor . Her iki çağrı için de yapabilir squareo = _.once(square); console.log(squareo(1)); console.log(squareo(2));ve alabilirsiniz . Bunu doğru anlıyor muyum? 1squareo
2016

@aschmied Haklısınız - ilk çağrının argüman kümesinin sonucu hafızaya alınacak ve temeldeki işlev bir daha asla çağrılmayacağından, parametreye bakılmaksızın diğer tüm çağrılar için döndürülecektir. Böyle durumlarda _.onceyöntemi kullanmanızı önermiyorum . Bkz jsfiddle.net/631tgc5f/1
asawyer

1
@aschmied Veya her bağımsız değişken kümesi için bir kez ayrı bir çağrı kullanın. Bunun gerçekten bu tür bir kullanım için tasarlandığını sanmıyorum.
asawyer

1
Zaten kullanıyorsanız kullanışlı _; Bu kadar küçük bir kod parçası için tüm kütüphaneye bağlı olarak tavsiye etmem.
Joe Shanahan

11

Statik değişkenlerden bahsetmişken, bu biraz kapanış varyantına benzer:

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

once(); once(); 

Daha sonra isterseniz bir işlevi sıfırlayabilirsiniz:

once.done = false;


3

"Kendini kaldır" işlevine sahip olabilirsiniz

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

    Once = undefined;
}

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

Ancak, hataları yutmak istemiyorsanız, bu en iyi cevap olmayabilir.

Bunu da yapabilirsiniz:

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

    Once = function(){};
}

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

Akıllı işaretçi gibi çalışmasına ihtiyacım var, eğer A tipinden hiç eleman yoksa çalıştırılabilir, bir veya daha fazla A elemanı varsa, fonksiyon çalıştırılamaz.

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

    // do stuff
}

1
Akıllı işaretçi gibi çalışmasına ihtiyacım var, eğer A tipinden herhangi bir eleman yoksa çalıştırılabilir, bir veya daha fazla A elemanı varsa fonksiyon çalıştırılamaz.
vlio20

@VladIoffe Sorduğun bu değil.
Shmiddty

OnceGeri arama olarak iletilirse bu işe yaramaz (örneğin, setInterval(Once, 100)). Orijinal işlev çağrılmaya devam edecek.
Ted Hopp

2

bunu dene

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

2

Crockford adında bir adamdan ... :)

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

1
Bunun harika olduğunu düşünüyorsanız bu TypeError: Cannot read property 'apply' of nullharika. Döndürülen işlevi ikinci kez çalıştırdığınızda elde ettiğiniz şey budur.
Ted Hopp

2

Aşağıdakilerle invalidateçalışan yeniden kullanılabilir işlev 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'de deneyin: https://jsbin.com/vicipar/edit?js,console

Bunyk'ten cevap varyasyonu


1

Örnek JSFiddle - http://jsfiddle.net/6yL6t/

Ve kod:

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();
    }
}

Yapabilirsin:

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

Ve sadece bir kez çalışacak :)


1

İlk kurulum:

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 kullanıyorsanız veya browsererify ile JavaScript yazıyorsanız, "bir kez" npm modülünü düşünün :

var once = require('once')

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

1

İşlevi gelecekte yeniden kullanabilmek istiyorsanız, bu, yukarıdaki ed Hopp koduna göre iyi çalışır (orijinal sorunun bu ekstra özelliği gerektirmediğini anladım!):

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

Çıktı şöyle görünür:

Hello World!
Reset
Hello World!

1

İhtiyaç duyduğunuzda yazması kolay basit dekoratör

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

kullanarak:

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

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

0

Alt çizgi "bir kez" işlevi kullanılmaya çalışılıyor:

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

http://underscorejs.org/#once


hayır, onu argümanlarla çağırmaya başladığınızda çok çirkin.
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 kullanıyorsanız, "bir kez" işlevini kullanabilirsiniz .

Belgelerden bir alıntı:

bir kez İşlev (a… → b) → (a… → b) PARAMETRELER v0.1.0'da eklendi

Bir fn işlevini kabul eder ve döndürülen işlevin kaç kez çalıştırıldığına bakılmaksızın fn'nin yalnızca bir kez çağrılabileceği şekilde fn'nin çağrılmasını koruyan bir işlev döndürür. Hesaplanan ilk değer, sonraki çağrılarda döndürülür.

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

0

mümkün olduğunca basit tutun

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

Sonucu görebilirsiniz

komut dosyası sonucu


Modülün içindeyseniz, thisbunun yerine şunu kullanınwindow
Shreeketh K

0

JQuery, one () yöntemini kullanarak işlevi yalnızca bir kez çağırmaya izin verir :

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>

() Üzerinde JQuery yöntemini kullanarak uygulama :

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>

Yerel JS kullanarak uygulama:

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

Küresel kapsamı kirletmek kötü bir uygulama (pencere olarak da bilinir)
vlio20

Sana katılıyorum ama birisi bundan bir şeyler çıkarabilir.
atw

Bu çalışmıyor. Bu kod ilk çalıştırıldığında, fonksiyon oluşturulur. Daha sonra işlev çağrıldığında çalıştırılır ve global false olarak ayarlanır, ancak işlev yine de bir sonraki sefer çağrılabilir.
trincot

Hiçbir yerde yanlış olarak ayarlanmamıştır.
atw

-2

Bu, sonsuz döngüleri önlemek için kullanışlıdır (jQuery kullanarak):

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

Ad alanı kirliliğinden endişeleniyorsanız, "doIt" için uzun, rastgele bir dizeyi değiştirin.


-2

Yapışkan yürütmeyi önlemeye yardımcı olur

var done = false;

function doItOnce(func){
  if(!done){
    done = true;
    func()
  }
  setTimeout(function(){
    done = false;
  },1000)
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.