티스토리 뷰
| Lexical scoping
> Lexical Scoping Example
- "Lexical" : Lexical Scoping 과정에서 변수가 어디에서 사용 가능한지 알기 위해
그 변수가 소스코드 내 어디에서 선언되었는지를 고려한다는 것을 의미
1 2 3 4 5 6 7 8 | function init() { var name = "Mozilla"; // name은 init에 의해 생성된 지역 변수 function displayName() { // displayName() 은 내부 함수이며, 클로저 alert(name); // 부모 함수에서 선언된 변수를 사용 } displayName(); } init(); | cs |
- 결과 : Mozilla
- displayName()은 내부엔 자신만의 지역 변수가 없지만,
함수 내부에서 외부 함수의 변수에 접근할 수 있기 때문에
displayName() 역시 부모 함수 init()에서 선언된 변수 name에 접근할 수 있다.
(displayName()이 자신만의 name 변수를 가지고 있었다면, this.name을 사용했을 것임)
- 중첩된 함수는 외부 범위에서 선언한 변수에도 접근할 수 있다.
// Example
> Example01
1 2 3 4 5 6 7 8 9 10 11 12 13 | var number = 1; function a() { var number = 10; b(); } function b() { console.log(number); } a(); b(); | cs |
- 결과 : 1 1
- 함수의 선언에 따라 상위 스코프가 결정
(만일 함수의 호출로 상위 스코프가 결정되었다면, b()의 상위 스코프는 a()를 가리켜 b()는 10이 나왔을 것임) - Dynamic Scope
> Example02
1 2 3 4 5 6 7 8 9 10 11 12 13 | var number = 1; function a() { var number = 10; b(number); } function b(number) { console.log(number); } a(); b(number); | cs |
- 결과 : 10 1
| Closure
* 객체가 어떤 데이터와(그 객체의 속성) 하나 혹은 그 이상의 메소드들을 연관시킨다.
> Closure Example
- "Closure" : 함수와 함수가 선언된 어휘적 환경의 조합,
이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성
1 2 3 4 5 6 7 8 9 10 11 12 13 | function makeFunc() { var name = "Mozilla"; function displayName() { alert(name); } return displayName; } var myFunc = makeFunc(); //myFunc변수에 displayName을 리턴 //유효범위의 어휘적 환경을 유지 myFunc(); //리턴된 displayName 함수를 실행(name 변수에 접근) | cs |
- 결과 : Mozilla
- makeFunc() 실행이 끝나면 name 변수에 더 이상 접근할 수 없게 될 것 같지만,
JavaScript는 함수를 리턴하고, 리턴하는 함수가 클로저를 형성하여 접근이 가능해진다.
- myFunc 변수는 makeFunc() 실행 때 생성된 displayName()의 인스턴스에 대한 참조.
displayName의 인스턴스는 변수 name이 있는 어휘적 환경에 대한 참조를 유지
> Another Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function makeAdder(x) { var y = 1; return function(z) { y = 100; return x + y + z; }; } var add5 = makeAdder(5); var add10 = makeAdder(10); //클로저에 x와 y의 환경이 저장됨 console.log(add5(2)); // 107 (x:5 + y:100 + z:2) console.log(add10(2)); // 112 (x:10 + y:100 + z:2) //함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산 | cs |
- 결과 : 107 112
- makeAdder()은 특정한 값을 인자로 가질 수 있는 함수들을 리턴
- add5와 add10은 모두 클로저에 해당, 같은 함수 본문 정의를 공유하지만 서로 다른 맥락적 환경을 저장
- 클로저가 리턴된 후에도 외부함수의 변수들(y)에 접근 가능하다.
|| 이벤트 기반 예제
ex. [ 페이지의 글자 크기를 조정하는 버튼 ]
> javaScript
1 2 3 4 5 6 7 | var size12 = makeSizer(12); var size14 = makeSizer(14); var size16 = makeSizer(16); document.getElementById('size-12').onclick = size12; document.getElementById('size-14').onclick = size14; document.getElementById('size-16').onclick = size16; | cs |
> HTML
1 2 3 4 5 6 7 | <p>Some paragraph text</p> <h1>some heading 1 text</h1> <h2>some heading 2 text</h2> <a href="#" id="size-12">12</a> <a href="#" id="size-14">14</a> <a href="#" id="size-16">16</a> | cs |
> CSS
1 2 3 4 5 6 7 8 9 10 11 | body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; } | cs |
|| Closure를 이용하여 private method 흉내내기
> Example 01.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | var counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } }; })(); console.log(counter.value()); // logs 0 counter.increment(); counter.increment(); console.log(counter.value()); // logs 2 counter.decrement(); console.log(counter.value()); // logs 1 | cs |
- counter.increment, counter.decrement, counter.value 세 함수에 의해 공유되는 어휘적 환경
1. 공유되는 어휘적 환경은 실행되는 익명 함수 안에서 생성
2. 이 익명 함수는 정의되는 즉시 실행
3. 이 어휘적 환경은 두 개의 private item을 포함 (privateCounter 변수, changeBy 함수)
- private item은 익명 함수 외부에서 접근될 수 없고 익명 래퍼에서 반환된 public 함수를 통해 접근
- 익명 래퍼에서 반환된 세 가지 public 함수는 같은 환경을 공유하는 Closure 라는 것!
> Example 02. 여러 개의 카운터 만들기
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 | 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 */ | cs |
- 각 클로저(counter1, counter2)는 그들 고유의 클로저를 통한 privateCounter 변수의 다른 버전을 참조!
- 각 클로저가 호줄될 떄마다 하나의 클로저에서 변수 값을 변경하더라도 다른 클로저의 값에는 영향을 주지 않음.
|| Closure Scope Chain
- 모든 클로저에는 세 가지 Scope가 있다.
1. 지역 범위 (Local Scope)
2. 외부 함수 범위 (Outer Scope)
3. 전역 범위 (Global Scope)
> Example 01. 각 입력 필드 ID에 해당하는 엘리먼트의 onfocus 이벤트에 관련된 도움말을 보여주는 메소드에 연결
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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(); | cs |
- 함수 팩토리를 사용하여 각자의 환경을 공유하는 클로저 생성
> Example 02. let 키워드를 사용하는 방법
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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++) { let item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } } setupHelp(); | cs |
- var 대신 let 키워드를 사용하여 모든 Closure가 블록 범위 변수를 바인딩할 것임.
|| 성능 관련 고려 사항
- 생성자가 호출될 때마다 메서드가 다시 할당되는 상황을 방지하기 위하여,
새로운 객체/클래스를 생성할 때 메소드는 객체의 프로토타입에 연결되어야 한다.
이 방법은 클로저가 필요하지 않을 경우에도 다른 함수 내에서 함수를 불필요하게 작성 상황을 방지해준다.
> 비실용적인 코드
1 2 3 4 5 6 7 8 9 10 11 | function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; } | cs |
> 클로저의 이점을 이용하고 기존 프로토타입에 추가하는 방법
1 2 3 4 5 6 7 8 9 10 | 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; }; | cs |
> 더 깔끔한 방법
1 2 3 4 5 6 7 8 9 10 11 12 | function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } (function() { this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; }).call(MyObject.prototype); | cs |
출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures
'Web' 카테고리의 다른 글
Internet과 작동 원리 (HTTP, Browser, DNS, Domain, Hosting) (0) | 2021.06.09 |
---|---|
jQuery Selectric 정리 (2) | 2021.06.03 |
[Vue.js] Vue 기본 내용 정리 (0) | 2020.11.21 |
[JDBC] JAVA JDBC(select, insert, delete, update) (2) | 2020.10.22 |
[Node.js] cheerio module 로 크롤링하기 (0) | 2020.06.23 |