java-school logo

함수

자바스크립트에서 함수는 선언적 함수와 익명 함수로 나뉜다.

선언적 함수

선언적 함수는 페이지가 로드할 때 단 한번 해석된다.
해석된 후에는 함수는 호출될 때마다 실행된다.
선언적 함수는 function 키워드, 함수명, 인자 리스트, 함수 몸체를 아래와 같은 문법으로 만든다.
function 함수명 (인자1, 인자2,,,인자n) {
	//함수의 실행구문..
}
function add(a,b) {
	return a + b;
}
alert(add(3,4));

익명 함수(anonymous function)

익명 함수는 런타임 때 동적으로 생성되는 함수이다.
var multiply = function(a, b) {
	return a * b
};
alert(multiply(3,4));
Function 생성자를 이용해서 익명 함수를 만들 수 있다.
하지만 잘 쓰지는 않는다.
var minus = new Function('a','b', 'return a - b');
alert(minus(3,4));

자기 호출 익명 함수(Self-Executing Anonymous Functions)

자기 호출 익명 함수는 jQuery 와 같은 자바스크립트 라이브러리 코드에서 많이 볼 수 있는데, 함수를 ()를 이용해서 감싸고 마지막에 (); 있으면 () 안의 함수가 즉시 실행된다.
(function() {
	alert("Hello World!");
})();
(function(whom) {
	alert("Hello " + whom);
})('장길산');
(function(a, b) {
	alert(a + b);
})(2,5);

스코핑(Scoping)과 호이스팅(Hoisting)

스코핑(Scoping)은 변수가 유효성을 갖는 영역이 결정되는 규칙을 말한다.
스코핑을 충분히 이해하기 위해서는 호이스팅에 대한 이해가 중요하다.
호이스팅이란 자바스크립트 인터프리터가 '선언된 변수나 함수를 현재 실행 영역의 맨 위로 끌어올리는 동작'을 말한다.
할당 구문은 런타임 과정에서 이루어지기 때문에 호이스팅되지 않는다.
참고로, 호이스팅은 자바스크립트의 공식 용어는 아니다.
ECMA-262.pdf 문서에서 호이스팅이란 용어는 나오지 않는다.
자바는 블록({ }) 안에서 변수를 선언하면 그 변수는 블록에서만 유효한 변수이다.
하지만 자바스크립트에서 그와 같은 변수는 함수 안에서만 만들 수 있다.
자바스크립트에서는 {}, if 문 블록에서만 유효한 변수는 만들 수 없다.

다음 코드의 결과를 예측해 보자.
var x = 1;
function fn() {
	if (true) {
		var x = 10;
	}
	alert(x);
}
fn();
var x = 1;
function fn() {
	x = 10;
	return;
}
fn();
alert(x);
var x = 1;
function fn() {
	x = 10;
	return;
	function x() {}
}
fn();
alert(x);
위 예제는 설명이 필요하다. 정확하게 위 소스는 다음과 같이 해석되어 실행된다.
결론적으로 fn() 함수의 식별자 x는 fn() 함수 내에서만 유효하게 된다.
var x = 1;
function fn() {
	function x() {}
	x = 10;
	return;
}
fn();
alert(x);
var x = 1;
function fn() {
	function x() {}
	x = 10;
	return x;
}
var retVal = fn();
alert(retVal);
alert(x);
자바스크립트에서는 {}, if 문 블록에서만 유효한 변수는 만들 수 없다.
var x = 1;
alert(x);
if (true) {
	var x = 2;
	alert(x);
	x++;
}
alert(x);
function foo() {
	var x = 1;
	if (x) {
		(function () {
			var x = 2;
			alert(x);
		}());
	}
	alert(x);
}
foo();
위 예제는 함수에 var x = 2;를 선언했다.
이 x 변수는 함수 내에서만 유효한 영역을 가지는 새로운 변수이다.
함수 밖의 var x = 1;와는 전혀 상관없는 변수이다.
var x = 10;
function fn() {
	alert(x);
	var x = 20;
	alert(x);
}
fn();
alert(x);
위 코드가 호이스팅되면 아래 코드와 같다.
var x = 10;
function fn() {
	var x;
	alert(x);
	x = 20;
	alert(x);
}
fn();
alert(x);
만약 위 코드에서 함수 fn() 안에 선언된 var x;를 주석 처리하면 결과는 어떻게 바뀔까?
sayHo(); //sayHo();호출이 코드에서 먼저 있다
function sayHo() { 
	alert("Ho");
}
호이스팅되면 결과적으로 아래 코드와 같다.
function sayHo() { 
	alert("Ho");
}
sayHo();
하지만 비슷해 보이는 다음 코드는 에러를 발생시킨다.
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();
호이스팅되면 결과적으로 아래 코드와 같다.
function bumpInto() {
	function greet() {
		alert("How You Doin?");
	}
	function greet() {
		alert("What's Up?");
	}
	return greet();
}
bumpInto();
매개변수 리스트가 다르면 자바처럼 오버 로딩될까?
function bumpInto() {
	function greet() {
		alert("How You Doin?");
	}
	function greet(whom) { //매개변수가 있는 greet 함수
		alert("What's Up?");
	}
	return greet();
}
bumpInto();
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();
호이스팅 되면 결과적으로 아래 코드와 같다.
function bumpInto() {
	var greet;
	var greet;
	greet = function () {
		alert("How You Doin?");
	}
	return greet();
	greet = function (whom) {
		alert("What's Up?");
	}
}
bumpInto();
return 문 다음에 있는 "What's Up?"을 출력하는 greet() 함수는 실행될 기회가 없다.

클로저(Closures)

내부 함수

자바스크립트는 내부 함수(inner function)를 지원하는 언어다.
내부 함수는 외부 함수(내부 함수는 감싸는)에서 선언된 변수를 사용할 수 있다.
function fn() {
	var balance = 0; //외부에서 선언된 변수
	function deposit(money) { //내부함수
		balance += money;
		alert(balance);
	}
	deposit(100);
}
fn();
부모 함수 밖에서 내부 함수를 직접 부를 수는 없지만 부모 함수 밖에서 내부 함수를 실행시킬 방법은 있다.
"자바스크립트 함수는 기본적으로 함수 객체이다. 그래서 함수를 변수에 할당하거나 다른 함수의 인자로 넘길 수 있다."
위 예제를 다음과 같이 바꾸어 보자.
코드는 내부 함수에 대한 참조를 반환하여, 부모 함수 밖에서도 이 함수를 참조할 수 있게 된다.
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() 함수가 종료될 때 이 함수 내부에 정의된 지역 변수(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);
함수가 정의된 범위 밖에서 그 내부 함수를 참조할 때 클로저가 만들어진다.
내부 함수가 자신의 영역 범위를 넘어서 참조를 통해서 호출될 수 있다는 사실은 그 함수가 앞으로도 계속 호출될 수 있고, 그래서 자바스크립트는 그 함수를 계속 유지해야 함을 뜻한다.
변수가 부모 함수의 지역 변수라면 당연히 내부 함수는 그 부모의 범위를 상속하고 있기 때문에 그 변수를 참조할 수 있다.
같은 참조 변수를 이용해서 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());
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());
참고