Fungsi di JavaScript yang hanya bisa dipanggil sekali


116

Saya perlu membuat fungsi yang hanya dapat dijalankan sekali, setiap kali setelah yang pertama tidak akan dijalankan. Saya tahu dari C ++ dan Java tentang variabel statis yang dapat melakukan pekerjaan itu tetapi saya ingin tahu apakah ada cara yang lebih elegan untuk melakukan ini?


~ 8 tahun kemudian saya membuat permintaan fitur ke lib dekorator saya ( github.com/vlio20/utils-decorators ) untuk memiliki dekorator yang akan melakukan hal itu ( github.com/vlio20/utils-decorators/issues/77 )
vlio20

Jawaban:


221

Jika dengan "tidak akan dieksekusi" yang Anda maksud "tidak akan melakukan apa-apa saat dipanggil lebih dari sekali", Anda dapat membuat penutupan:

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

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

Sebagai jawaban atas komentar @Vladloffe (sekarang dihapus): Dengan variabel global, kode lain dapat menyetel ulang nilai dari tanda "dieksekusi" (apa pun nama yang Anda pilih). Dengan penutupan, kode lain tidak memiliki cara untuk melakukan itu, baik secara tidak sengaja atau sengaja.

Seperti jawaban lain yang ditunjukkan di sini, beberapa pustaka (seperti Underscore dan Ramda ) memiliki fungsi utilitas kecil (biasanya bernama once()[*] ) yang menerima fungsi sebagai argumen dan mengembalikan fungsi lain yang memanggil fungsi yang disediakan tepat sekali, terlepas dari bagaimana caranya berkali-kali fungsi yang dikembalikan dipanggil. Fungsi yang dikembalikan juga menyimpan nilai yang pertama kali dikembalikan oleh fungsi yang disediakan dan mengembalikannya pada panggilan berikutnya.

Namun, jika Anda tidak menggunakan pustaka pihak ketiga seperti itu, tetapi masih menginginkan fungsi utilitas seperti itu (daripada solusi nonce yang saya tawarkan di atas), cukup mudah untuk diterapkan. Versi terbaik yang pernah saya lihat adalah yang ini diposting oleh David Walsh :

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

Saya akan cenderung untuk berubah fn = null;menjadi fn = context = null;. Tidak ada alasan bagi penutupan untuk mempertahankan referensi ke contextsekali fntelah dipanggil.

[*] Namun, ketahuilah, bahwa pustaka lain, seperti ekstensi Drupal ke jQuery ini , mungkin memiliki fungsi bernama once()yang melakukan sesuatu yang sangat berbeda.


1
bekerja sangat baik, dapatkah Anda menjelaskan login di belakangnya, bagaimana var dieksekusi = false; bekerja
Amr.Ayoub

@EgyCode - Ini dijelaskan dengan baik dalam dokumentasi MDN tentang penutupan .
Ted Hopp

maaf, maksud saya logika, saya tidak pernah mengerti boolean var dan bagaimana cara kerjanya dalam kasus itu dieksekusi
Amr.Ayoub

1
@EgyCode - Dalam konteks tertentu (seperti dalam ifekspresi pengujian pernyataan), JavaScript mengharapkan salah satu nilai trueatau falsedan aliran program bereaksi sesuai dengan nilai yang ditemukan saat ekspresi dievaluasi. Operator bersyarat seperti ==selalu mengevaluasi ke nilai boolean. Variabel juga dapat menampung salah satu trueatau false. (Untuk info lebih lanjut, lihat dokumentasi tentang boolean , truthy , and falsey .)
Ted Hopp

Saya tidak mengerti mengapa dieksekusi tidak diatur ulang dengan setiap panggilan baru. Bisakah seseorang menjelaskannya?
Juanse Cora

64

Gantilah dengan fungsi NOOP (tanpa operasi) yang dapat digunakan kembali .

// 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: Bagaimana ini janggal? Sekali lagi, ini sangat bersih, membutuhkan lebih sedikit kode, dan tidak memerlukan variabel baru untuk setiap fungsi yang harus dinonaktifkan. Sebuah "noop" dirancang tepat untuk situasi seperti ini.
I Hate Lazy

