이번에는 조금 복잡한 이야기를 해볼까요?


  자바스크립트 this 키워드에 대한 설명은 함수 파트에서 함께 진행하려고 하였습니다만 this를 통해 곤란해 하는 분들이 많아 앞당겨 진행하겠습니다. 이번에 드리는 설명을 올바르게 이해하기 위해서는 자바스크립트의 함수, 그리고 객체에 대한 정확한 이해가 밑받침 되어야합니다.


  자바스크립트는 가볍게 쓰는 경향이 있기 때문에 메커니즘을 착각하기도 쉽습니다. 자바스크립트에서 this의 본질은 여타언어 C++, Java등과 다르지 않습니다. this는 메서드를 호출한 호출객체를 가르킵니다. 이를테면


  // 자바스크립트의 객체 생성방법 중 하나
  var people = new Object();
  people.name = "thinkberry";
  people.displayName = function() { alert(this.name); }
  
  peolpe.displayName();
  

  위의 코드를 실행하게 되면 thinkberry가 뜨게 되죠. this는 곧 메서드를 호출한 객체 즉 자신을 가리키게 됩니다. 이것은 어느 언어나 마찬가지입니다. 하지만 자바스크립트는 함수호출객체, 데이터 타입으로서의 함수, 중첩함수, 어휘적 유효범위, HTML의 엘리먼트속에서 this가 사용되는 과정에 사용자로 하여금 혼란에 빠트리게 됩니다. 여러분이 this를 완벽하게 이해하려면 아래의 코드에서 출력결과를 예측해보시기 바랍니다. 




var scope = "global";


function scopeCheck() { 

var scope = "outterLocal";

alert("(1) "+this.scope); // 출력결과 예상


var object = new Object();

object.scope = "field";

object.scopeCheck = function() { alert("(2) "+this.scope); } 

object.scopeCheck(); // 출력결과 예상

function inner() { 

var scope = "innerLocal";

alert("(3) "+this.scope); 

}

inner(); // 출력결과 예상

var anotherObject = new Object();

anotherObject.scope = "anotherField";

anotherObject.scopeCheck = function() { alert("(4) "+this.scope); } 

object.scopeCheck = anotherObject.scopeCheck;

object.scopeCheck(); // 출력결과 예상
}

scopeCheck();


  

  제가 자바스크립트 과목의 교수였다면 위와 같은 문제를 냈을 겁니다. 자바스크립트를 가리키는 강의가 있기나 할진 모르겟지만.. 위 코드를 올바르게 이해한다면 this 키워드를 거의 완벽하게 이해했다고 말할 수 있습니다.

  정답은 (1) global   (2) field   (3) global   (4) field 입니다.

  그럼 해설을 해볼까요?
  


(1)

        var scope = "global";


