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

116

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

vlio20
источник
~ 8 лет спустя я создал запрос функции для моей библиотеки декораторов ( 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 (теперь удален): с помощью глобальной переменной другой код может сбросить значение флага «выполнено» (какое бы имя вы для него не выбрали). С закрытием другой код не имеет возможности сделать это случайно или намеренно.

Как указывают другие ответы здесь, несколько библиотек (таких как 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 по закрытию .
Тед Хопп
извините, я имел в виду логику, я никогда не понимал логическую переменную и как она работает в этом случае выполняется
Амр. Айуб
1
@EgyCode - в определенных контекстах (например, в ifтестовом выражении оператора) JavaScript ожидает одно из значений trueили, falseи поток программы реагирует в соответствии со значением, найденным при оценке выражения. Условные операторы, такие как ==always, возвращают логическое значение. Переменная также может содержать либо trueили false. (Для получения дополнительной информации см. Документацию по логическим , правдивым и ложным .)
Тед Хопп
Я не понимаю, почему выполняется не сбрасывается при каждом новом вызове. Кто-нибудь может объяснить это, пожалуйста?
Хуанс Кора 08
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» предназначен именно для такого рода ситуации.
I Hate Lazy
1
@fableal: Я только что посмотрел на ответ Хакры. Так что создавайте новое закрытие и переменную каждый раз, когда вам нужно делать это с новой функцией? У вас есть очень забавное определение «элегантный».
I Hate Lazy
2
Согласно ответу asawyer вам нужно было выполнить только _.once (foo) или _.once (bar), а сами функции не должны знать, что они запускаются только один раз (нет необходимости в noop и нет необходимости в * = нет).
fableal 03
7
Не совсем лучшее решение. Если вы передаете эту функцию как обратный вызов, ее все равно можно вызывать несколько раз. Например: setInterval(foo, 1000)- и это уже не работает. Вы просто перезаписываете ссылку в текущей области.
кот
1
Многоразовая invalidateфункция, которая работает с и setIntervalт. Д
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)

VSync
источник
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;
    };
  };
asawyer
источник
1
Мне кажется забавным onceпринимать аргументы. Вы можете сделать squareo = _.once(square); console.log(squareo(1)); console.log(squareo(2));и получить 1за оба звонка squareo. Я правильно понимаю?
добавлено
@aschmied Вы правы - результат набора аргументов первого вызова будет запомнен и возвращен для всех остальных вызовов независимо от параметра, поскольку основная функция никогда не вызывается снова. В подобных случаях я не предлагаю использовать этот _.onceметод. См. Jsfiddle.net/631tgc5f/1
asawyer 09
1
@aschmied Или я предполагаю использовать отдельный вызов один раз для каждого набора аргументов. Я не думаю, что это действительно предназначено для такого рода использования.
asawyer 09
1
Удобно, если вы уже используете _; Я бы не рекомендовал полагаться на всю библиотеку для такого небольшого фрагмента кода.
Джо Шанахан
11

Говоря о статических переменных, это немного похоже на вариант закрытия:

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

once(); once(); 

Затем вы можете сбросить функцию, если хотите:

once.done = false;
Bunyk
источник
4
var quit = false;

function something() {
    if(quit) {
       return;
    } 
    quit = true;
    ... other code....
}
Диодей - Джеймс Макфарлейн
источник
См. Ответ Теда Хоппа. Это способ определения переменных на уровне функций.
Диодеус - Джеймс Макфарлейн
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
}
Shmiddty
источник
1
Мне нужно, чтобы он работал как умный указатель, если нет элементов типа A, он может быть выполнен, если есть один или несколько элементов A, функция не может быть выполнена.
vlio20 03
@VladIoffe Это не то, о чем вы спрашивали.
Шмиддти, 03
Это не сработает, если Onceпередается как обратный вызов (например, setInterval(Once, 100)). Исходная функция будет продолжать вызываться.
Тед Хопп
2

попробуй это

var fun = (function() {
  var called = false;
  return function() {
    if (!called) {
      console.log("I  called");
      called = true;
    }
  }
})()
Database_Query
источник
2

От какого-то чувака по имени Крокфорд ... :)

function once(func) {
    return function () {
        var f = func;
        func = null;
        return f.apply(
            this,
            arguments
        );
    };
}
Jowen
источник
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

Вариант ответа от Буник

Q20
источник
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);
    });
}

И запустится только один раз :)

Aldekein
источник
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;
    };
}
andlrc
источник
1

Если вы используете Node.js или пишете JavaScript с помощью browserify, подумайте о модуле npm "Once" :

var once = require('once')

function load (file, cb) {
  cb = once(cb)
  loader.load('file')
  loader.once('load', cb)
  loader.once('error', cb)
}
orcaman
источник
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!
CMP
источник
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
Pery Mimon
источник
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; */
RegarBoy
источник
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 = _=>{};
}

Вы можете увидеть результат

результат сценария

Shreeketh K
источник
Если вы находитесь внутри модуля, просто используйте 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 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;
  };
};
ATW
источник
Плохая практика - загрязнять глобальную область видимости (также
известную
Я согласен с вами, но кто-то может что-то получить от этого.
atw
Это не работает. Когда этот код выполняется впервые, создается функция. Затем, когда функция вызывается, она выполняется, и для глобального значения устанавливается значение false, но функция все еще может быть вызвана в следующий раз.
trincot
Он нигде не установлен на 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)
}
Глеб Долзиков
источник