본문 바로가기

Story/Javascript

Self-Executing Anonymous Functions 익명함수

반응형

Self-Executing Anonymous Functions

When learning JavaScript, with all the attention given to variables, functions, ‘if’ statements, loops and event handlers, often little is done to educate you on how you might cleanly organise your code into a cohesive, structurally-sound whole.

Let’s take the following code for example:

1
2
3
4
5
6
7
8
var foo = 'Hello';
var bar = 'World!';

function baz(){
  return foo  + ' ' + bar;
}

console.log(baz());

This style of code looks quite normal, works fine and doesn’t cause any problems. At least for now.

This style of code, when implemented in a large application, can start to become an unwieldy mess. The global namespace becomes littered with functions and variables, all tenuously linked to each other through a combination of rudimentary comments and potentially unspoken developer knowledge.

The first step on the journey to beautiful, modular JavaScript is to learn the art of the self-executing anonymous function.

1
2
3
(function(){
  console.log('Hello World!');
})();

Let’s look at this carefully. This code is made up of two key parts.

First is the anonymous function:

1
2
3
(function(){
  //Normal code goes here
})

The really interesting part is what happens when we add this right at the end:

1
();

Those two little brackets cause everything contained in the preceding parentheses to be executed immediately. What’s useful here is that JavaScript has function level scoping. All variables and functions defined within the anonymous function aren’t available to the code outside of it, effectively using closure to seal itself from the outside world.

Let’s apply this design patten to our gloriously inane example code.

1
2
3
4
5
6
7
8
9
10
11
12
13
(function(){
  var foo = 'Hello';
  var bar = 'World!'
  
  function baz(){
      return foo + ' ' + bar;
  }
})();

 //These all throw exceptions:
console.log(foo);
console.log(bar);
console.log(baz());

The last three lines throw exceptions because currently nothing is accessible outside the anonymous function. To allow access to a variable or function, we need to expose it to the global ‘window’ object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(function(){
  var foo = 'Hello';
  var bar = 'World!'
  
  function baz(){
      return foo + ' ' + bar;
  }

  window.baz = baz; //Assign 'baz' to the global variable 'baz'...
})();

console.log(baz()); //...and now this works.

//It's important to note that these still won't work: 
console.log(foo);
console.log(bar);

One of the major benefits of this pattern, as seen on the last two lines of the previous example, is that you can limit access to variables and functions within your closure, essentially making them private and only choosing to expose an API of your choice to the global scope.

One popular spin on this design pattern, which can be seen in the jQuery source, is to pass in some commonly used objects. In our code we reference ‘window’, so let’s pass that in as a parameter to the anonymous function.

1
2
3
4
5
6
7
8
9
10
11
(function(window){
  var foo = 'Hello';
  var bar = 'World!'
  
  function baz(){
      return foo + ' ' + bar;
  }

  //In this context, 'window' refers to the parameter
  window.baz = baz;
})(window); //Pass in a reference to the global window object

When minifying your code, this design pattern will yield great results. All references to ‘window’ in your code can be renamed to ‘a’, for example:

1
2
3
(function(a){
  console.log(a === window); //Returns 'true'
})(window);

Normally you’ll want to pass in a few objects. A technique you can see used within jQuery itself is to reference an extra parameter that isn’t defined when the anonymous function is executed, in effect creating an alias for ‘undefined’:

1
2
3
4
(function(window, document, $, undefined){
  var foo;
  console.log(foo === undefined); //Returns 'true'
})(window, document, jQuery);

You may have noticed that the previous example also aliased ‘jQuery’ to ‘$’, allowing it to play nicely with other frameworks without having to use jQuery in noConflict mode.

It’s worth pointing out that the parameter names are purely for convention. The following code would work equally as well and serves as a great illustration of what’s really going on in this design pattern:

1
2
3
4
5
6
7
(function(mark, loves, drinking, coffee){
  mark.open('http://www.google.com'); //window
  loves.getElementById('menu'); //document
  drinking('#menu').hide(); //jQuery
  var foo;
  console.log(foo === coffee); //undefined
})(window, document, jQuery);

Although, for obvious reasons, I advise against this ;)

The benefits of this design pattern will become even more apparent in later posts. Harnessing the power of self-executing anonymous functions will allow you to create more complex but ultimately more intuitive code structures that will make your larger projects much easier to manage.

 

출처 : http://markdalgleish.com/2011/03/self-executing-anonymous-functions/