function scopeCheck() { 

var scope = "outterLocal";

alert("(1) "+this.scope); // global


  자바스크립트는 자신을 호출한 객체를 가르킬 때 this 키워드를 사용합니다. 하지만 호출한 (명시적)객체가 없다면 호출스택을 거슬러 올라가며 가장 가까운 (명시적)객체를 찾습니다. 계속 거슬러 올라가도 (명시적)객체가 없다면 this키워드는 결국 최상단의 전역 객체를 가르키게 됩니다. 브라우저 측 자바스크립트에서는 보통 window 객체인데 이는 곧 전역을 의미합니다. 때문에 전역 변수 scope의 값인 global을 가르키게 되죠. 쉽게 이해하자면 this 키워드가 참조할 객체가 없다면 전역범위를 가리킨다고 생각하시면 되겠습니다.

  자바스크립트는 모든 호출이 객체를 통해 이루어지는 메커니즘입니다. 이를 잘 몰라도 자바스크립트를 작성하는데 문제는 없으니 시간낭비라고 생각되시면 이 설명을 건너뛰어도 좋습니다. scopeCheck라는 함수를 호출하면 사실은 묵시적 객체가 생성됩니다. 이 객체의 목적은 오직 해당 함수를 호출하기 위한 묵시적 객체입니다. 함수 내에 선언된 지역변수들은 묵시적 객체의 지역변수가 되고 호출된 함수 그 자체는 묵시적 객체의 멤버 함수로 할당되면서 함수가 시작되게 됩니다. 안타깝게도 묵시적 객체는 코드 상에서 접근할 수 있는 방법이 없는 메커니즘 상의 객체 입니다. 이와 반대로 사용자가 직접 new 키워드를 사용하여 생성한 객체가 있습니다. 이를 명시적 객체라고 하겠습니다. 명시적 객체는 얼마든지 코드 상에서 접근할 수 있습니다. 자바스크립트에서 this 키워드는 기본적으로 묵시적 객체를 참조하지 않습니다. 사용자가 new 키워드를 통해 생성한 명시적 객체만 참조하죠. 하지만 아무리 거슬러 올라가도 명시적 객체가 없다면 결국 최상단의 객체인 컨텍스트 객체(브라우저상에서는 Window 객체)를 참조하게 됩니다.




(2)

        var object = new Object();

object.scope = "field";

object.scopeCheck = function() { alert("(2) "+this.scope); } 

object.scopeCheck(); // 출력결과 예상



  이것은 전형적인 멤버함수의 호출입니다. 따라서 자연스럽게 객체의 멤버변수 scope의 값인 field를 출력하게 됩니다. 명시적 객체를 this가 바로 찾을 수 있기 때문에 global을 출력할 일이 없겠죠?






(3)
var scope = "global";

function scopeCheck() { 

var scope = "outterLocal";

function inner() { 

var scope = "innerLocal";

alert("(3) "+this.scope); 

}

}

inner(); // 출력결과 예상


  이것은 전형적인 중첩함수 nested function 입니다. 자바스크립트에서 중첩함수를 쓸 일은 생각보다 많은데, 대부분 어휘적 유효범위를 구현하기 위해서 그렇습니다. 자바스크립트 1.5는 올바른 객체지향을 지원하지 않기 때문에 클로져가 아주 유용하게 사용되죠. 어휘적 유효범위와 클로져는함수 파트에서 알아볼 것입니다. 중첩함수내의 this키워드는 명시적 객체를 찾아 거슬러 올라갑니다. inner()를 호출한 객체는... 묵시적이군 패스, scopeCheck()를 호출한 객체는... 묵시적이군 패스, 아 더이상 거슬러 올라갈 수 없구나 전역객체를 참조해야겠군. 결국 global을 출력하게 되는 것이죠. 이런 중첩함수를 만나더라도 this 키워드는 명시적 '객체'만을 참조한다는 것을 기억하면 헤깔릴 일은 없을 것입니다. 혹시 innerLocal이나 outterLocal이 출력된다고 생각했다면 다시 생각해보세요!




(4)

  

var object = new Object();

object.scope = "field";

object.scopeCheck = function() { alert("(2) "+this.scope); } 


var anotherObject = new Object();

anotherObject.scope = "anotherField";

anotherObject.scopeCheck = function() { alert("(4) "+this.scope); } 

object.scopeCheck = anotherObject.scopeCheck;

object.scopeCheck(); // 출력결과 예상

  이것은 데이터 타입으로서의 함수가 관건입니다. 

object.scopeCheck = anotherObject.scopeCheck; 

이 부분에서 anotherObject의 멤버 함수가 object의 멤버함수로 할당되고 있습니다. anotherObject의 scopeCheck함수는 사실 무명함수에 대한 참조를 가지고 있을 뿐입니다. 따라서자바스크립트에서 함수는 호출 객체와 독립적으로 존재하고 있는 것입니다! 그저 둥둥 떠있을 뿐이죠!  anotherObject.scopeCheck의 함수 참조가 object.scopeCheck에 할당되었습니다. object에서 호출된 함수는 object의 것처럼 작동합니다. 따라서 최초에는 anotherObject에 할당된 함수 였지만 scopeCheck 함수상의 this키워드는 object를 가리켜 'another field'가 아닌 'field'를 출력하게 됩니다.


위의 예제를 통해 this로 발생되는 의문을 거의 모두 해결하셨으리라 생각되는군요. 이런 딱딱한 어체는 당분간 안쓰려고 했는데 오늘만큼은 봐주세요.

http://thinkberry.co.kr/textyle/728

'js' 카테고리의 다른 글

클로져(Closures)  (0) 2014.11.03
함수(function) 다시 보기  (0) 2014.11.03
다차원 배열 정렬 (multiple array sort)  (0) 2014.10.30
JSTL core : <c:forEach> 사용법과 varStatus 상태값  (0) 2014.10.19
Posted by 조신부리
,

클로져(Closures)

js 2014. 11. 3. 11:24

자바스크립트를 배우려는 사람들에게 클로져는 어렵게 느껴지지만 자바스크립트를 깊게 알기 위해서 반드시 넘어야할 산이다.

다음 함수를 생각해보자.

function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();

init() 함수는 name 이라는 지역변수를 만들고 displayName() 이라는 함수를 정의한다. displayName() 은 내부함수라고 불리는데 이는 함수 init() 안에 정의되었고 init() 함수 안에서만 사용할 수 있기 때문이다. displayName() 함수는 지역변수를 가지지 않지만 외부에서 정의된 name변수를 사용하고 있다.

코드를 한번 실행해보라. 잘 동작할 것이다. 이 예제는 함수 스코핑(functional scoping) 을 보여주기 위해 소개했다. 자바스크립트에서 중첩된 함수는 그 함수 외부에서 정의된 변수를 사용할 수 있다. 

다른 예제를 보자.

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

이 예제를 실행해 보면 위의 예제 init() 함수와 동일한 결과를 보이는걸 알 수 있다(알람창에 "Mozilla" 문자열이 보일 것이다). 위 예제와 다른 점은 외부함수의 리턴 값이 내부함수 displayName() 라는 것이다. 흥미롭지 않은가?

이 코드가 문제없이 실행되는 것은 직관적이지 않다. 일반적으로 함수안에 정의된 지역변수는 함수가 종료되기 전까지만 존재한다. makeFunc() 함수가 종료될 때 이 함수 내부에 정의된 지역변수는 없어지는게 상식적이다. 이 코드가 문제없이 동작하는 걸 보면 다른 일이 일어나고 있는 것 같다!

이 퍼즐에 대한 해답은  myFunc 함수가 클로져(closure) 를 갖는다는 것이다. 클로져는 두 개의 것으로 이루어진 특별한 오브젝트이다. 첫 번째는 함수이고 두 번째는 그 함수가 만들어진 환경이다. 그 함수가 만들어진 환경은 함수가 만들어질 때 사용할 수 있었던 변수들로 이루어진다. 이 경우에 myFunc 는 displayName 함수와 "Mozilla" 문자열을 포함하는 클로져이다.

조금 더 흥미로운 예제를 보자. makeAdder 라는 함수이다.

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

print(add5(2));  // 7
print(add10(2)); // 12

이 예제에서 makeAdder(x) 라고 하는 하나의 인자를 받는 함수를 만들었다. 이 함수는 x라는 인자를 받아서 새로운 함수를 반환한다. 반환하는 함수는 y라는 인자를 받아서 x+y를 돌려주는 함수이다.

makeAdder 는 함수 공장(function factory)이다. 특정한 수를 인자에 더해서 돌려주는 함수들을 '찍어낸다'. 위의 예제에서 두개의 함수를 찍어냈다. 첫째는 인자에 5를 더하는 함수이고 둘째는 인자에 10을 더하는 함수이다.

add5 와 add10 는 둘다 클로져이다. 두 함수는 같은 정의를 가지지만 다른 환경을 저장한다. add5의 환경에서 x는 5이지만 add10 의 환경에서 x는 10이다.

실용적인 클로져

이제까지는 이론이었다. 클로져는 실용적인가? 이제는 실용적인 사용 방법을 알아보자. 어떤 데이터(환경)와 함수를 연관시키는데 클로져를 사용할 수 있다. 이건 객체지향 프로그래밍과 유사하다. 객체지향 프로그래밍에서는 객체가 데이터(그 객체의 속성)와 하나 이상의 메쏘드를 연관시킨다.

결론적으로 함수에서 오브젝트를 사용하려고 할 때 클로져를 사용할 수 있다.

웹 프로그래밍에서 이런 일이 많이 일어난다. 많은 자바스크립트 코드가 이벤트를 기반으로 짜여진다. (특정한 동작을 만들고 클릭이나 키보드 누르기에 이 동작을 연결시킨다) 이벤트에 반응하는 코드를 만든다고 할 수 있겠다. 이런 코드들을 콜백(callback)이라고 부른다.

여기에 실용적인 예제가 있다. 페이지의 글자 크기를 조정하는 몇 개의 버튼을 만든다고 생각해보자. body 엘리먼트에 px단위로 font-size를 설정하고 다른 엘리먼트에서는 상대적인 em 단위로 font-size를 설정하면 되겠다.

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}
h2 {
  font-size: 1.2em;
}