1
@fableal: Saya baru saja melihat jawaban hakra. Jadi buat closure dan variabel baru setiap kali Anda perlu melakukan ini ke fungsi baru? Anda memiliki definisi yang sangat lucu tentang "elegan".
I Hate Lazy

2
Sesuai dengan respons asawyer, Anda hanya perlu melakukan _.once (foo) atau _.once (bar), dan fungsinya sendiri tidak perlu diperhatikan hanya dijalankan sekali (tidak perlu noop dan tidak perlu yang * = noop).
fableal

7
Bukan solusi terbaik. Jika Anda meneruskan fungsi ini sebagai callback, itu masih bisa dipanggil beberapa kali. Misalnya: setInterval(foo, 1000)- dan ini sudah tidak berfungsi lagi. Anda baru saja menimpa referensi dalam lingkup saat ini.
seekor kucing

1
Reusable invalidatefungsi yang bekerja dengan setIntervaldll,:. Jsbin.com/vicipar/1/edit?js,console
Q20

32

Arahkan ke fungsi kosong setelah dipanggil:

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>


Atau, seperti:

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
Solusi ini lebih sesuai dengan semangat bahasa yang sangat dinamis seperti Javascript. Mengapa mengatur semaphore, ketika Anda dapat dengan mudah mengosongkan fungsi setelah digunakan?
Ivan Čurdinjaković

Solusi yang sangat bagus! Solusi ini juga berkinerja lebih baik daripada pendekatan penutupan. Satu-satunya "kelemahan" kecil adalah Anda harus menjaga nama fungsi tetap sinkron jika namanya berubah.
Lionel

5
Masalahnya adalah jika ada referensi lain ke fungsi di suatu tempat (misalnya, ia diberikan sebagai argumen dan disimpan di variabel lain di suatu tempat - seperti dalam panggilan ke setInterval()) maka referensi akan mengulangi fungsionalitas asli saat dipanggil.
Ted Hopp

@TedHopp - inilah perlakuan khusus untuk kasus-kasus tersebut
vsync

1
Ya, persis seperti jawaban Bunyk di utas ini. Ini juga mirip dengan closure (seperti dalam jawaban saya ) tetapi menggunakan properti daripada variabel closure. Kedua kasus tersebut sangat berbeda dari pendekatan Anda dalam jawaban ini.
Ted Hopp

25

UnderscoreJs memiliki fungsi yang melakukan itu, 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
Tampaknya lucu bagi saya untuk oncemenerima argumen. Anda dapat melakukan squareo = _.once(square); console.log(squareo(1)); console.log(squareo(2));dan mendapatkan 1kedua panggilan ke squareo. Apakah saya memahami ini dengan benar?
aschmied

@aschmied Anda benar - hasil dari kumpulan argumen panggilan pertama akan dimomisasi dan dikembalikan untuk semua panggilan lainnya terlepas dari parameternya karena fungsi yang mendasarinya tidak pernah dipanggil lagi. Dalam kasus seperti itu saya tidak menyarankan menggunakan _.oncemetode tersebut. Lihat jsfiddle.net/631tgc5f/1
asawyer

1
@aschmied Atau saya kira menggunakan panggilan terpisah untuk sekali per set argumen. Saya tidak berpikir ini benar-benar dimaksudkan untuk penggunaan semacam itu.
asawyer

1
Berguna jika Anda sudah menggunakan _; Saya tidak akan merekomendasikan bergantung pada seluruh perpustakaan untuk kode sekecil itu.
Joe Shanahan

11

Berbicara tentang variabel statis, ini sedikit mirip dengan varian penutupan:

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

once(); once(); 

Anda kemudian dapat mengatur ulang suatu fungsi jika Anda ingin:

once.done = false;


3

Anda bisa saja memiliki fungsi "menghapus dirinya sendiri"

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

    Once = undefined;
}

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

Tapi ini mungkin bukan jawaban terbaik jika Anda tidak ingin salah menelan.

Anda juga bisa melakukan ini:

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

    Once = function(){};
}

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