참고 : http://tb.chan.je/147

참고 : http://en.wikipedia.org/wiki/Immediately-invoked_function_expression

 

Javascript 상에서 private, public, static

언어적인 기능으로 보면 Javascript는 완벽하게 private, public, static을 구현한다. 게다가 구현 후에 작동방식도 거의 비슷하다.

1. private 프로퍼티, 메쏘드의 선언
     ClassA=function(){

          var a=30;

          var test=function(){

                return a;

          }

     }


위의 예처럼 클래스 내부에서 var를 통해 생성한 메쏘드 혹은 프로퍼티는 내부에서만 호출할 수 있는 private이 된다.

당연한 얘기지만 Javascript의 구조상 private로 선언된 프로퍼티나 메쏘드는 prototype을 통해 상속했든 소유로 상속했든 다른 객체에서 불러낼 수 없다. 따라서 프레임웍을 짤 작정으로 부모클래스를 만들 생각이라면 var로 선언해서는 안된다(자식이 전혀 이용할 수 없다)

2. public 프로퍼티, 메쏘드의 선언
     ClassA=function(){

          this.a=30;

          this.test=function(){

                return this.a;

          }

     }

public의 경우는 this. 키워드를 이용하면 손쉽게 구현된다. public는 외부에서 자유롭게 호출할 수 있을뿐만 아니라 상속한 자식객체에서도 불러쓸 수 있다. 자식객체가 public 프로퍼티나 메쏘드를 덮어쓰면 아무런 통지 없이 override된다. 따라서 public으로 선언된 모든 프로퍼티와 메쏘드는 override키워드가 들어가 있는 것이나 다름없다.

 

3. static 프로퍼티, 메쏘드의 선언
     ClassA=function(){

     }

     ClassA.a=30;

     ClassA.test=function(){

           return ClassA.a;

     }

클래스 선언 외부에서 프로퍼티와 메쏘드를 추가하게 되면 이는 Static이 된다. 다른 OOP언어들과 마찬가지로 static메쏘드 내에는 오직 static프로퍼티나 전역속성의 값만 포함할 수 있다.

4. 사용시 이슈가 되는 부분


    - private을 상속시키고 싶을 때
       private는 오직 해당 클래스 내부에서만 유효하다. 따라서 자식객체도 사용할 수 없다(이건 바꿀 수 없는 현실..)
       여기에 대해 나는 '__' 이름규칙을 암묵적으로 사용하고 있다.
 

       ClassA=function(){

            this.__a=30;

            this.__test=function(){

                  return this.__a;

            }

       }
 

       선언 자체야 public이 되어 당연히 상속되고 호출도 가능하지만 '__'라는 이름 제약때문에 실수로 호출하지 않는다.

     - private프로퍼티와 public 메쏘드의 조합

       이건 처음부터 아무런 문제없이 동작함으로 걱정없이 아래와같이 사용하면 된다. 하지만 상속이 되지 않는건 언제나 주의!
       ClassA=function(){

            var a=30;

            this.test=function(){

                  return a;

            }

       }
       예컨데 classA에서는 전혀 문제없지만 classB.prototype=new ClassA(); 한 경우 test()를 하게 되면 a가 존재하지 않아 에러!

 

     - 부모의 public 속성이 자식에게 공유되는 문제

       매우 심각한 문제로 반드시!! 회피방안을 생각해둬야한다.
       내장객체, 사용자정의객체가 프로퍼티에 할당되면 값이 아니라 레퍼런스(포인터)가 잡혀 별도의 자식 instance에서 공유하게 된다.
       ClassA=function(){

            this.a=new Array();

       }

       ClassB=function(){

            this.set=function(val){

                   this.a.push(val)

            }

            this.get=function(){

                   return this.a.length;

            }

       }

       ClassB.prototype=new ClassA();
       var test1=new ClassB();

       var test2=new ClassB();

       test1.set();
       alert(test2.get()); // 1을 반환함
       위 소스를 보면 classA에서 선언된 a값엔 Array객체가 들어있는데 자식객체인 ClassB에서는 별도로 선언한 적이 없기 때문에

       ClassB의 인스턴스인 test1,test2에서 a가 잡고 있는 동일한 Array객체를 참조하게된다.
       결과적으로 test1쪽의 a에 push를 했음에도 불구하고 자동으로 같은 객체를 가르치고 있는 test2의 a도 갱신된 꼴이 된다.

       * 해법1 : 객체가 프로퍼티의 값으로 올 때는 반드시 초기화 함수를 부모객체가 지원해야한다. 이것을 보다 일반화하여 init등의
                    이름으로 통일해두면 좋다. 위의 샘플의 일반적인 해법은 아래와 같다.
       ClassA=function(){

            this.initA=function(){

                   this.a=new Array();

            }

       }

       ClassB=function(){

            this.initA();

            this.set=function(val){

                   this.a.push(val)

            }

            this.get=function(){

                   return this.a.length;

            }

       }

       ClassB.prototype=new ClassA();
       이와 같은 선언을 하면 ClassB가 생성시에 명시적으로 ClassA의 속성을 지정하기 때문에 중복문제가 제거된다.
       * 해법2 : 보다 근원적인 해법은 자식객체가 명시적으로 부모생성자를 호출하는 것이다. call와 apply메쏘드를 통해 기본 지원된다.
       ClassA=function(){

            this.a=new Array();

       }

       ClassB=function(){

            ClassA.call(this);

            this.set=function(val){

                   this.a.push(val)

            }

            this.get=function(){

                   return this.a.length;

            }

       }

       ClassB.prototype=new ClassA();
       훨씬 깔끔하기도 하고 매우 일반적이기도 하다. call과 apply에 대해서는 따로 자세히 쓰지 않겠다.

 