이제 body 엘리먼트의 font-size만 바꾸면 font-size가 em단위로 설정된 다른 엘리먼트들의 글자 크기도 바뀔 것이다.

자바스크립트 코드이다.

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12size14, size16 은 body 엘리먼트의 글자 크기를 각각 12, 14, 16 픽셀로 바꾸는 함수이다. 이제 이 함수를 버튼과 연결시키자.

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>

JSFIDDLE에서보기

 

클로져를 이용해서 private 함수 흉내내기

몇몇 언어(예를들어 자바)는 같은 클래스 내부의 메쏘드에서만 호출할 수 있는 private 메쏘드를 지원한다.

자바스크립트는 이를 지원하지 않지만 클로져를 이용해서 흉내낼 수 있다. private 함수는 코드에 제한적인 접근만을 허용한다는 점 뿐만 아니라 전역 네임스페이스를 깔끔하게 유지할 수 있다는 점에서 중요하다.

아래에 모듈 패턴이라고 알려진 클로져를 통해 몇 개의 public 함수가 private 함수와 변수에 접근하는 코드가 있다.

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }   
})();

alert(Counter.value()); /* 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* 2 */
Counter.decrement();
alert(Counter.value()); /* 1 */

이전 예제에서는 각 클로져가 자기만의 환경을 가졌지만 이 예제에서는 하나의 환경을 Counter.incrementCounter.decrement, Counter.value 세 함수가 공유한다.

