스코프[Learning JavaScript]

스코프

  • 스코프는 변수와 상수, 매개변수가 언제 어디서 정의되는지 결정한다.
// 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';
  
  //코드를 전부 이 안에 작성
  //이 코드는 스트릭트 모드로 동작
  //이 코드와 함께 동작하는 다른 스크립트는 스트릭트 모드의 영향받지 않음.
});