다루는 내용
- 함수 표현식의 특징
- 함수와 재귀
- 클로저를 이용한 고유(프라이빗) 변수
1. 함수 표현식의 특징
- 함수를 정의하는 방법은 함수 선언과 함수 표현식 2가지가 있음.
- 함수 선언에서 뚜렷한 특징은 호이스팅(hoisting)이다.
1 2 3 4 5
sayHi(); function sayHi() { console.log("hi"); } // 함수 선언부를 다른 코드보다 먼저 읽고 실행함
- 함수 표현식은 일반적인 변수 할당과 거의 비슷하며 함수 이름이 없어 익명 함수로 간주함
1 2 3 4 5
sayHi(); // error var sayHi = function() { console.log("hi"); } // 다른 표현식과 마찬가지로 호출 하기 전에 할당해야함
- 함수 표현식은 다른 함수에서 사용할 수 있도록 함수를 반환하는 형태도 가능
1 2 3 4 5 6 7 8 9 10 11 12 13
function compareFunction(propertyName) { return function(object1, object2) { var val1 = object1[propertyName]; var val2 = object2[propertyName]; if(val1 < val2) { return -1; } else if(val1 > val2) { return 1; } else { return 0; } }; }
2. 함수와 재귀
2-1 재귀
- 함수가 자기 자신을 호출하는 형태
1 2 3 4 5 6 7 8 9 10 11 12
function factorial(num) { if(num <= 1) { return 1; } else { return num * factorial(num-1); } } var anotherFactorial = factorial; factorial = null; console.log(anotherFactorial(4)); // error // factorial 은 null 인데 anotherFactorial 이 factorial()을 실행하려 하기 때문
- arguments.callee 를 호출하면 되지만, 이는 스트릭트 모드에서 접근할 수 없기에,
이름 붙은 함수 표현식
을 사용1 2 3 4 5 6 7
var factorial = (function f(num) { if (num <= 1) { return 1; } else { return num * f(num-1); } });
2-2 클로저
- 다른 함수의 스코프에 있는 변수에 접근 가능한 함수
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
function compareFunction(propertyName) { return function(object1, object2) { var val1 = object1[propertyName]; var val2 = object2[propertyName]; if(val1 < val2) { return -1; } else if(val1 > val2) { return 1; } else { return 0; } }; } var compare = compareFunction("name"); var result = compare({name : "snack"}, {name : "gray"});
- 외부 함수가 실행을 마치고 익명 함수를 반환하면 익명 함수의 스코프 체인은 외부 함수의 활성화 객체와 전역 변수 객체를 포함하도록 초기화되므로, 익명 함수는 외부 함수의 변수 전체에 접근 가능
- 외부 함수가 실행을 마치면 실행 컨텍스트의 스코프 체인은 파괴되지만, 활성화 객체는 익명 함수가 파괴될 때까지 메모리에 남음
- compare = null 을 통해 함수에 대한 참조를 사라지게 하여 GC 가 메모리 회수가 가능하게 함
- 클로저는 외부 스코프를 보관해야 하므로 메모리를 많이 잡아먹는다. 그러므로 남용은 지양
- 클로저는 항상 외부 함수의 변수에 마지막으로 저장된 값만 알 수 있음 (전체 변수 객체에 대한 참조를 저장하기 때문)
1 2 3 4 5 6 7 8 9 10 11 12
function createdFunction() { var result = new Array(); for(var i = 0; i < 10; i++) { result[i] = function(num) { return function() { return num; }; }(i); } return result; }
- 변수 i 를 익명함수에 매개변수로 넘김
- 함수 매개변수는 값 형태로 전달되므로 i의 현재 값을 매개변수 num 에 복사
- 익명함수는 num 을 간직한 클로저를 생성하여 반환
- this 객체는 런타임에서 함수가 실행 중인 컨텍스트에 묶이는데, 익명함수는 특정 객체에 묶여 있지 않으므로 mode == strict mode ? undefined : window
- 모든 함수는 호출되는 순간 자동으로 this와 arguments 두 특별한 변수를 갖게되는데, 내부 함수는 결코 외부 함수의 this 와 arguments 에 직접적으로 접근이 불가능, 그러므로 다음과 같이 해서 접근 가능
1 2 3 4 5 6 7 8 9 10 11 12 13 14
var name = "the window"; var object = { name : "my object", getNameFunc : function() { var that = this; return function() { return that.name; }; } }; console.log(object.getNameFunc()()); // my object
- 블록 레벨 스코프라는 개념이 없기 때문에 익명 함수를 괄호로 감싸서 흉내낼 수 있음 (
고유 스코프
라고 부르기도 함)1 2 3 4 5 6 7 8
function outputFunc(count) { (function () { for(var i=0; i < countl i++) { console.log(i); } })(); console.log(i); // error }
- 전역 스코프에 추가되는 변수나 함수의 수를 제한하는 용도로 자주 사용 (대규모 어플리케이션에선 전역 스코프에 변수나 함수를 추가하지 않는 편이 좋음)
- 익명 함수에 대한 참조가 존재하지 않아 클로저의 메모리 문제도 덜함
3. 클로저를 이용한 고유(프라이빗) 변수
- 자바스크립트에는 private member 이란 개념이 없지만,
고유 변수
라는 개념은 존재 - 함수 내부에서 정의한 변수는 함수 밖에서 접근할 수 없으므로 모두
고유 변수
로 간주 (함수 매개변수, 지역 변수, 내부 함수 등) - 다음과 같이 고유 및 특권 맴버를 정의해서 데이터를 직접적으로 수정할 수 없게 보호 가능
1 2 3 4 5 6 7 8 9 10 11 12 13 14
function Person(name) { this.getName = function() { return name; }; this.setName = function(value) { name = value; }; } var person = new Person("snack"); console.log(person.getName()); // snack person.setName("gray"); console.log(person.getName()); // gray
- 생성자 패턴에는 인스턴스가 매번 생성되므로, 이를 방지하기 위해 정적 고유 변수를 사용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
(function() { // 고유 변수와 함수 var name = ""; function privateFunction() { return false; } //생성자 Person = function(value) { name = value }; // 공용 메소드와 특권 메소드 Person.prototype.getName = function() { return name; }; Person.prototype.setName = function(value) { name = value; }; })(); var person1 = new Person("snack"); console.log(person1.getName()); // snack person1.setName("gray"); console.log(person1.getName()); // gray var person2 = new Person("john"); console.log(person1.getName()); // john console.log(person2.getName()); // john
- 각 인스턴스가 독립 변수를 가질 수는 없지만, 프로토타입을 통해 코드 재사용성은 좋아짐
- 싱글톤 같은 일을 하는 모듈 패턴이 있음
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
var application = function() { //고유 변수와 함수 var components = new Array(); //초기화 components.push(new BaseComponent()); //공용 인터페이스 return { getComponentCount : function() { return components.length; }, registerComponent : function(component) { if(typeof component == "object") { components.push(component); } } }; }();
- 애플리케이션 레벨의 정보를 관리할 싱글톤을 두는 경우가 많음
- 하나의 객체를 반드시 생성하고 몇 가지 데이터를 가지며 또한 고유 데이터에 접근 가능한 공용 메소드를 외부에 노출하도록 초기화해야 할 때 유용
- 싱글톤 객체가 특정 타입의 인스턴스지만 프로퍼티나 메서드를 추가하여 확장해야 할 때
모듈 확장 패턴
을 사용1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
var application = function() { //고유 변수와 함수 var componens = new Array(); //초기화 components.push(new BaseComponent()); //애플리케이션 인스턴스 생성 var app = new BaseComponent(); //공용 인터페이스 app.getComponentCount = function() { return components.length; } app.registerComponent = function(component) { if(typeof component == "object") { components.push(component); } }; return app; }();
app 은 application 객체의 로컬 버전