공유되는 환경은 정의되자마자 실행되는 익명 함수 안에서 만들어진다. 이 환경에는 두 개의 private 아이템이 존재한다. 하나는 privateCounter라는 변수이고 나머지 하나는 changeBy라는 함수이다. 이 두 아이템 모두 익명함수 외부에선 접근할 수 없다. 하지만 익명함수 안에 정의된 세개의 public 함수에서 사용되고 반환된다.

이 세개의 public 함수는 같은 환경을 공유하는 클로져이다. 자바스크립트 어휘 스코핑(lexical scoping) 덕분에 세 함수 모두 privateCounter 변수와 changeBy 함수에 접근할 수 있다.

익명 함수가 카운터를 정의하고 이것을 Counter 변수에 할당한다는 걸 알아차렸을 것이다. 이 함수를 다른 변수에 저장하고 이 변수를 이용해 여러개의 카운터를 만들수도 있다.

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* 2 */
Counter1.decrement();
alert(Counter1.value()); /* 1 */
alert(Counter2.value()); /* 0 */

두개의 카운터가 어떻게 독립적으로 존재하는지 주목하라. makeCounter() 함수를 호출하면서 생긴 환경은 호출할 때마다 다르다. 클로져 변수 privateCounter 는 다른 인스턴스를 가진다.

객체지향 프로그래밍을 사용할 때 얻는 이점인 정보 은닉과 캡슐화를 클로져를 사용함으로써 얻을 수 있다.

자주하는 실수: 반복문 안에서 클로져 만들기

자바스크립트 1.7의 let 키워드 가 도입되기 이전에는 반복문 안에서 클로져를 생성해서 문제가 되는 경우가 빈번했다. 다음 예제를 보자.

<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

JSFIDDLE에서보기

helpText 배열은 세개의 도움말을 정의한다. 각 도움말은 입력 필드의 ID와 연관된다. 이 세개의 정의를 반복하며 입력필드에 onfocus 이벤트가 발생했을 때 입력필드에 해당하는 도움말을 표시한다.

이 코드를 실행해보면 제대로 동작하지 않는다는 것을 알 수 있다. 어떤 필드에 포커스를 주더라도 나이에 관한 도움말이 표시된다.

이유는 onfocus 이벤트에 지정한 함수가 클로져라는 것이다. 이 클로져는 함수 본체와 setupHelp 함수의 스코프로 이루어져 있다. 세개의 클로져가 만들어졌지만 각 클로져는 하나의 환경을 공유한다. 반복문이 끝나고 onfocus 콜백이 실행될 때 콜백의 환경에서 item 변수는 (세개의 클로져가 공유한다) helpText 리스트의 마지막 요소를 가리키고 있을 것이다.

여러개의 클로져를 이용해서 문제를 해결할 수 있다. 위에서 언급한 함수 공장을 사용해보자.

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp();

JSFIDDLE에서보기

예상한대로 작동한다. 콜백이 하나의 환경을 공유하지 않고 makeHelpCallback 함수가 만든 새로운 환경을 가진다. 이 환경에는 helpText 배열로부터 해당하는 문자열이 help 변수에 담겨있다.

