'바인딩', var 할당, 함수 인자 사용, this 전달, 프로퍼티 할당 등의 과정에서 자바스크립트의 이름에 값을 할당하는 행위를 가리킨다.
'스코프'라는 단어는 자바스크립트에서 다양한 의미로 해석된다.
* this 바인딩값
* this 바인딩값이 정의하는 실행 컨텍스트
* 변수 '생명주기'
* 변수값 해석 방식 또는 어휘적 바인딩
3.1 전역 스코프
- 스코프의 범위라는 말은 변수의 생명주기를 가리킨다.
aGlobalVariable = 'global'; // var 키워드 없이 변수를 정의하면 전역 스코프가 된다.
aGlobalVariable = 'modify' // 변수의 속성값을 바로 바꿀 수 있다.
aGlobalVariable //-> modify
- 전역 변수 선언은 프로그래밍 방법론에서 좋지 않은 패러다임에 속하며 동일한 변수명을 중복해서 선언하는 사이드 이펙트가 발생할 가능성이 크다.
- 자바스크립트의 변수는 불변성을 가질 수 없다.
- 프로그램의 어떤 코드에서든 언제든지 값을 고칠 수 있다는 것이 전역 변수의 약점이다.
3.2 어휘 스코프(Lexical Scoping)
- 자바스크립트의 함수들은 동적이라기보다 어휘적으로 유효 범위가 정해진다.
- 어휘 스코프란 변수와 값의 유효 범위를 가리킨다.
- 중첩된 함수가 그 함수를 포함하는 함수의 모든 전달인자와 지역 변수들에 접근할 수 있음을 의미한다.
a = "Outer"
function afun() {
var a = "Middle";
return _.map([1,2], function(e) {
var a = "In";
return [a, e].join(' ');
});
}
afun(); //-> ["In 1", "In 2"]
- 가장 가까운 바인딩 컨텍스트에서 시작해서 바인딩을 찾을 때까지 외부로 변수 탐색이 이루어 진다.
3.3 동적 스코프(dynamic scoping)
- 가장 저평가되었으면서도 동시에 과하게 남용되는 스코프 중 하나다.
- 동적 스코핑은 이름을 가진 값들의 전역 테이블 개념을 기반으로 한다.
var globals = {};
function makeBindFun(resolver) {
return function(k, v) {
var stack = globals[k] || [];
globals[k] = resolver(stack, v);
return globals;
};
}
var stackBinder = makeBindFun(function(stack, v) {
// 키와 값을 받아와 키와 관련된 슬롯에 있는 전역 바인딩 맵에 값을 추가 한다.
stack.push(v);
return stack;
});
var stackUnbinder = makeBindFun(function(stack) { // 마지막 값 바인딩을 꺼낸다.
stack.pop();
return stack;
});
/* 중간 과정 */
var stackBinder = function(k, v) {
var stack = globals[k] || [];
globals[k] = function(stack, v) {stack.push(v);
return stack;
}
return globals;
}
/* stackBinder 을 명령행 프로그래밍으로 한다면 */
var stackBinder = function(k, v) {
var stack = globals[k] || [];
stack.push(v)
globals[k] = stack;
return globals;
}
var dynamicLookup = function(k) { // k 키에 해당하는 값의 마지막 값 리턴
var slot = globals[k] || [];
return _.last(slot);
};
stackBinder('a', 1);
stackBinder('b', 2);
dynamicLookup('a')
//-> 1
globals;
// {a: [1], b: [2]}
function f() { return dynamicLookup('a'); };
function g() { stackBinder('a', 'g'); return f(); };
f();
//-> 1
g();
//-> 'g'
globals;
// {a: [1, "g"], b: [100]} // 새로운 값이 추가 되면 [1, "g"]가 된다.
- 어떤 함수를 호출한 함수가 누군지 알아야만 주어진 변수의 바인딩값을 알 수 있다는 것은 동적 스코핑의 치명적인 약점이다.
3.3.1 자바스크립트의 동적 스코프
- this 레퍼런스에 동적 스코프가 적용된다.
- this 레퍼런스가 자신이 처음 만들어졌던 환경과 다른 컨텍스트에서 다른 값을 가질 수 있다.
- 호출자가 누구냐에 따라 this 레퍼런스의 값이 결정된다.
- apply나 call이 참조하는 레퍼런스에 따라 this 레퍼런스가 달라진다.
- this 레퍼런스는 동적으로 스코핑되므로 버튼 클릭과 같은 이벤트 처리함수에서 this를 사용하는 것은 위험하다.
function a() {return this;}
a(); //-> Window 전역객체
a.call('b'); //-> b
a.apply('c', []); //-> c
보충
- 함수를 호출할 때에 먼저 유효 범위를 함수가 정의될 당시의 효력을 지니는 유효 범위 체인으로 설정한다.
function a() {
// 선언된 모든 변수는 전역 네임스페이스 대신 호출 객체의 프로퍼티가 된다.
}
a(); // 호출하는 함수는 전역객체이다.
(function(){
})();
3.4 함수 스코프
function strangeIdentity(n) {
// intentionally strange
for(var i=0; i<n; i++);
return i;
}
strangeIdentity(138);
//-> 138
- 한수 바디 안의 모든 var 선언을 암묵적으로 자신이 포함된 함수의 바디 시작 부분으로 이동시킨다.(호이스팅)
- 함수의 모든 코드는 내부에 정의된 모든 변수에 접근할 수 있다.
function strangerIdentity(n) {
// this가 해당 함수 스코프가 아닌 전역 스코프 이다.
for(this['i'] = 0; this['i']<n; this['i']++);
return this['i'];
}
strangerIdentity.call({}, 100); //-> 빈 객체를 넘겨 전역 스코프를 건드리지 않는 다.
// 언더 스코어에서는 _.clone() 함수를 사용해서 전역 컨텍스트 문제를 해결한다.
3.5 클로져
변수를 '캡처'하는 함수다.
3.5.1 클로저 시뮬레이션
- 나중에 사용할 목적으로 정의된 스코프에 포함된 외부 바인딩을 캡처하는 함수다.
- 클로저가 변수를 캡처했다면 생명 주기가 연장된다.
function a() {
var b = "asdf";
return function() {
return b;
}
}
var c = a()
c() //-> asdf
function createScaleFunction(FACTOR) {
return function(v) {
return _.map(v, function(n) {
return (n * FACTOR);
});
};
}
var scale10 = createScaleFunction(10); // 함수 인자를 캡쳐한다.
/* scale10 아래와 같은 익명함수가 만들어진다. */
function(v) {
return _.map(v, function(n) {
return n * 10;
}
}
scale10([1,2,3]);
//=> [10, 20, 30]
자유 변수
내부 함수를 포함하는 내부에 정의된 모든 변수를 볼 수 있다는 것이 클로저를 유지하는 기본 원칙이다. 이렇게 자유롭게 볼 수 있는 변수를 '자유'변수라고 부른다
function makeAdder(CAPTURED) {
return function(free) {
return free + CAPTURED;
};
}
var add10 = makeAdder(10);
add10(32);
// 외부 함수의 CAPTURED 변수를 선언하지 않고도 사용하므로 변수를 캡처했다라고 할 수 있다.
//makeAdder로 새로운 함수를 만들 때마다 고유한 CAPTURED 인스턴스가 만들어진다.
//=> 42
// 함수캡쳐
function averageDamp(FUN) {
return function(n) {
return average([n, FUN(n)]);
}
}
var averageSq = averageDamp(function(n) { return n * n }); // 함수를 캡쳐한다.
averageSq(10);
//=> 55
셰도잉
x 라는 이름의 변수가 특정 스코프에 정의되어 있는 상태에서 하위스코프에서 같은 이름으로 다른 변수를 정의할 때 셰도잉이 발생한다.
- 연달아 같은 이름의 변수를 두 번 선언하면 두 번째 선언만 유효하게 된다.
- '가장 가까운' 변수 바인딩이 우선권을 갖는 다.
var c = 1; var c = 2;
c //-> 2
var a = 0;
function b(a) {
return "= " +a;
}
b(1); //-> "= 1";
b(); //-> "= "
function d(e) {
return function(e) {
return e + 1;
}
}
var f = e(100);
f(1); //?
3.5.2 클로저 사용하기
function complement(PRED) {
return function() {
return !PRED.apply(null, _.toArray(arguments));
};
}
function isEven(n) { return (n%2) === 0 }
var isOdd = complement(isEven);
isOdd(2); //=> false
isOdd(413); //=> true
function isEven(n) { return false; } // 새로운 레퍼런스를 만들었다.
isOdd(13) //-> true;
// 클로저에서는 클로저가 만들어질 당시에 캡처한 값의 레퍼런스를 캡처한다.
function a(b) {
return function() {
return b;
}
}
var o = {x:1};
var c = a(o);
c(); // {x:1}
o.z = 100; // o 변수 수정, 새로 만든 게 아니다.
c(); // {x:1, z:100} // 변수 o의 레퍼런스가 클로저의 내부와 외부에 모두 존재하므로 경계가 엉키게 된다.
function a() {
var b = 0; //private 변수
return {
c : function() {
return b;
}
}
}
3.5.3 추상화 도구 클로저
- 클로저를 이용해서 생성 시에 캡처되는 어떤 '설정'에 따라 다른 함수를 만들 수 있다.
function plucker(FIELD) {
return function(obj) {
return (obj && obj[FIELD]);
};
}
var best = {title: "Infinite Jest", author: "DFW"};
var getTitle = plucker('title'); // title(설정) 필드를 가져오는 함수를 만든다.
getTitle(best);
//=> "Infinite Jest"
var books = [{title: "Chthon"}, {stars: 5}, {title: "Botchan"}];
var third = plucker(2); // 3번째(설정) 값을 가져오는 함수
third(books);
//=> {title: "Botchan"}
'책정리 > 자바스크립트' 카테고리의 다른 글
함수형 자바스크립트 - 7장 순수성, 불변성, 변경 정책 (0) | 2017.02.04 |
---|---|
함수형 자바스크립트 - 4장 고차원 함수 (0) | 2017.02.03 |
함수형 자바스크립트 - 5장 함수로 함수 만들기 (0) | 2017.01.23 |
함수형 자바스크립트 - 2장 일급 함수와 응용형 프로그래밍 (0) | 2017.01.09 |
함수형 자바스크립트 - 1장 함수형 자바스크립트 소개 (0) | 2017.01.09 |
댓글