출처 : http://blog.naver.com/hika00/150029057576

 

 

 

자바스크립트의 함수는 정말 유연하며 강력한 기능을 가지고 있습니다. 그런데 다소 독특한 함수의 쓰임새가 혼동을 주는 경우도 있습니다. 하지만 함수의 사용방법과 활용범위를 알게되면 즐거운 자바스크립트 개발을 할 수 있게 됩니다. 자바스크립트의 강력한 힘을 보여주는 함수에 대해서, 너무 길면 복잡하니까 재미있는 것 위주로 간단하게 알아보도록 하겠습니다.

  • 함수를 만드는 방법 (function declarations and function expressions)
  • 함수의 인자 (arguments)
  • 함수와 메서드 (functions and methods)

함수를 만드는 방법

함수는 함수선언(function declaration)과 함수표현식(function expression)이라는 두 가지 방식으로 만들 수 있습니다. 둘 모두 function 키워드를 사용하지만 작성 방법이 약간 다릅니다.

함수선언

변수를 선언할 때 var 키워드를 쓰고 한칸 띄고 변수명을 적어주는 것과 거의 유사합니다. 함수를 선언할 때에는 function 키워드를 써주고 한칸 띄고 함수명을 써주고 괄호를 열고 닫고 ( ) 그 안에 인자를 넣어주고 중괄호를 열고 닫고 { } 그 안에 함수에서 실행할 문장들을 넣어주면 됩니다.

매개변수(parameter)와 인자(argument)의 차이점

많은 개발자들이 매개변수라는 단어와 인자라는 단어를 거의 같은 의미로 혼용해서 사용하고 있습니다. 이 두 용어는 비슷하긴 하지만 차이점을 가지고 있는데요 자세한 내용은 여기를 참고해주십시오

이 글에서는 편의상 인자로 통일해서 사용하도록 하겠습니다.

  1. // 함수선언
  2. function test() {
  3.     // 여기에 함수에서 실행할 문장을 넣습니다.
  4. }
  5. // 함수호출은 이렇게  
  6. test();  

함수표현식

함수표현식은 함수명없이 function 키워드만 사용한 함수 자체를 의미합니다. 이런 형태를 이름없는 함수, 익명함수(anonymous function)라고 합니다.

  1. // 함수표현식  
  2. function() {  
  3.     // 여기에 함수에서 실행할 문장을 넣습니다.  
  4. }  

그런데 위와 같은 함수표현식은 함수를 만들기는 했지만 호출해서 실행을 할 수가 없는 형태입니다. 이름을 불러서 호출하고 싶은데 이름이 없으니 부르지 못하는 것입니다. 하지만 다른 곳에서 불러서 호출할 수 없는 대신 함수를 만들자마자 그 자리에서 실행시킬 수 있습니다.

  1. // 함수를 실행시키려면 함수명 뒤에 ()를 붙이면 됩니다.  
  2. // 아래의 함수는 이름이 없지만 함수 바로 뒤에 ()를 붙이면 그자리에서 실행시킬 수 있습니다.  
  3.  
  4. function() {  
  5.     // 여기에 함수에서 실행할 문장을 넣습니다.  
  6. }();    // <- 이렇게 실행시킵니다.  
  7.  
  8.  
  9. // 위의 코드는 보기에 조금 헷갈릴 수가 있기 때문에 함수정의 부분을 ()로 감싸주면 좋습니다.  
  10.  
  11. (function() {  
  12.     // 여기에 함수에서 실행할 문장을 넣습니다.  
  13. })();  