추가로 원문에는 없지만 makeHelpCallback 함수를 이용하지 않고 즉시 실행 함수를 이용하면 아래와 같다.

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = (function(help) {
      return function() {
        showHelp(help);
      }
    })(item.help);
  }
}

setupHelp();

 

성능과 관련해서

클로져가 필요하지 않은 작업인데도 함수안에 함수를 만드는 것은 스크립트 처리 속도와 메모리 사용량 모두에서 현명한 선택이 아니다.

예를들어 새로운 오브젝트나 클래스를 만들 때 오브젝트 생성자에 메쏘드를 정의하는 것 보다 오브젝트의 프로토타입에 정의하는것이 좋다. 오브젝트 생성자에 정의하게 되면 생성자가 불릴때마다 메쏘드가 새로 할당되기 때문이다.

비현실적이지만 설명을 위해 예제를 첨부했다.

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

위의 코드는 일일히 메쏘드를 만들면서 클로져의 이점을 살리지 못하고 있다.

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

또는 다음처럼 하자

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

위의 두 예제에서는 상속된 속성은 모든 오브젝트에서 사용될 수 있고 메쏘드 정의가 오브젝트가 생성될 때마다 일어나지 않는다. 오브젝트 모델에 대한 자세한 설명을 참고하라.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures

Posted by 조신부리
,

함수(function) 다시 보기

js 2014. 11. 3. 10:27

자바스크립트는 한동안 개발자들의 많은 오해와 편견으로 toy language 취급을 받아 왔습니다. 누구든지 쉽게 배워 간단히 적용할 수 있다는 생각에 깊이 있는 학습이 이뤄지지 않았습니다. 하지만, 현대의 웹 애플리케이션이 시대적 요구와 사용자의 기대로 인해 점점 더 복잡한 대규모 시스템으로 발전해 나가면서 자바스크립트에 대한 관심이 크게 늘고 있습니다. 이에 자바스크립트에 대한 올바른 이해를 위해 자바스크립트에서 가장 중요한 주제인 함수에 대해 간략히 설명하겠습니다.

대부분 자바스크립트에서 함수를 설명할 때 “자바스크립트에서 함수는 first-class object(또는 citizen, value)다”라는 정의는 항상 빠지지 않고 등장하는 단골문장입니다. 하지만, first-class object에 대한 정확한 언급이 없어 그냥 흘려 듣기 쉽습니다.

그렇다면 first-class object란 무엇일까요?

  • first-class object는 변수에 저장할 수 있어야 합니다.
  • first-class object는 함수의 파라미터로 전달할 수 있어야 합니다.
  • first-class object는 함수의 반환값으로 사용할 수 있어야 합니다.
  • first-class object는 자료 구조에 저장할 수 있어야 합니다.

위와 같은 조건들을 충족시키는 객체를 first-class object라 부릅니다. Java에서 메소드는 위 조건들을 충족시키지 못하기 때문에 first-class citizen으로 취급되지 않습니다.

자바스크립트에서 함수는 first-class object다.

또한, 함수는 변수의 스코프를 결정하고 private 변수 또는 메소드 뿐만 아니라 함수의 특징을 이용하여 public 속성과 메소드를 제공하며 자바스크립트 모듈을 작성하는 좋은 도구이기도 합니다.

함수에 대한 올바르고 정확한 이해는 자바스크립트를 이해하는 데 있어 핵심 중의 하나이며 대규모 웹 애플리케이션이나 Single Page Applications(SPAs)을 개발하는데 있어 가장 중요한 개념이 됩니다.

 

함수와 익명함수

자바스크립트에서 함수를 정의하는 방법은 Function 객체를 사용하는 방법과 연산자인 function을 사용하는 방법이 있습니다.  일반적으로 Function 객체를 사용한 정의 방법은 많이 사용되지는 않습니다. 연산자인 function을 이용한 함수 정의 방식은 함수선언문(function declaration)과 함수표현식(function expression)으로 나뉩니다.

우리는 그간 아래와 같이 함수를 정의하고 사용해 왔습니다.

 이와 같은 방식을 함수선언식(function declaration)이라고 합니다. 함수선언식으로 정의된 함수는 자바스크립트 인터프리터가 스크립트가 로딩되는 시점에 바로 초기화하고 이를 VO(variable object)에 저장합니다.  그렇기 때문에 함수 선언의 위치와는 상관없이 소스 내 어느 곳에서든지 호출이 가능합니다.