Saya membutuhkannya untuk bekerja seperti smart pointer, jika tidak ada elemen dari tipe A maka dapat dijalankan, jika ada satu atau lebih elemen A, fungsinya tidak dapat dijalankan.

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

    // do stuff
}

1
Saya membutuhkannya untuk bekerja seperti smart pointer, jika tidak ada elemen dari tipe A maka dapat dijalankan, jika ada satu atau lebih elemen A, fungsinya tidak dapat dijalankan.
vlio20

@VladIoffe Bukan itu yang Anda minta.
Shmiddty

Ini tidak akan berfungsi jika Oncediteruskan sebagai panggilan balik (mis., setInterval(Once, 100)). Fungsi asli akan terus dipanggil.
Ted Hopp

2

coba ini

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

2

Dari beberapa pria bernama Crockford ... :)

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

1
Ini bagus jika menurut Anda itu TypeError: Cannot read property 'apply' of nullbagus. Itulah yang Anda dapatkan saat kedua kali Anda memanggil fungsi yang dikembalikan.
Ted Hopp

2

Reusable invalidatefungsi yang bekerja dengan 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);

Cobalah di JSBin: https://jsbin.com/vicipar/edit?js,console

Variasi jawaban dari Bunyk


1

Berikut adalah contoh JSFiddle - http://jsfiddle.net/6yL6t/

Dan kodenya:

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

Anda bisa melakukan:

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

Dan itu akan berjalan hanya sekali :)


1

Pengaturan awal:

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

Jika Anda menggunakan Node.js atau menulis JavaScript dengan browserify, pertimbangkan modul npm "Once" :

var once = require('once')

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

1

Jika Anda ingin dapat menggunakan kembali fungsi tersebut di masa mendatang, maka ini berfungsi dengan baik berdasarkan kode ed Hopp di atas (Saya menyadari bahwa pertanyaan asli tidak memerlukan fitur tambahan ini!):

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

Outputnya terlihat seperti:

Hello World!
Reset
Hello World!

1

dekorator sederhana yang mudah ditulis saat Anda membutuhkan

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

menggunakan:

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

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

0

Mencoba menggunakan fungsi garis bawah "sekali":

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

http://underscorejs.org/#once


nah, itu terlalu jelek ketika Anda mulai menyebutnya dengan argumen.
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

Jika Anda menggunakan Ramda, Anda dapat menggunakan fungsi "sekali" .

Kutipan dari dokumentasi:

Once Function (a… → b) → (a… → b) PARAMETERS Ditambahkan dalam v0.1.0

Menerima fungsi fn dan mengembalikan fungsi yang menjaga pemanggilan fn sedemikian rupa sehingga fn hanya dapat dipanggil sekali, tidak peduli berapa kali fungsi yang dikembalikan tersebut dipanggil. Nilai pertama yang dihitung dikembalikan dalam pemanggilan berikutnya.

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

0

buat sesederhana mungkin

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

Anda bisa lihat hasilnya

hasil skrip


Jika Anda berada di dalam modul, cukup gunakan thissebagai penggantiwindow
Shreeketh K

0

JQuery memungkinkan untuk memanggil fungsi hanya sekali menggunakan metode 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>

Implementasi menggunakan metode JQuery pada () :

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>

Implementasi menggunakan JS asli:

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

Ini adalah praktik yang buruk untuk mencemari ruang lingkup global (alias jendela)
vlio20

Saya setuju dengan Anda tetapi seseorang mungkin mendapatkan sesuatu darinya.
atw

Ini tidak berhasil. Ketika kode itu pertama kali dijalankan, fungsinya dibuat. Kemudian ketika fungsi dipanggil itu dijalankan dan global disetel ke false, tetapi fungsi itu masih bisa dipanggil di lain waktu.
trincot

Ini tidak disetel ke false di mana pun.
atw

-2

Yang ini berguna untuk mencegah loop tak terbatas (menggunakan jQuery):

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

Jika Anda khawatir tentang polusi namespace, gantikan string acak yang panjang untuk "doIt".


-2

Ini membantu mencegah eksekusi yang lengket

var done = false;

function doItOnce(func){
  if(!done){
    done = true;
    func()
  }
  setTimeout(function(){
    done = false;
  },1000)
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.