위와 같이 작성되자 마자 바로 실행되는 익명함수를 self-executing anonymous function 또는 self-invoking anonymous function 이라고 합니다. 매우 유용하고 많이 사용되는(특히 closure를 만들 수 있어서) 패턴입니다.

그런데 함수표현식으로 만든 함수를 위와 같이 익명함수로만 사용할 수 있는 것은 아닙니다. 아래처럼 변수를 만들고 함수의 참조를 변수에 넣어주면 됩니다.

  1. // 함수표현식을 test라는 변수에 넣어주면 test는 이 함수에 대한 참조를 가지게 됩니다.  
  2. var test = function() {  
  3.     // 여기에 함수에서 실행할 문장을 넣습니다.  
  4. };  
  5.  
  6. // 함수호출은 이렇게.. 함수선언으로 함수를 만들었을때와 호출방법이 같습니다.  
  7. test();  

위 코드의 4라인의 맨 끝에 세미콜론 ; 을 넣은 것을 유의해 주십시오. 함수선언을 했을때는 함수가 끝나는 } 다음에 세미콜론을 안붙여줘도 되지만 함수표현식으로 만들고 변수에 담을때는 문장의 끝이 되므로 세미콜론을 꼭 넣어줘야 합니다.

함수의 인자(arguments)

함수를 만들때 함수에서 받을 인자의 개수를 정해주고, 함수를 호출할때에도 그 인자의 개수를 맞춰서 호출하는 것이 보통입니다. 그런데 자바스크립트는 이 부분이 자유스러워서 인자를 여러개 받도록 정의된 함수에 인자를 하나도 안넘기고 호출하거나, 인자를 받지 않는 것으로 정의된 함수에 인자를 넣어서 호출해도 에러가 발생하지 않습니다. 물론 그렇게 할 경우 함수를 정의할 때 원했던 기능이 제대로 실행되지는 않겠지만 문법적인 에러는 발생하지 않습니다.

  1. // 인자를 세 개 받아서 차례대로 alert 하는 함수  
  2. function test(arg1, arg2, arg3) {  
  3.      
  4.     alert(arg1);  
  5.     alert(arg2);  
  6.     alert(arg3);  
  7.      
  8. }  
  9.  
  10. // 인자를 하나도 안넣고 호출  
  11. test();     // undefined가 세 번 출력됨  
  12.             // 인자가 넘어오지 않아서 test 함수 안에서 arg1, arg2, arg3의 값은  
  13.             // undefined가 됨. undefined 세 개를 더해서 결과 값은 NaN이 됨  
  14.              
  15. // 인자를 두 개 넣고 호출  
  16. test(1, 2);     // 1, 2, undefined가 출력됨  

위의 코드에서 보시는 것과 같이 함수에서 받기로 했던 인자가 호출할 때 제대로 넘어오지 않으면 그 값은 undefined가 됩니다. 반대의 경우로, 함수를 정의할 때 인자를 설정하지 않으면 함수를 호출할 때 인자를 많이 넣어서 호출해도 함수에서 사용할 수 없을 것입니다.

  1. function test() {  
  2.      
  3. }  
  4.  
  5. // 함수를 호출해도 아무 일도 일어나지 않음. 에러도 발생안함.  
  6. test(1, 2, 3);  

그런데 자바스크립트에는 arguments라는 내장객체가 있어서(Function 객체의 프로퍼티입니다.) 매우 편리하게 사용할 수 있습니다. 이 객체에는 함수가 받은 인자들이 배열형태로 저장되어 함수안에서 사용할 수가 있습니다.(자바스크립트의 배열이 가지고 있는 메서드나 프로퍼티를 사용할 수 없으므로 진짜 자바스크립트 배열은 아닙니다.) arguments 객체는 length 프로퍼티를 가지고 있는데 거기에는 넘겨받은 인자의 개수가 들어있습니다.

  1. function test() {  
  2.      
  3.     alert(arguments.length);    // 3 -> 넘겨받은 인자의 개수  
  4.     alert(arguments[0]);        // 1 -> 첫 번째 인자값  
  5.     alert(arguments[1]);        // 2 -> 두 번째 인자값  
  6.     alert(arguments[2]);        // 3 -> 세 번째 인자값  
  7.      
  8. }  
  9.  
  10. test(1, 2, 3);      // 차례대로 3,1,2,3을 alert 함  

arguments 객체를 사용하면 호출할 때마다 인자의 개수가 달라지는 함수를 작성할 수 있어서 유연한 프로그래밍을 가능하게 해줍니다.

함수와 메서드 (functions and methods)

함수가 어떤 객체의 프로퍼티일 때 함수를 그 객체의 메서드라고 합니다. 경우에 따라 부르는 명칭이 다른 것이기 때문에 함수와 메서드의 내용은 똑같습니다. 그래서 지금까지 함수의 특징으로 말씀드린 모든 내용은 메서드도 마찬가지로 적용되는 것입니다. 이와같이 객체의 프로퍼티 형태로 있는 함수를 메서드라고 부를때, 사실 자바스크립트에서 모든 함수는 메서드라고 할 수 있습니다. 변수, 함수와 같은 자바스크립트의 모든 구성요소는 전역객체(global object)의 프로퍼티입니다. 자바스크립트가 웹브라우저에서 실핼될 때 전역객체는 window 입니다. 따라서 모든 변수와 함수는 window 객체의 프로퍼티입니다.

이 내용은 사용자가 새로 만든 객체에도 모두 적용됩니다. 전역범위(global scope)에서 아무 객체나 함수나 변수를 만들면 모두 window 객체의 프로퍼티가 됩니다. 이 글에서 계속 만들고 있는 test 함수를 호출할 때 test() 이런 식으로 하고 있는데 test 함수는 전역범위에서 만든 함수이기 때문에 전역객체인 window 의 프로퍼티가 됩니다. 그래서 window.test() 이런 식으로 호출 할 수 있습니다. 함수가 객체의 프로퍼티일 때 메서드라고 하므로 test 함수는 window 객체의 메서드가 되는 것입니다.

객체의 메서드를 실행하려면 "객체명.메서드명()" 이런 식으로, 그러니까 위의 경우는 window.test() 라고 해야 실행이 되는게 맞는데 여태까지 그냥 test() 로 실행했어도 에러가 발생하지 않았던 이유는 무엇일까요? 그것은 전역객체 window의 경우에는 프로퍼티나 메서드를 호출할 때 window. 을 생략하는 것이 가능하기 때문입니다. 그래서 alert()나 confirm()과 같은 함수들도 그냥 함수명만 가지고 호출을 할 수 있는 것입니다. 이것들은 모두 window 객체의 프로퍼티(함수인 프로퍼티이기 때문에 메서드)이므로 window.alert(), window.confirm() 으로 호출해도 됩니다.

이렇게 자바스크립트의 모든 함수들은 어떤 객체의 프로퍼티인 형태, 즉 메서드입니다. 그리고 그 객체는 메서드의 context가 됩니다. 그러니까 alert()나 confirm()과 같은 함수, 그리고 위에서 만든 test() 함수의 context는 모두 전역객체인 window 입니다. 자바스크립트로 개발을 할 때 메서드의 context를 정확히 파악하는 것은 매우 중요합니다. 메서드 안에서 this 키워드는 그 메서드의 context를 가리키는 예약어이고 이 this 키워드를 사용해서 유용한 기능을 가진 메서드를 많이 개발할 수 있습니다.

자바스크립트의 모든 함수는 call과 apply라는 메서드를 가지고 있습니다. 자바스크립트 내장 함수 뿐 아니라 개발자가 그때그때 만드는 함수들도 모두 가지고 있습니다. call과 apply의 역할은 메서드의 context를 다른 객체로 변경해 주는 것입니다. 그래서 어떤 객체에 어떤 메서드를 만들었다고 해서 그 메서드를 그 객체에서만 사용할 수 있는 것은 아닙니다. 다른 객체에서도 그 메서드를 자신의 메서드인 것처럼 사용할 수 있는데 이런 것을 context binding 이라고 합니다. call과 apply가 context binding을 할 수 있게 해주는 것입니다.

반응형