함수 정의할 때 “함수는 first-class object이므로 변수에 할당될 수 있다.” 라는 전제 하에 아래와 같이 작성할 수 있습니다.

이렇게 정의한 방식을 함수표현식(function expression)이라고 합니다. 함수가 변수에 할당되었으므로 “함수는 객체이다.”라는 정의가 가능합니다. 함수표현식은 함수선언식과는 달리 스크립트 로딩 시점에 VO에 함수를 저장하지 않고 runtime시에 해석되고 실행되므로 이 두가지를 구분하는 것은 중요합니다.

함수선언식으로 함수를 정의하면 사용하기에 쉽지만 대규모 애플리케이션을 개발하는 경우 인터프리터가 너무 많은 코드를 VO에 저장하므로 애플리케이션의 응답속도는 현저히 떨어질 수 있으므로 주의해야 할 필요가 있습니다. 참고로, 스크립트 파일을 모듈화하고 이를 필요한 시점에 비동기 방식으로 로딩하여 http 요청을 줄이고 응답속도와 사용자 체감속도를 향상시킬 수 있습니다.

 

즉시실행함수(Immediately-invoked function expression)

자바스크립트에서 가장 큰 문제점 중의 하나는 글로벌 스코프에 정의된 것은 코드 내의 어디서든지 접근이 가능하다는 것입니다. 하지만, 외부에 공유되면 안되거나 공유될 필요가 없는 속성이나 메소드가 있습니다. 또한, 다른 스크립트 파일 내에서 동일한 이름으로 명명된 변수나 함수가 있을 경우 원치 않는 결과를 가져올 수 있습니다.

익명 함수표현식으로 함수를 하나 정의하고 실행해 보겠습니다. 그리고 외부에서 함수 내의 변수에 접근해 보겠습니다.

 이번에는 익명 즉시실행함수로 함수를 정의해 보겠습니다.

위 두개의 코드는 동일한 동작을 수행합니다.

함수표현식은 함수를 정의하고, 변수에 함수를 저장하고 실행하는 일련의 과정을 거칩니다.  하지만, 즉시실행함수를 사용하면 이와 같은 과정을 거치지 않고 즉시 실행된다는 특징이 있습니다. 차이점이라면 단순히 함수를 괄호”()”로 랩핑한 게 전부입니다.  이런 함수를 즉시실행함수(IIFE)라 부릅니다.

이번에는 변수를 선언하고 이 변수에 즉시실행함수를 할당해 보겠습니다.

콘솔에는 “private” 라고 출력됩니다. 

즉시실행함수 내에서 선언한 변수를 외부에서도 접근할 수 있음을 확인할 수 있습니다. 변수의 접근 범위가 함수 내부가 아닌 외부에서도 가능해진 것입니다.  이와 같이, 즉시실행함수는 변수의 스코프를 포함하는데 사용되며 외부에서 함수 내의 변수에 접근할 경우 이를 통제할 수 있습니다. 즉시실행함수는 글로벌 네임스페이스에 변수를 추가하지 않아도 되기 때문에 코드 충돌이 없이 구현할 수 있어 플러그인이나 라이브러리 등을 만들 때 많이 사용됩니다.

아래 두개의 코드는 기명 함수표현식과 즉시실행함수에서 파라미터를 전달하는 방법을 보여줍니다.

위 두개의 코드 블럭은 동일한 동작을 수행합니다. 또한, 앞서의 예제처럼 괄호”()”로 랩핑한 차이 밖에 없습니다.

jQuery나 Prototype 라이브러리는 동일한 $라는 글로벌 변수를 사용합니다. 만약, 이 두개의 라이브러리를 같이 사용한다면 $ 변수에 대한 충돌이 생길 것입니다. 하지만, 즉시실행함수의 코드 블럭에서 jQuery를 위한 $ 변수를 사용하고자 한다면 아래와 같이 파라미터를 전달하는 방법으로 Prototype의 $ 변수에 대한 overwritting을 예방할 수 있습니다.

 

모듈 패턴(Module Pattern)

현대의 웹 애플리케이션은 점점 더 복잡하고 고도화된 대규모 애플리케이션이나 데스크탑 애플리케이션의 모습을 닮아가는 형태(Rich Internet Application)로 진화하고 있는 추세입니다. 하나의 파일에 모든 코드를 담는 것은 불가능하고 설사 그렇게 작성되었다 하더라도 많은 문제점을 내포할 뿐만 아니라 유지보수시 골치 아픈 경험을 자주 하게될 것입니다. 그렇다면 이를 어떻게 극복할 수 있을까요? 그에 대한 답은 자바스크립트 함수의 특징을 이용한 모듈화에서 부터 찾을 수 있습니다.

