티스토리 뷰

반응형


| 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


반응형
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday