한 번만 호출 할 수있는 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 (현재 삭제됨)의 주석에 대한 답변 : 전역 변수를 사용하면 다른 코드에서 "executed"플래그의 값을 재설정 할 수 있습니다 (선택한 이름에 관계없이). 클로저를 사용하면 다른 코드는 실수로 또는 고의로 그렇게 할 수 없습니다.

여기에서 다른 답변이 지적했듯이 여러 라이브러리 (예 : UnderscoreRamda )에는 함수를 인수로 받아들이고 제공된 함수를 정확히 한 번 호출하는 다른 함수를 반환 하는 작은 유틸리티 함수 (일반적으로 once()[*] )가 있습니다. 반환 된 함수가 여러 번 호출됩니다. 반환 된 함수는 제공된 함수에서 처음 반환 된 값도 캐시하고 후속 호출에서 반환합니다.

그러나 이러한 타사 라이브러리를 사용하지 않지만 위에서 제공 한 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 execute = false; 작품
Amr.Ayoub

@EgyCode-이것은 클로저 에 대한 MDN 문서에 잘 설명되어 있습니다.
Ted Hopp

죄송합니다. 논리를 의미했습니다. 부울 var가 어떻게 작동하는지 이해하지 못했습니다.이 경우 실행되는 방식
Amr.Ayoub

1
@EgyCode-특정 컨텍스트 ( if문 테스트 표현식 에서와 같이 )에서 JavaScript는 값 중 하나를 예상 true하거나 false표현식이 평가 될 때 찾은 값에 따라 프로그램 흐름이 반응합니다. ==항상 같은 조건부 연산자 는 부울 값으로 평가됩니다. 변수는 true또는을 포함 할 수도 있습니다 false. (자세한 내용은 boolean , truthyfalsey 에 대한 설명서를 참조하십시오 .)
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 : 어떻게이게 우아하지 않나요? 다시 말하지만, 매우 깨끗하고 코드가 덜 필요하며 비활성화해야하는 모든 기능에 대해 새 변수가 필요하지 않습니다. "무 조작은" 상황이 이런 종류에 대해 정확히 설계되었습니다.
I Hate Lazy

1
@fableal : 방금 hakra의 대답을 보았습니다. 그래서 새로운 함수에 이것을 할 필요가있을 때마다 새로운 클로저와 변수를 만드시겠습니까? 당신은 "우아함"에 대한 아주 재미있는 정의를 가지고 있습니다.
I Hate Lazy

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와 같은 매우 동적 인 언어의 정신에 훨씬 더 가깝습니다. 사용 된 함수를 간단히 비울 수 있는데 왜 세마포어를 설정합니까?
이반 Čurdinjaković

아주 좋은 해결책! 이 솔루션은 또한 클로저 접근 방식보다 성능이 좋습니다. 유일한 사소한 "결점"은 이름이 변경 될 경우 함수 이름을 동기화 상태로 유지해야한다는 것입니다.
Lionel

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. 나는 이것을 이해하고 있는가?
aschmied

@aschmied 맞습니다-첫 번째 호출의 인수 집합의 결과는 기본 함수가 다시 호출되지 않으므로 매개 변수에 관계없이 다른 모든 호출에 대해 memomized 및 반환됩니다. 그런 경우에는 _.once방법을 사용하지 않는 것이 좋습니다 . jsfiddle.net/631tgc5f/1
asawyer 16. 9.

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 당신이 요청한 것이 아닙니다.
Shmiddty 2010 년

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훌륭합니다. 이것이 반환 된 함수를 두 번째로 호출 할 때 얻는 것입니다.
Ted Hopp

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

Bunyk답변 변형


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를 사용하거나 browserify로 JavaScript를 작성하는 경우 "once"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 () 메서드를 사용하여 함수를 한 번만 호출 할 수 있습니다 .

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로 설정되지만 다음 번에 함수를 계속 호출 할 수 있습니다.
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)
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.