Java나 C++과 같은 고급언어들은 언어 자체적으로 모듈화를 지원하는 방법을 제공하지만 자바스크립트는 언어레벨에서 캡슐화를 위한 접근제어자(private, public 등), 모듈 간의 구분을 위한 package가 명시적으로 제공되지 않습니다. 하지만, 명시적으로 제공되지 않는 이런 지원도구들을 자바스크립트 함수의 특징을 이용하여 유사하게 제공할 수 있습니다.

즉시실행함수는 우리가 작성한 코드들 뿐만 아니라 함께 사용하는 외부 라이브러리와도 충돌없이 구동하는 샌드박스(sandbox)를 제공합니다. 이 특징과 단위기능별로 작성된 코드를 분리된 개별 파일 형태로 유지한다면 앞서 언급한 모듈화를 위한 조건을 해결할 수 있습니다.

위에 작성된 즉시실행함수는 name, sex, position속성과 payBonus메소드를 가진 객체를 clerk변수에 반환합니다. 다시 말해, 즉시실행함수가 clerk변수에 저장되는 것이 아니라 즉시실행함수의 반환값이 clerk변수에 저장됩니다. name, sex, position변수를 글로벌 스코프에 추가하지 않고 단지 clerk변수만 추가되었습니다. 애플리케이션의 규모가 커지면 글로벌 변수에 대한 충돌을 고려해야 하므로 글로벌 변수 사용을 자제해야 합니다.
또한, name, sex, position속성은 clerk변수에 저장된 객체의 속성이므로 외부에서 접근이 가능(public)하지만 salary, totalBonus변수는 즉시실행함수 내의 변수이므로 외부에서 접근할 수 없습니다(private). paySalary()메소드를 호출하여 지급된 급여인 1800을 반환값을 가져올 수 있고 payBonus()메소드를 호출하여 지급된 상여금인 90을 반환할 수 있으며 payBosnus()메소드를 한번 더 호출하여 80을 반환할 수 있습니다. 여기서 주목해야 할 부분은 payBonus()메소드를 호출할 때마다 totalBonus 변수에 저장된 값이 업데이트 되지만 paySalary()메소드는 여러번 호출해도 salary 변수에 저장된 값은 업데이트 되지 않는 점입니다.

즉시실행함수를 위와 같이 사용하여 언어레벨에서 제공하지 못하는 모듈화 지원도구를 극복할 수 있으며 이렇게 작성된 코드를 분리된 파일로 구성하면 재사용성을 높일 수 있습니다.

자바스크립트 모듈 작성시 코드 순서

Javascript로 SPA를 구현할  때, 다음과 같은 순서의 코드로 모듈을 작성하게 되면 협업하는 개발자들에게 집약되고 일관된 코드를 제공하여 많은 도움이 될 것입니다. 이렇게 코드의 순서를 정하는 이유는 집약되고 일관된 코드를 제공하는데 있으므로 코드의 순서는 개발상황과 모듈용도에 맞춰가며 조정하고 추가 및 삭제될 수도 있습니다. 아래 순서는 단지 코드작성 순서의 예일뿐입니다.

  1. 모듈 스코프 내에서 사용할 변수 작성
  2. 유틸리티 메소드 작성
  3. DOM 조작 메소드 작성
  4. 이벤트 핸들러 작성
  5. Public 메소드 작성

아래 코드는 라이브러리를 모듈화하는 코딩기법을 정리해 봤습니다. jQuery, Backbone, underscore, requireJs 등 많은 자바스크립트 라이브러리나 프레임워크가 아래와 같은 코딩기법을 사용하고 있습니다.

 

맺음말

현대의 웹 애플리케이션은 시대와 환경의 요구로 인해 대규모 형태로 개발되고 있으며 데스크탑 애플리케이션을 닮아가는 추세이다. 자바스크립트로 이런 웹 애플리케이션을 개발하기 위해서는 자바스크립트 함수에 대한 올바른 이해가 뒷바침되어야 합니다.

