Функція в JavaScript, яку можна викликати лише один раз


116

Мені потрібно створити функцію, яку можна виконати лише один раз, кожен раз після першої вона не буде виконуватися. Я знаю з C ++ та Java про статичні змінні, які можуть виконати свою роботу, але я хотів би знати, чи існує більш елегантний спосіб зробити це?


~ 8 років потому я створив запит на особливості для моїх декораторів lib ( github.com/vlio20/utils-decorators ), щоб він мав декоратор, який зробить саме це ( github.com/vlio20/utils-decorators/isissue/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 (тепер видалено): За допомогою глобальної змінної інший код може скинути значення "виконаного" прапора (незалежно від того, яке ім'я ви вибрали для нього). Закриваючи, інший код не може це зробити, випадково чи навмисно.

Як вказують інші відповіді тут, у декількох бібліотеках (таких як Underscore і Ramda ) є невелика функціональна програма (типово названа once()[*] ), яка приймає функцію як аргумент і повертає іншу функцію, яка викликує функцію, що постачається, точно один раз, незалежно від того, як багато разів викликається повернута функція. Функція, що повертається, також кешує значення, яке спочатку повертається функцією, що надається, і повертає її при наступних викликах.

Однак якщо ви не використовуєте таку сторонній бібліотеку, але все ж хочете таку функцію утиліти (а не рішення, яке я запропонував вище), це досить просто здійснити. Найприємніша версія, яку я бачив - це ця, розміщена Девідом Уолшем :

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викликані.

[*] Однак майте на увазі, що інші бібліотеки, такі як розширення Drupal до jQuery , можуть мати функцію, яку називають, once()що робить щось зовсім інше.


1
працює дуже добре, чи можете ви пояснити логін за ним, як виконується var = false; роботи
Amr.Ayoub

@EgyCode - Це добре пояснено в документації MDN про закриття .
Тед Хопп

Вибачте, я мав на увазі логіку, я ніколи не розумів булеву вару і як це працює в такому випадку, виконаному
Amr.Ayoub

1
@EgyCode - У певних контекстах (як, наприклад, у ifтестовому виразі оператора) JavaScript очікує одне із значень trueабо falseі потік програми реагує відповідно до значення, знайденого при оцінці виразу. Умовні оператори, як ==завжди, оцінюють до булевого значення. Змінна також може містити будь-яку trueабо false. (Для отримання додаткової інформації дивіться документацію про булеві , трифти та фальси .)
Тед Хопп

Я не розумію, чому виконується не скидається з кожного нового дзвінка. Може хтось пояснить це, будь ласка?
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), а самі функції не повинні знати про те, що вони запускаються лише один раз (немає потреби в noop і немає потреби в * = noop).
байка

7
Насправді не найкраще рішення. Якщо ви передаєте цю функцію як зворотний дзвінок, її все одно можна викликати кілька разів. Наприклад: setInterval(foo, 1000)- і це вже не працює. Ви просто перезаписуєте посилання в поточному діапазоні.
кішка

1
invalidateФункція для багаторазового використання, яка працює з setIntervalтощо.: Jsbin.com/vicipar/1/edit?js,console
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. Навіщо ставити семафори, коли ви можете просто спорожнити функцію після її використання?
Іван Чурдінякович

Дуже приємне рішення! Це рішення також краще, ніж підхід до закриття. Єдиний незначний "недолік" - це те, що вам потрібно зберегти ім'я функції синхронізовано, якщо ім'я зміниться.
Ліонель

5
Проблема в тому , що якщо є ще одне посилання на функцію де - небудь (наприклад , він був переданий в якості аргументу і сховав у іншому змінному де - то - як у виклику setInterval()) , то посилання буде повторювати оригінальну функціональність при виклику.
Тед Хопп

@TedHopp - ось спеціальне лікування для цих випадків
vsync

1
Так, це точно як відповідь Буніка на цю тему. Це також схоже на закриття (як у моїй відповіді ), але використання властивості замість змінної закриття. Обидва випадки сильно відрізняються від вашого підходу у цій відповіді.
Тед Хопп

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. Я розумію це право?
ашмід

@aschmied Ви праві - результат набору аргументів першого виклику запам'ятовується та повертається для всіх інших викликів, незалежно від параметра, оскільки основна функція більше ніколи не викликається. У таких випадках я не пропоную використовувати _.onceметод. Дивіться jsfiddle.net/631tgc5f/1
asawyer

1
@aschmied Або я здогадуюсь використовувати окремий виклик один раз за набір аргументів. Я не думаю, що це насправді призначено для такого роду використання.
asawyer

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, його можна виконати, якщо є один або кілька елементів A, функцію неможливо виконати.

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

    // do stuff
}

1
Мені потрібно, щоб він працював як розумний вказівник, якщо немає елементів типу A, його можна виконати, якщо є один або кілька елементів A, функцію неможливо виконати.
vlio20

@VladIoffe Це не те, що ви просили.
Шміддті

Це не спрацює, якщо Onceвін передається як зворотній дзвінок (наприклад, setInterval(Once, 100)). Початкова функція буде продовжувати називатися.
Тед

2

спробуйте це

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

2

Від якогось чувака на ім’я Крокфорд ... :)

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

Варіація відповіді від Буника


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 або пишете 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

Якщо ви хочете мати можливість використовувати цю функцію в майбутньому, тоді це добре працює на основі коду Ед Хоппа, що був вище (я розумію, що оригінальне запитання не вимагало цієї додаткової функції!):

   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, ви можете використовувати функцію "один раз" .

Цитата з документації:

раз Функція (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 в () :

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.
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)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.