— javascript — 3 min read
스코프는 특정 장소에 변수를 저장하고 나중에 그 변수를 찾기 위해 정의된 규칙이다. 스코프 규칙은 어디서 어떻게 정의될까? 사실 자바스크립트는 컴파일러 언어다.
컴파일러가 언어를 처리하는 과정을 Compilation 이라고 한다. Compilation은 보통 3단계를 거친다.
자바스크립트 엔진은 기존 컴파일러와 다르게 미리 Compilation 을 수행할 수 없어서 최적화할 시간이 많지 않다.
엔진이 컴파일러가 생성한 코드를 실행할 때 변수가 선언된 적이 있는지 스코프에서 검색한다. 변수가 왼쪽에 가깝게 있으면 LHS, 변수가 오른쪽에 가깝게 있으면 RHS이다. 엔진이 아직 선언되지 않은 변수를 찾을 때 RHS로 찾으면 ReferenceError 가 발생한다. 하지만 LHS로 찾을 때 없으면 strict mode 가 아닌 경우에 한하여 새로운 변수를 글로벌로 생성하고 엔진에 넘겨준다.
자바스크립트는 함수 기반 스코프이므로 일반적으로 함수 단위로 스코프를 가진다. 하지만 블록스코프도 분명히 존재한다.
코드를 함수로 감싸면 새로운 스코프가 생성되어 변수와 함수를 '숨길' 수 있다. 그렇게 하는 여러 목적 중 하나는 소프트웨어 디자인 원칙인 '최소 권한의 원칙'에 따라 필요한 최소만 남겨놓고 숨겨야 안전한 코드가 된다. 또 하나는 같은 이름을 가졌지만 다른 기능을 하는 것들의 충돌을 방지할 수 있다.
자바스크립트에서 공식적으로 블록 스코프를 지원하지 않더라도 불필요하게 다른 스코프를 오염시키지 않도록 블록스코프를 지향해야 한다.
try catch
에서 catch문의 인수로 사용되는 error 객체는 catch에서만 접근 가능한 블록 스코프다.
let, const 같은 ES6 변수 선언 문법은 호이스팅을 따르지 않는다.
1function variableHoisting() {2 console.log(_var);3 console.log(_let); // Reference Error4 console.log(_const); // Reference Error5 6 var _var = 'var';7 let _let = 'let';8 const _const = 'const';9}1011variableHoisting();
많은 메모리를 잡아먹는 작업이 한번 수행된 뒤 필요 없는 경우에 따로 블록으로 감싸서 가비지콜렉션이 진행되도록 하는 것이 효과적이다.
자바스크립트 엔진은 컴파일을 먼저 한 다음 코드를 인터프리팅 한다. 선언문은 컴파일 단계에서 처리되고 대입문은 실행단계에서 처리된다. 호이스팅이란 변수와 함수 선언문은 선언된 위치에서 코드의 꼭대기로 '끌어올려' 지는 동작을 말한다. 호이스팅은 스코프별로 작동한다. 함수 선언문은 정상적으로 끌어올려지지만 함수 표현식은 다르다.
1foo(); // not ReferenceError, but TypeError2var foo = function bar() {}
foo 라는 변수는 존재하기 때문에 ReferenceError 가 호출되지 않지만 foo가 뭔지 모르는데 실행하려고 하기 때문에 TypeError가 난다.
함수와 변수선언문은 모두 끌어올려진다. 여기서 함수가 먼저 끌어올려지고 그 다음으로 변수가 끌어올려진다. 중복된 함수 선언문은 먼저 선언된 함수를 덮어 쓴다. 이렇듯 같은 스코프 내에서의 중복 정의는 혼란스러운 결과를 초래한다.
1function foo() {2 var a = 2;3 function bar() {4 return a;5 }67 return bar;8}910var baz = foo();1112console.log(baz()); // baz -> bar -> foo의 a값 에 접근할 수 있다.
1for(var i=0; i<10; i++) {2 setTimeout(function () {3 console.log(i); // 10만 10번 찍힌다.4 }, 0);5}67for (let i=0; i<10; i++) {8 setTimeout(function() {9 console.log(i); // let이 각자의 블록스코프를 10개 만드므로 순서대로 값이 찍힌다.10 }, 1000);11}
1var MyModules = (function Manager() {2 var modules = {};34 function define(name, deps, impl) {5 for (var i=0; i<deps.length; i++) {6 deps[i] = modules[deps[i]];7 }8 modules[name] = impl.apply(impl, deps); // 의존성을 인자로 넘겨 모듈에 대한 정의 래퍼 함수를 호출하여 반환 값인 모듈 API를 이름으로 정리된 내부 모듈리스트에 저장한다.9 }10 function get(name) {11 return modules[name];12 }1314 return {15 define: define,16 get: get17 }18})();1920MyModules.define('bar', [], function () {21 function hello(who) {22 return 'Let me introduce: ' + who;23 }2425 return {26 hello: hello27 };28});2930MyModules.define('foo', ['bar'], function (bar) {31 var hungry = 'hippo';3233 function awesome() {34 console.log(bar.hello(hungry).toUpperCase());35 }36 37 return {38 awesome: awesome39 };40});4142var bar = MyModules.get('bar');43var foo = MyModules.get('foo');4445console.log(bar.hello('hippo'));46foo.awesome();
import
, export
)이 추가되었다. 기존의 함수 기반 모듈은 동적으로 동작하여 런타임에서 수정할 수 있지만 최신 문법은 정적으로 컴파일러 단계에서 동작한다.document.createElement()
를 통해 생성된 객체는 DOM요소를 가리키는 특별한 호스트 객체다.