특히, 즉시실행함수를 이용한 변수에 대한 스코프, 글로벌 네임스페이스 오염 방지, 언어레벨에서 명시적으로 지원하지 않는 캡슐화 도구를 모듈패턴으로 극복하는 방법 그리고 다른 라이브러리와 충돌없이 우리의 소스를 유지하는 방법은 꼭 이해해야  합니다.

다음에는 클로져(Closure)에 대한 내용이나 scope chain, prototype chain에 대해 올려볼 계획입니다.

http://www.nextree.co.kr/p4150/

 

Posted by 조신부리
,

JavaScript는 Array 객체에 sort() method를 제공한다.
그냥 호출하면 문자열 정렬을 하는 듯.

<script>
var arr = new Array(52.53691.140.987.31);
arr.sort();
document.write(arr);

// 결과는 아래와 같음
// 0.98,1.14,369,52.5,7.31
</script>




이 sort() 함수를 오버라이딩해서 custom sort가 가능하다.

<script>
// 견본배열
var arr = new Array(52.53691.140.98, 7.31);
// 일단 한 번 출력해보면 배열 그대로 출력이 된다.
document.write(arr+'<br />');

// custom sort
// 문자열, 정수, 실수의 비교를 하려면 미리 타입캐스팅을 해두고 비교하면 된다.
// return 에 있는 부등호의 방향을 뒤집으면 역정렬.
arr.sort(function (a1, a2) {
    // 문자열 비교 (toLowerCase()는 대소문자 구분없이 하기 위함)
    a1 = a1.toString().toLowerCase();
    a2 = a2.toString().toLowerCase();
    // 정수
    //a1 = parseInt(a1);
    //a2 = parseInt(a2);
    // 실수
    //a1 = parseFloat(a1);
    //a2 = parseFloat(a2);

    return (a1<a2) ? -1 : ((a1>a2) ? 1 : 0);
});

// 원하는대로 정렬이 되었음을 확인가능하다.
document.write(arr);
</script>





마지막으로 다차원 배열의 정렬.

<script>
// 이름, 나이 배열을 item으로 가지는 다차원 배열 정의
var arr2 = new Array();
arr2[0] = new Array('백충덕',   31);
arr2[1] = new Array('bloodguy'20);
arr2[2] = new Array('nicehide'19);
arr2[3] = new Array('racoonx',  48);
arr2[4] = new Array('mindriot'23);

// 그냥 출력해봄
document.write(arr2);
document.write('<br />');

// iIdx로 인덱스 번호를 지정하여, 
// 이름, 나이별로 정렬이 가능함.
arr2.sort(function (a1, a2) {
    var idx = 0// 이름
    //var idx = 1; // 나이

    switch (idx) {
        case 0// 이름 - 문자열 정렬 (사실 기본이라 아무것도 안해도 됨)
            a1[idx] = a1[idx].toString().toLowerCase();
            a2[idx] = a2[idx].toString().toLowerCase();
            break;
        case 1// 나이 - 정수변환
            a1[idx] = parseInt(a1[idx]);
            a2[idx] = parseInt(a2[idx]);
            break;
    }

    return (a1[idx]<a2[idx]) ? -1 : ((a1[idx]>a2[idx]) ? 1 : 0);
});
document.write(arr2);
</script>

http://bloodguy.tistory.com/trackback/619

'js' 카테고리의 다른 글

자바스크립트 this에 대해 알아보자  (0) 2014.12.06
클로져(Closures)  (0) 2014.11.03
함수(function) 다시 보기  (0) 2014.11.03
JSTL core : <c:forEach> 사용법과 varStatus 상태값  (0) 2014.10.19
Posted by 조신부리
,

JSTL 문법의 for문 사용법

<c:forEach items="${리스트가 받아올 배열이름}" var=$"{for문안에서 사용할 변수}" varStatus="status">

status 는 for문의 돌아가는 상태를 알 수 있게 체크하여 준다

#{status.current}   현재의 for문에 해당하는 번호

#{status.index} 0부터의 순서

#{status.count} 1부터의 순서

#{status.first}  현재 루프가 처음인지 확인

#{status.last}  현재 루프가 마지막인지 확인

#{status.begin} for문의 시작 값

#{status.end}   for문의 끝 값

#{status.step}  for문의 증가값

'js' 카테고리의 다른 글

자바스크립트 this에 대해 알아보자  (0) 2014.12.06
클로져(Closures)  (0) 2014.11.03
함수(function) 다시 보기  (0) 2014.11.03
다차원 배열 정렬 (multiple array sort)  (0) 2014.10.30
Posted by 조신부리
,