스코프
- 스코프는 변수와 상수, 매개변수가 언제 어디서 정의되는지 결정한다.
// x의 스코프가 함수 f라고 한다.
function f(x) {
return x+3;
}
f(5); //8
x; //ERROR
- 변수의 스코프가 어떤 함수라고 말할 때는, 함수를 실제 호출할 떼까지는 함수 바디의 정해진 매개변수가 존재하지 않음을 나타낸다.
- 함수를 호출할 때마다 매개변수가 나타나고, 함수가 제어권을 반환하면 스코프밖으로 사라진다.
스코프와 존재
- 아직 선언하지 않은 변수나 함수가 종료되면서 존재하지 않게 된 변수는 스코프 안에 ‘있지 않다.’
- 변수가 스코프 안에 있지 않다는 것이, 그 변수가 존재하지 않는다는 말은 아니다.
- 존재한다는 말은 그 식별자가 메모리가 할당된 무언가를 가리키고 있다는 뜻
- 스코프는 프로그램의 현재 실행 중인 부분, 즉 실행 컨텍스트에서 현재 보이고 접근할 수 있는 식별자들을 말한다.
정적 스코프와 동적 스코프
- 프로그램의 소스 코드를 살펴보는 건 그 프로그램의 정적 구조를 살펴보는 것이다.
- 자바스크립트의 스코프는 정적이다. 소스 코드만 봐도 변수가 스코프에 있는지 판단할 수 있다는 뜻이다.
- 정적 스코프는 어떤 변수가 함수 스코프 안에 있는지 함수를 정의할 때 알 수 있다.
//변수 x는 함수 f를 정의할 때 존재하지만, y는 다른 스코프에 존재한다.
//함수 f는 자신이 정의될 때 접근할 수 잇었던 식별자에는 여전히 접근할 수 있지만,
//호출할 때 스코프에 있는 식별자에 접근할 수는 없다.
const x = 3;
function f() {
console.log(x);
console.log(y);
}
{ //새 스코프
const y = 5;
f();
}
//결과 : y is undefined
- 자바스크립트의 정적 스코프는 전역 스코프와 블록 스코프, 함수 스코프에 적용된다.
전역 스코프
- 프로그램을 시작할 때 암시적으로 주어지는 스코프.
- 어떤 함수도 호출하지 않았을 때 실행 흐름은 전역 스코프에 있다.
- 전역 스코프에서 선언한 것은 무엇이든 프로그램의 모든 스코프에서 볼 수 있다.
- 전역 스코프에서 선언된 것들을 전역 변수라고 한다.
- 전역 스코프에 의존하는 것은 피해야한다.
//사용자 정보를 보관하는 프로그램. (전역 변수를 사용하는 방법)
let name = "Irena"; //전역
let age = 25; //나이
function greet() {
console.log(`Hello, ${name}`);
}
function getBirhYear() {
return nae Date().getFullYear() - age;
}
- 프로그램 어디에서든 상관없이 name값을 바꿀 수 있다.
- 프로그램의 다른 부분에서 name과 age를 정확히 사용한다고 가정하고 있다.
//사용자 정보를 단일 객체에 보관하는 방법
let user = {
name = "Irena" ,
age = 25
};
function greet() {
console.log(`Hello, ${user.name}`);
}
function getBirthYear() {
return new Date().getFullYear() - user.age;
}
- 전역 스코프의 식별자 숫자를 하나 줄였을 뿐, 개선해야한다.
//geet()와 getBirhYear()를 전역 스코프에 의존하지 않게 만든다.
function greet(user) {
console.log(`Hello, ${user.name}`);
}
function getBirthYear(user {
return new Date().getFullYear() - user.age;
}
블록 스코프
- 중괄호로 묶은 블록의 스코프에서만 보이는 식별자
{
const x = 3;
console.log(x); //3
}
console.log(x); //ERROR
변수 숨기기
- 다른 스코프에 있으면서 이름이 같은 변수나 상수는 혼론을 초래한다.
- 스코프 하나가 끝난 다음에 다른 스코프가 있는 식이라면 단순하다.
{
//block 1
const x = 'blue';
console.log(x); // "blue"
}
console.log(typeof x); // "ERROR"
{
//block 2
const x = 3;
console.log(x); //3
}
console.log(typeof x); // "ERROR"
- 스코프가 중첩되는 경우
{
//외부 블록
let x = 'blue';
console.log(x); // "blue"
{
//내부블록
let x = 3;
console.log(x); // "3"
}
console.log(x); // "blue"
}
console.log(typeof x); // "ERROR"
- 내부 블록의 x는 외부 블록에서 정의한 x와 이름만 같을 뿐 다른 변수이므로 외부 스코프의 x를 숨기는 효가가 있다.
- 실행 흐름이 내부 블록에 들어가 새 변수 x를 정의하는 순간, 두 변수가 모두 스코프 안에 있다.
{
//외부 블록
let x = { color : "blue" };
let y = x; //y와 x는 같은 객체를 가리킨다.
let z = 3;
{
//내부 블록
let x = 5; //이제 바깥의 x는 가려졌다.
console.log(x); // 5
console.log(y.color); // "blue"
// 외부 스코프의 x가 가리키는 객체는 스코프안에 있다.
y.color = "red";
console.log(z); // 3
}
console.log(x.color); //red, 객체는 내부 스코프에서 수정되었다.
console.log(y.color); //red, x와 y는 같은 객체를 가리킨다.
console.log(z); // 3
}
- 스코프의 계층적인 성격 때문에 어떤 변수가 스코프에 있는지 확인하는 스코프 체인이란 개념이 생겼다.
함수, 클로저, 정적스코프
- 함수가 특정 스코프에 접근할 수 있도록 의도적으로 그 스코프에서 정의하는 경우가 많다.
- 스코프를 함수 주변으로 좁히는 것이 클로저
let globalFunc; //정의되지 않은 전역 함수
{
let blockVar = 'a'; //블록 스코프에 있는 변수
globalFunc = function() {
console.log(blockVar);
}
}
globalFunc(); //"a"
- globalFunc는 블록안에서 값을 할당 받았다. 이 블록 스코프와 그 부모인 전역 스코프가 클로저를 형성한다.
- 스코프 안에서 함수를 정의하면 해당 스코프는 더 오래 유지된다.
let f;
{
let o = { note : 'Safe' };
f = function() {
return o;
}
}
let oRef = f(); //클로저는 일반적으로는 접근할 수 없는 것에 접근할 수 있는 효과!
oRef.note = "Not so safe after all!";
즉시 호출하는 함수 표현식(IIFE)
- 함수 표현식을 사용하면 즉시 호출하는 함수 표현식(IIFE)이란 것을 만들 수 있다.
- IIFE는 함수를 선언하고 즉시 실행한다.
//IIFE 형태
(function() {
//IIFE 바디
});
- 함수 표현식으로 익명 함수를 만들고 그 함수를 즉시 호출한다.
- 내부에 있는 것들이 모두 자신만의 스코프를 가지지만, IIFE 자체는 함수이므로 그 스코프 밖으로 무언가를 내보낼 수 있다.
const message = (function() {
const secret = "secret";
return `${secret.length} charactoers long`;
})();
console.log(message);
- 변수 secret은 IIFE의 스코프 안에서 보호되어 외부에서 접근할 수 없다.
//자신의 호출 횟수를 반환하는 함수
const f = (function() {
let count = 0;
return function() {
return `${++count}`;
}
})();
f(); // 1
f(); // 2
- 변수 count는 IIFE 안에 안전하게 보관되어 있어 손댈 방법이 없다.
- f는 자신이 몇 번 호출됐는지 항상 정확히 알고 있다.
- 클로저를 만들고 클로저에서 무언가 반환받을 때에 유용하게 쓰인다.
함수 스코프와 호이스팅
- let으로 변수를 선언하면, 그 변수는 선언하기 전에는 존재하지 않는다.
- var로 선언한 변수는 현재 스코프 안이라면 어디서든 사용할 수 있으며, 심지어 선언되기도 전에 사용할 수 있다.
let var1;
let var2 = undefined;
var1; // undefined
var2; // undefined
undefinedVar; //ERROR : notDefined
//let을 쓰면, 변수를 선언하기 전 사용하려 할 때 에러가 난다.
x; //ERROR : x는 정의되지 않았다.
let x = 3;
// var로 변수를 선언하면 선언하기 전에도 사용할 수 있다.
x; //undefined
var x = 3;
x; //3
- var로 선언한 변수는 끌어올린다는 뜻의 ‘호이스팅’이라는 메커니즘을 따른다.
- 자바스크립트는 함수나 전역 스코프 전체를 살펴보고 var로 선언한 변수를 맨 위로 끌어올린다.
- 선언만 끌어올려지는 것이며, 할당은 끌어올려지지 않는다.
//원래 코드
if(x !== 3) {
console.log(y);
var y = 5;
if(y === 5) {
var x = 3;
}
console.log(y);
}
if(x === 3) {
console.log(y);
}
//위 코드를 자바스크립트가 해석한 코드
var x;
var y;
if(x !== 3) {
console.log(y);
y = 5;
if(y === 5) {
x = 3;
}
console.log(y);
}
if(x === 3) {
console.log(y);
}
- var를 이용해 변수를 선언하면 자바스크립트는 같은 변수를 여러 번 정의하더라도 무시한다.
//원래 코드
var x = 3;
if(x === 3) {
var x = 2;
console.log(x);
}
console.log(x);
//위 코드를 자바스크립트가 해석한 코드
var x;
x = 3;
if(x === 3) {
x = 2;
console.log(x);
}
console.log(x);
- 같은 함수나 전역 스코프 안에서는 var로 새 변소를 만들 수 없으며, let으로 가능했던 변수 숨김도 불가능하다.
함수 호이스팅
- 함수 선언도 스코프 맨 위로 끌어올려진다.
- 함수를 선언하기 전에 호출할 수 있다
f(); //'f'
function f() {
console.log('f');
}
- 변수에 할당한 함수 표현식은 끌어올려지지 않는다. 변수의 스코프 규칙을 그대로 따른다.
f(); //ERROR : f는 정의되지 않았다.
let f = function() {
console.log('f');
}
사각지대
- let으로 선언하는 변수는 선언하기 전까지 존재하지 않는다는 직관적 개념을 나타내는 표현
- 스코프 안에서 변수의 사각지대는 변수가 선언되기 전의 코드
//let 키워드가 도입되기 전 let으로 변수를 선언하지 않은 코드
if(typeof x === "undefined") {
console.log("x is undefined");
} else {
//x를 사용해도 안전한 코드
}
//let 키워드가 도입되고 let으로 변수 선언을 한 코드
if(typeof x === "undefined") { // (ERROR : x는 정의되지 않았다.)
console.log("x is undefined");
} else {
// x를 사용해도 안전한 코드
}
let x = 5;
스트릭트 모드
- ES5 문법에서는 암시적 전역변수라는 것이 생길 수가 있다.
- var로 변수를 선언하는 것을 잊으면 자바스크립트는 전역 변수를 참조하려 한다고 간주하고, 그런 전역 변수가 존재하지 않으면 스스로 만들었다.
- 이런 이유로 암시전 전역 변수를 허용하지 않는 ‘스트릭트 모드’를 도입했다.
- 스트릭트 모드를 사용하려면 문자열 “use strict” 코드 맨 앞에 쓰면 된다.
- 전역 스코프에 “use strict”를 사용하면 스크립트 전체가 스트릭트 모드로 실행된다.
(function() {
'use strict';
//코드를 전부 이 안에 작성
//이 코드는 스트릭트 모드로 동작
//이 코드와 함께 동작하는 다른 스크립트는 스트릭트 모드의 영향받지 않음.
});