함수
자바스크립트에서 함수는 선언적 함수와 익명 함수로 나뉜다.
선언적 함수
선언적 함수는 페이지가 로드할 때 단 한번 해석된다.
해석된 후에는 함수는 호출될 때마다 실행된다.
선언적 함수는 function 키워드, 함수명, 파라미터 리스트, 함수 몸체를 아래와
같은 문법으로 만든다.
function 함수명 (파라미터1, 파라미터2,,,파라미터n) { //함수의 실행구문.. }
function add(a,b) { return a + b; } alert(add(3,4));
7
익명 함수(anonymous function)
익명 함수는 런타임 때 동적으로 생성되는 함수이다.
var multiply = function(a, b) { return a * b }; alert(multiply(3,4));
12
Function 생성자를 이용해서 익명 함수를 만들 수 있다.
하지만 잘 쓰지는 않는다.
var minus = new Function('a','b', 'return a - b'); alert(minus(3,4));
-1
자기 호출 익명 함수(Self-Executing Anonymous Functions)
자기 호출 익명 함수는 jQuery 와 같은 자바스크립트 라이브러리 코드에서 많이 볼 수 있는데,
함수를 ()를 이용해서 감싸고 마지막에 (); 있으면 () 안의 함수가 즉시 실행된다.
(function() { alert("Hello World!"); })();
Hello World!
(function(whom) { alert("Hello " + whom); })('장길산');
Hello 장길산
(function(a, b) { alert(a + b); })(2,5);
7
스코핑(Scoping)과 호이스팅(Hoisting)
스코핑(Scoping)은 변수가 유효성을 갖는 영역이 결정되는 규칙을 말한다.
스코핑을 충분히 이해하기 위해서는 호이스팅에 대한 이해가 중요하다.
호이스팅이란 자바스크립트 인터프리터가 '선언된 변수나 함수를 현재 실행 영역의 맨 위로 끌어올리는 동작'을 말한다.
할당 구문은 런타임 과정에서 이루어지기 때문에 호이스팅되지 않는다.
참고로, 호이스팅은 자바스크립트의 공식 용어는 아니다.
ECMA-262.pdf 문서에서 호이스팅이란 용어는 나오지 않는다.
자바는 블록({ }) 안에서 변수를 선언하면 그 변수는 블록에서만 유효한 변수이다.
하지만 자바스크립트에서 그와 같은 변수는 함수 안에서만 만들 수 있다.
자바스크립트에서는 {}, if 문 블록에서만 유효한 변수는 만들 수 없다.
다음 코드의 결과를 예측해 보자.
var x = 1; function fn() { if (true) { var x = 10; } alert(x); } fn();
10
var x = 1; function fn() { x = 10; return; } fn(); alert(x);
10
var x = 1; function fn() { x = 10; return; function x() {} } fn(); alert(x);
1
위 예제는 설명이 필요하다.
정확하게 위 소스는 다음과 같이 해석되어 실행된다.
결론적으로 fn() 함수의 식별자 x는 fn() 함수 내에서만 유효하게 된다.
var x = 1; function fn() { function x() {} x = 10; return; } fn(); alert(x);
1
var x = 1; function fn() { function x() {} x = 10; return x; } var retVal = fn(); alert(retVal); alert(x);
10 1
자바스크립트에서는 {}, if 문 블록에서만 유효한 변수는 만들 수 없다.
var x = 1; alert(x); if (true) { var x = 2; alert(x); x++; } alert(x);
1 2 3
function foo() { var x = 1; if (x) { (function () { var x = 2; alert(x); }()); } alert(x); } foo();
2 1
위 예제는 함수에 var x = 2;를 선언했다.
이 x 변수는 함수 내에서만 유효한 영역을 가지는 새로운 변수이다.
함수 밖의 var x = 1;와는 전혀 상관없는 변수이다.
var x = 10; function fn() { alert(x); var x = 20; alert(x); } fn(); alert(x);
undefined 20 10
위 코드가 호이스팅되면 아래 코드와 같다.
var x = 10; function fn() { var x; alert(x); x = 20; alert(x); } fn(); alert(x);
undefined 20 10
만약 위 코드에서 함수 fn() 안에 선언된 var x;를 주석 처리하면 결과는 어떻게 바뀔까?
sayHo(); //sayHo();호출이 코드에서 먼저 있다 function sayHo() { alert("Ho"); }
Ho
호이스팅되면 결과적으로 아래 코드와 같다.
function sayHo() { alert("Ho"); } sayHo();
Ho
하지만 비슷해 보이는 다음 코드는 에러를 발생시킨다.
sayHo(); //TypeError: sayHo is not a function var sayHo = function() { alert("Ho"); }
다음은 좀 더 어려운 예제이다.
function bumpInto() { function greet() { alert("How You Doin?"); } return greet(); function greet() { alert("What's Up?"); } } bumpInto();
What's Up?
호이스팅되면 결과적으로 아래 코드와 같다.
function bumpInto() { function greet() { alert("How You Doin?"); } function greet() { alert("What's Up?"); } return greet(); } bumpInto();
What's Up?
파라미터 리스트가 다르면 자바처럼 오버 로딩될까?
function bumpInto() { function greet() { alert("How You Doin?"); } function greet(whom) { //파라미터가 있는 greet 함수 alert("What's Up?"); } return greet(); } bumpInto();
What's Up?
greet()와 greet(whom) 함수는 자바처럼 오버 로딩되지 않는다.
나중에 선언된 greet(whom) 함수가 실행된다.
할당 구문은 런타임 과정에서 이루어지기 때문에 호이스팅 되지 않는다고 했다.
다음 예제를 보자.
function bumpInto() { var greet = function () { alert("How You Doin?"); } return greet(); var greet = function (whom) { alert("What's Up?"); } } bumpInto();
How You Doin?
호이스팅 되면 결과적으로 아래 코드와 같다.
function bumpInto() { var greet; var greet; greet = function () { alert("How You Doin?"); } return greet(); greet = function (whom) { alert("What's Up?"); } } bumpInto();
How You Doin?
return 문 다음에 있는 "What's Up?"을 출력하는 greet() 함수는 실행될 기회가 없다.
클로저(Closures)
내부 함수
자바스크립트는 내부 함수(inner function)를 지원하는 언어다.
내부 함수는 외부 함수(내부 함수는 감싸는)에서 선언된 변수를 사용할 수 있다.
function fn() { var balance = 0; //외부에서 선언된 변수 function deposit(money) { //내부함수 balance += money; alert(balance); } deposit(100); } fn();
100
부모 함수 밖에서 내부 함수를 직접 부를 수는 없지만 부모 함수 밖에서 내부 함수를 실행시킬 방법은 있다.
"자바스크립트 함수는 기본적으로 함수 객체이다.
그래서 함수를 변수에 할당하거나 다른 함수의 아규먼트로 넘길 수 있다."
위 예제를 다음과 같이 바꾸어 보자.
코드는 내부 함수에 대한 참조를 반환하여, 부모 함수 밖에서도 이 함수를 참조할 수 있게 된다.
function fn() { var balance = 0; //외부에서 선언된 변수 function deposit(money) { //내부함수 balance += money; alert(balance); } alert("fn() 함수가 실행된다."); return deposit; } var retFn = fn();//만약 fn함수가 반환하지 않는 함수라면 retFn은 undefined 이다. retFn(200);
fn() 함수가 실행된다. 200
이 코드가 문제없이 실행된다는 사실은 자바 프로그래머를 불편하게 한다.
자바에서는 메소드 안에 정의된 지역변수는 메소드가 종료되면 사라진다.
마찬가지로 fn() 함수가 종료될 때 이 함수 내부에 정의된 지역 변수(balance)는 없어져야 하지만,
fn() 함수가 리턴 값을 반환했는데도 balance 변수는 여전히 유효하다.
balance가 여전히 유효한 이유는 retFn가 클로저를 갖는다는 데 있다.
클로저는 2개로 이루어진 특별한 객체이다.
첫 번째는 함수이고 두 번째는 그 함수가 만들어진 환경이다.
여기서 환경이란 함수가 만들어질 때 사용할 수 있는 변수들이다.
"retFn는 deposit 함수와 balance 변수를 포함하는 클로저를 갖는다."
function fn() { var balance = 0; function deposit(money) { balance += money; alert(balance); } return deposit; } var ac1 = fn(); alert("---ac1---"); ac1(50); ac1(450); var ac2 = fn(); alert("---ac2---"); ac2(2000); ac2(500);
---ac1--- 50 500 ---ac2--- 2000 2500
함수가 정의된 범위 밖에서 그 내부 함수를 참조할 때 클로저가 만들어진다.
내부 함수가 자신의 영역 범위를 넘어서 참조를 통해서 호출될 수 있다는 사실은 그 함수가 앞으로도
계속 호출될 수 있고, 그래서 자바스크립트는 그 함수를 계속 유지해야 함을 뜻한다.
변수가 부모 함수의 지역 변수라면 당연히 내부 함수는 그 부모의 범위를 상속하고
있기 때문에 그 변수를 참조할 수 있다.
같은 레퍼런스를 이용해서 deposit()를 두 번 호출하면 balance 변수의 값이 누적되고 있음을 확인할 수 있다.
ac1(50); ac1(450);
ac2(200); ac2(500);
서로 다른 레퍼런스(ac1과 ac2)로 deposit() 함수를 호출하면, balance 변수가 서로 독립적으로 증가한다.
클로저 간 변수 공유
function account(accountNo) { var balance = 0; function getAccountNo() { return accountNo; } function deposit(money) { balance += money; } function withdraw(money) { balance -= money; } function getBalance() { return balance; } return { "getAccountNo": getAccountNo, "deposit": deposit, "withdraw": withdraw, "getBalance": getBalance }; } var ac1 = account('111'); ac1.deposit(1000); ac1.withdraw(500); alert(ac1.getAccountNo() + ': ' + ac1.getBalance()); var ac2 = account('222'); ac2.deposit(3000); ac2.withdraw(1000); alert(ac2.getAccountNo() + ': ' + ac2.getBalance());
111: 500 222: 2000
4 개의 내부 함수는 동일한 지역변수를 참조하고 있으며, 동일한 변수 범위를 공유하고 있다.
deposit() 함수가 balance를 1000 증가시키면 withdraw()가 다시 호출될 때에 증가된 balance의 값이
새로운 시작 값이 된다.
account()을 또 호출하면(var ac2 = account('222');)
동일한 닫힌 환경을 가지는 클로저의 새 인스턴스가 생성된다.
객체 지향 자바스크립트
자바에서의 클래스를 자바스크립트에서는 다음처럼 작성하면 된다.
간단히 설명하면,
자바스크립트에서는 class 란 키워드가 없으니 클래스를 만들려면 function를 이용해서 만든다.
this는 자바에서의 this 와 같다고 보면 된다.
function Account(accountNo,balance) { this.accountNo = accountNo.toString(); this.balance = balance; } Account.prototype.getAccountNo = function () { return this.accountNo; }; Account.prototype.getBalance = function () { return this.balance; }; Account.prototype.deposit = function (money) { this.balance += money; }; Account.prototype.withdraw = function (money) { this.balance -= money; }; var ac1 = new Account('111',500); ac1.deposit(1000); ac1.withdraw(500); alert(ac1.getAccountNo() + ': ' + ac1.getBalance()); var ac2 = new Account('222',1000); ac2.deposit(3000); ac2.withdraw(1000); alert(ac2.getAccountNo() + ': ' + ac2.getBalance());
111: 1000 222: 3000참고