💘 I N D E X
- 스코프는 식별자 언제 어디서 정의되는지에 따라 결정한다.
- 변수는 자신이 선언된 위치에 의해 자신이 유효한 범위, 즉 다른 코드가 변수 자신을 참조할 수 있는 범위가 결정된다. 이는 변수를 포함한 모든 식별자에 해당된다.
- 모든 식별자(변수 이름, 함수이름, 클래스 이름 등)는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위가 결정된다.
- 즉, 스코프는 식별자가 유효한 범위를 말한다.
- 유효한 참조범위 라고 하면 정리가 될 것 같다.
-
var : 함수 레벨 스코프(function-level scope)
- 함수 내에서 선언된 변수는 함수 내에서만 유효하다.
- 함수 내의 블록과 상관없음.
- 함수 외부에서는 참조할 수 없음.
-
let, const : 블록 레벨 스코프(block-level scope)
- if문, for문, while문, try/catch문, 함수 내부의 블록 등, 블록 내부에서 선언된 변수는 코드 블록 내에서만 유효하다.
- 코드 블록 외부에서는 참조할 수 없다.
-
예시1)
function f(x){ //var x; console.log(x + 3); } f(5) //8 x; //ReferenceError: x is not defined
- 여기에서 x는 함수 내부에만 존재한다.
- x의 스코프는 함수라고 말할 수 있다.
- x는 함수 내부에서 하나의 변수처럼 사용이 된다.
- f(5)는 함수 내부에서 var x=5로 동작한다.
-
예시2)
function add(x, y) { //var x, y; // 매개변수는 함수 몸체 내부에서만 참조할 수 있다. // 즉, 매개변수의 스코프(유효범위)는 함수 몸체 내부다. console.log(x, y); // 2 5 return x + y; } add(2, 5); // 매개변수는 함수 몸체 내부에서만 참조할 수 있다. console.log(x, y); // ReferenceError: x is not defined
-
예시 3)
var var1 = 1; // 코드의 가장 바깥 영역에서 선언한 변수 if (true) { var var2 = 2; // 코드 블록 내에서 선언한 변수 if (true) { var var3 = 3; // 중첩된 코드 블록 내에서 선언한 변수 } } function foo() { var var4 = 4; // 함수 내에서 선언한 변수 **///만약 이곳에서 var5를 참조한다면? console.log('outside var5 : ',var5);** function bar() { var var5 = 5; // 중첩된 함수 내에서 선언한 변수 } } console.log(var1); // 1 console.log(var2); // 2 console.log(var3); // 3 console.log(var4); // ReferenceError: var4 is not defined console.log(var5); // ReferenceError: var5 is not defined foo(); //ReferenceError: var5 is not defined
- var는 함수 스코프이다.
- var 1,2,3은 전역 변수가 된다.
- var 4는 함수 내부에서 선언된 변수이므로(스코프가 함수 foo로 한정됨) 외부에서 참조할 수 없어 에러가 난다.
- var5 또한 함수 내부의 변수이므로 외부에서 참조가 불가능하다.
- 만약 bar함수 외부이면서 foo함수 내부인 범위에서 var5를 참조한다면 에러가 날 것이다.
- var5는 bar함수의 스코프이기 때문이다.
-
코드안에 같은 이름을 가진 변수가 각각 다른 스코프 안에 존재할 때, 자바스크립트 엔진은 스코프를 통해 어떤 변수를 참조할 것인지 결정한다.
var x = 'global'; function foo() { var x = 'local'; console.log(x); // ① } foo(); console.log(x); // ②
- 위의 예제에서 외부에 선언된 x와, 함수 내부에 선언된 x는 스코프가 서로 다르다.
- 1번은 전역변수로 어디에서든 참조할 수 있고
- 2번은 함수 내부에서만 참조할 수 있다.
-
자바스크립트 엔진은 코드를 실행할 때 문맥을 고려한다(context)
-
코드가 어디에서 실행되고 주변에 어떤 코드가 있는지에 따라 위의 예제처럼 각각의 x에 대해 다른 결과를 만들어낸다.
-
코드가 어디에서 실행되고 주변에 어떤 코드가 있는지 ⇒ 렉시컬 환경
-
즉, 코드의 문맥은 렉시컬 환경으로 만들어진다.
-
이를 구현한 것이 실행 컨텍스트이다.
-
모든 코드는 실행 컨텍스트에서 평가되고 실행된다.
-
자세한 사항은 23장에서 계속된다.
-
코드내에 같은 이름을 가진 식별자가 여럿 존재할 수 있지만,
-
한 스코프 내에서의 식별자는 유일해야한다.
-
즉, 스코프는 네임스페이스라고 할 수 있다.
- var키워드로 선언된 변수는 같은 스코프 내에서 중복 선언이 허용된다.
- 만약 중복으로 선언되면, 기존의 변수에 재할당이 된다.
function foo() {
var x;
var x= undefined;
x=1;
x=2;
var x = 1;
// var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용한다.
// 아래 변수 선언문은 자바스크립트 엔진에 의해 var 키워드가 없는 것처럼 동작한다.
var x = 2;
console.log(x); // 2
}
foo();
- 하지만 let이라 const로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용하지 않는다,
function bar() {
let x = 1;
// let이나 const 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용하지 않는다.
let x = 2; // SyntaxError: Identifier 'x' has already been declared
}
bar();
-
스코프는 전역과 지역으로 구분할 수 있다.
구분 설명 스코프 변수 전역 코드의 바깥 영역 전역 스코프 전역 변수 지역 함수 몸체 내부 지역 스코프 지역 변수 -
변수는 자신이 선언된 위치에 의해 자신이 유효한 범위가 결정된다.
- 전역이란 코드의 가장 바깥 영역을 말한다.
- 전역은 전역 스코프를 만든다.
- 전역에 변수를 선언하면 그 변수는 전역 스코프를 갖는다.
- 전역 변수는 어디서든 참조할 수 있다.
- 프로그램을 시작할 때 암시적으로 전체를 아우르는 글로벌 스코프
- 전역 스코프에서 선언된 것들이 전역변수이다.
- 전역스코프에 의존하는 것을 피해야한다. 남용하면 안됨
- 전역 변수를 사용하면 의도치 않게 다른 곳에서 변수에 영향을 줄 수 있기 때문이다.
- 전역 변수를 쓰려면 차라리 단일 객체에 넣어 사용하는 편이 안전하다.
- 수많은 객체에 일일이 변수를 담는 것을 비효율 적이다.
- 함수의 매개변수를 이용해 함수 스코프를 사용하는 방법도 있다.
- 지역이란 함수 몸체 내부 영역이다.
- 지역은 지역 스코프를 만든다.
- 지역에 변수를 선언하면 그 변수는 지역 스코프를 갖는 지역 변수가 된다.
- 지역 변수는 자신이 선언된 지역과 그 하위 지역(중첩 함수)에서만 참조할 수 있다.
- 즉, 지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서만 유효하다.
- 함수는 중첩될 수 있으므로, 함수의 지역 스코프도 중첩될 수 있다.
- 이는 스코프가 함수의 중첩에 의해 계층적 구조를 갖는다는 것을 의미한다.
- 중첩 함수의 지역 스코프는 중첩 함수를 포함하는 외부 함수의 지역 스코프와 계층적 구조를 갖는 것이다.
- 이때, 외부 함수의 지역 스코프를 중첩 함수의 상위 스코프라고 한다.
- 모든 스코프는 하나의 계층적 구조로 연결되며, 모든 지역 스코프의 최상위 스코프는 전역 스코프이다.
- 이렇게 스코프가 계층적으로 연결된 것을 스코프 체인이라고 한다.
-
변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색한다.
-
이를 통해 상위 스코프에서 선언한 변수를 하위 스코프에서도 참조할 수 있는 것이다.
-
최상위 글로벌 스코프에 도달하면, 변수를 찾지 못했어도 검색을 멈춘다.
-
스코프 체인은 물리적인 실체로 존재한다.
-
자바스크립트 엔진은 위 그림과 유사한 자료구조인 렉시컬 환경을 실제로 생성한다.
-
변수 선언이 실행되면 식별자를 키값으로 저장하고, 변수 할당이 일어나면 해당 키의 값으로 저장한다.
-
상위 스코프에서 유효한 변수는 하위 스코프에서는 참조할 수 있지만, 하위 스코프에서 유효한 변수를 상위 스코프에서 참조할 수 없다.
// 전역 함수
function foo() {
console.log('global function foo');
}
function bar() {
// 중첩 함수
function foo() {
console.log('local function foo');
}
foo(); // ① 내부의 foo함수를 가리킨다.
}
bar();//① - 'local function foo'
- 선언문으로 함수를 정의하면 런타임 이전에 함수 객체가 먼저 생성된다.(호이스팅)
- 자바스크립트 엔진은 함수 이름과 동일한 이름의 식별자를 암묵적으로 선언하고 함수 객체를 할당한다.
- 1번의 foo함수를 호출하면, 자바스크립트 엔진은 함수를 호출하기 위해 먼저 함수를 가리키는 식별자 foo를 검색한다.
- 본인이 속한 스코프부터 검색하므로 내부의 foo함수를 찾아 실행한다.
- 이처럼 함수도 식별자이기 때문에 함수 자체도 본인의 스코프를 갖는다.
💡 다른 책에서 스코프 체인을 클로저로 설명하고 있어서 내용을 가져왔습니다.
사실 클로저에 대해 설명하려면 더 자세한 내용이 있어야하지만 나중에 다른 챕터에서 다루므로 여기에서는 스코프 체인의 개념으로 이 섹션을 이해해주시면 되겠습니다.
-
클로저
- 함수가 특정 스코프에 접근할 수 있도록 의도적으로 그 외부의 스코프에서 정의함.
- 코어 자바스크립트에서는 함수의 내부 환경과,
- 외부 스코프 환경 중에 함수가 참조하는 것을 합쳐서 스코프라고 한다.
- 즉, 함수가 실행되기에 필요한 것들만 스코프로 만들어놓은 것이 클로저이다.
let globalFunc; //정의되지 않은 전역 함수 { let blockVar = 'a'; //블록스코프 내부 변수 globalFunc = function(){ console.log(blockVar) } } globalFunc(); //a
- globalFunc은 정의되어 있지 않다가 블록 내부에서 값이 할당되었다.
- 이 블록스코프와 그 부모인 전역스코프가 클로저를 형성한다.
- globalFunc는 외부의 전역 스코프에서 가져오기 때문이다.
- 전역의 전체가 클로저가 아니고, 전역 스코프 중에 globalFunc 등 내부 스코프(블록스코프)에서 사용하는 변수 환경만을 이야기한다.
- 클로저가 형성되었기 때문에 globalFunc함수는 클로저에 들어있는 식별자에 접근이 가능하다.
- 그래서 외부 전역에서 globalFunc을 실행시켜도 blockVar에 접근하여 a를 반환하는 것이다.
- 코드의 실행이 끝나면 스코프도 끝나고, 스코프가 끝나면 그 안의 변수 또한 gc로 인해 메모리에서 제거된다.
- 위의 예제에서 블록스코프가 끝났음에도, 아래 구문(마지막줄)의 globalFunc에서 내부의 blockVar를 참조할 것이기에 스코프를 계속 유지해준다.
- 그래서 globalFunc를 실행해도 blockVar를 반환할 수 있었던 것이다.
- 함수 레벨 스코프란 지역 스코프가 코드 블록이 아닌 함수에 의해서만 생성된다는 의미이다.
- 자바스크립트에서는 var키워드로 선언된 변수는 오로지 함수의 코드 블록만을 지역 스코프로 인정하고, 다른 코드 블록(if, for, while....)은 지역 스코프를 만들지 않는다.
var x = 1;
if (true) {
// var 키워드로 선언된 변수는 함수의 코드 블록(함수 몸체)만을 지역 스코프로 인정한다.
// 함수 밖에서 var 키워드로 선언된 변수는 코드 블록 내에서 선언되었다 할지라도 모두 전역 변수다.
// 따라서 x는 전역 변수다. 이미 선언된 전역 변수 x가 있으므로 x 변수는 중복 선언된다.
// 이는 의도치 않게 변수 값이 변경되는 부작용을 발생시킨다.
var x = 10;
}
console.log(x); // 10
var i = 10;
// for 문에서 선언한 i는 전역 변수다. 이미 선언된 전역 변수 i가 있으므로 중복 선언된다.
for (var i = 0; i < 5; i++) {
console.log(i); // 0 1 2 3 4
}
// 의도치 않게 변수의 값이 변경되었다.
console.log(i); // 5 //먼저 i++한 뒤에 i<5조건을 보기때문에 i는 5가 된다.
-
함수 선언문은 그 자체로 호이스팅이 되지만,
-
변수에 함수를 할당하는 형태인 함수 표현식은 함수가 끌어올려지지 않는다.
- 변수는 호이스팅할 때, 변수만 위로 끌어올려져 선언한 뒤 초기화가 이루어진다.(undefined가 됨)
- 그 값인 함수는 해당 라인이 실행되는 시점에서야 변수에 할당된다.
- 그러므로 함수 표현식은 호이스팅 되지 않는다고 하는 것이다.
- 정확히는 함수표현식에서 변수는 호이스팅되고, 할당되는 함수는 호이스팅되지 않는다고 해야할 것이다.
-
함수 선언문
myFunc(); //This is myFunc function myFunc(){ console.log('This is myFunc'); }
-
함수 표현식
myFunc(); **//TypeError : myFunc is not a function** var myFunc = function(){ console.log('This is myFunc'); }
- 블록스코프는 해당 블록의 스코프에서만 ‘보이는’ 식별자를 의미한다.
- let과 const는 식별자를 블록 스코프에서 선언한다.
console.log('before block');
{
console.log('inside block');
const x = 3;
console.log(x);
}
console.log(`outside block; x = ${x}`); //ReferenceError: x is not defined
//block1
const x = 'blue';
console.log(x);//blue
}
console.log(typeof x);//undefined;
{
//block2
console.log(x);//ReferenceError: x is not defined
}
console.log(typeof x);//undefined;
- 스코프는 어디서 어떻게 식별자를 찾는가를 결정하는 규칙의 집합이다.
- 자바스크립트는 렉시컬 스코프를 따른다. (렉시컬 스코프, 또는 정적 스코프라고 한다.)
- 함수를 어디서 호출했는지가 아니라 함수를 어디서 정의했는지에 따라 상위 스코프를 결정한다.
- 함수가 호출된 위치는 상위 스코프 결정에 어떠한 영향도 주지 않는다.
- 즉, 함수의 상위 스코프는 언제나 자신이 정의된 스코프인 것이다.
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // 1
bar(); // 1
- 자바스크립트는 렉시컬 스코프를 따르므로, 함수를 어디에서 정의했는지에 따라 상위 스코프를 결정한다.
- 함수가 호출된 위치는 상위 스코프를 결정하는 것에 어떠한 영향도 주지 않는다.
- 즉, 함수의 상위 스코프는 언제나 자신이 정의된 스코프이다.
- 위의 예제에서 bar함수는 전역에서 정의된 함수이다.
- 그러므로 bar의 상위 스코프는 전역 스코프이며, bar함수 내부에서 참조하는 x는 전역변수 x이다.
-
동적 스코프 : 함수를 어디에서 호출했는지에 따라 함수의 상위 스코프를 결정한다.
-
정적 스코프 : 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정한다.
-
예제 - 보이는 가시성과 존재의 차이
const x = 3; function f(){ console.log(x); //3 console.log(y); //ReferenceError: y is not defined } //새 스코프 { const y = 5; f(); }
-
변수 x는 함수 f를 정의할 때 존재하지만, y는 그렇지 않다.
-
x는 위의 예제에서 글로벌이다.
-
만약 f안에서 x를 선언했다면 함수 내부 스코프 안에 새로운 x를 선언하는 것이다.
-
일단 함수 내부에서 x를 찾는다.
-
함수 안에 x를 선언하지 않았으므로,(변수 x가 존재하지 않으므로) 함수 밖에서 x를 찾는다.
-
밖에서 const x = 3을 찾았다.
-
함수를 정의할 때 이미 x를 외부에서 찾아 가리키고 있다.
- 호출할 때가 아닌, 정의할 때 스코프를 알 수 있으므로 정적 스코프이다.
-
x는 존재하지만 함수 스코프 안에는 없는 변수인 것이다.
-
그러니 존재와 스코프는 구별해서 봐야한다.
-
y는 다른 블록 스코프 안에 존재한다.
-
다른 스코프에서 y를 선언하고 그 안에서 함수 f를 호출하더라도,
-
x는 함수의 스코프 안에 있지만, y는 여전히 레퍼런스 에러를 띄운다.
-
만약 실행할 때 스코프가 형성된다면 y또한 함수 내부에서 찾을 수 있다.
-
하지만 자바스크립트는 정적 스코프이므로, 함수가 선언될 당시에 접근가능한 식별자들로 스코프가 생성된다.
- 그래서 함수 내부에서 x는 존재하고, y는 존재하지 않는 것임.
-
정리하자면, 함수 f는 자신이 정의될 때 접근할 수 있었던 식별자에는 접근할 수 있지만, 호출할 때 스코프에 있던 식별자에는 접근할 수 없다. (이미 선언 당시에 스코프가 형성되기 때문)
-
-
자바스크립트의 정적 스코프는 아래에 적용된다.
- 전역 스코프 (global scope)
- 블록 스코프 (block scope)
- 함수 스코프 (function scope)
const x = 'this is global x';
const z = 'zzzz';
{
//외부 블록
const x = 'blue';
const y = 'red';
console.log(x); //blue
console.log(y); //red
console.log(z); //zzz
{
//내부 블록;
let x = 3;
console.log(x); //3
console.log(y); //red
console.log(z); //zzz
}
console.log(x); //blue
}
console.log(typeof y);//undefined;
- 위의 예제는 변수 숨김(variable masking)을 잘 보여준다.
- 내부 블록의 x는 외부 블록에서 정의한 x와 이름만 같을 뿐 다른 변수이다.
- 내부 블록 안에서는 외부 블록(외부스코프)의 x를 가리는(숨기는)효과가 있다.
- 중요한 것은, 실행 흐름이 내부 블록에 들어가 새 변수 x를 정의하는 순간, 두 변수가 모두 스코프 안에 있다는 것이다.
- 다만, 변수의 이름이 같으므로 외부 스코프에 있는 변수에 접근할 방법이 없을 뿐이다.