TIL) Scope 뽀개기

Solmii·2020년 5월 30일
0

JavaScript

목록 보기
15/24
post-thumbnail

Scope 란?


함수의 매개변수를 비롯한, 모든 변수는 코드의 일정 범위 안에서만 유효하다는 성질을 갖는다.

이렇게, 특정 변수를 선언하고 사용할 수 있는 코드 상의 유효 범위스코프(scope)라고 한다.

( 이는 JavaScript의 문법이 아니라 '변수가 어디까지 쓰일 수 있는지'의 범위를 의미한다. )


Block

block이란 중괄호{ }(curly brace)로 감싸진 것을 말한다.

예시를 보면 바로 감이 온다.

function hi() {
  return 'i am block';
} // function의 내부는 하나의 block
for (let i = 0; i < 10; i++) {
  count++;
} // for문 내부도 하나의 block
if (i === 1) {
  let j = 'one';
  console.log(j);
} // if문의 내부도 하나의 block 

{ }(block)내부에서 변수가 정의되면 변수는 오로지 { }(block)내부에서만 사용할 수 있다.


Local(지역) Scope

function add(x, y) { // 변수 `x`와 `y`가 정의됨
  return x + y;
}

add(2, 3); // 5
console.log(x); // error

{ }(block)내부에서 정의된 변수를 local(지역) 변수라고 부른다.

{ }(block) 으로 감싸진 변수이고, 당연히 { }(block) 내부에서만 사용할 수 있다.


Global(전역) Scope

const color = 'red';
console.log(color); // red

function returnColor() {
  console.log(color); // red
  return color;
}

console.log(returnColor()); // red

{ }(block) 밖인 global scope에서 정의된 변수를 global variable(전역변수)라고 한다.

{ }(block) 외부에서 정의되었기 때문에 당연히 { }(block) 외부에 접근할 수 있다.

지역 변수와 다른 점은 Global(전역) Scope 는 { }(block) 내부에도 자유자재로 접근할 수 있다는 것!

위의 예시에서 color 라는 변수는 global 변수이기 때문에 returnColor함수의 block에서도 접근이 가능해서 'red'를 반환한 것이다.


Scope Chain (스코프 연쇄)

const five = 5;
function add5(x) {
  return x + five; // 바깥 스코프의 `five` 변수에 접근
}
add5(3); // 8

⇒ 다시 한번 Global(전역) Scope 예시가 나왔는데, 이처럼 함수 내부 코드에서 무조건 매개변수 혹은 지역 변수만 사용할 수 있는건 아니다.

이렇게 함수는 x = five 를 return 하기 위해 열심히 five 변수를 찾다가....
함수 내부에서 찾을 수 없으면 바깥 스코프에 접근해서 함수 바깥의 변수 five 를 가져와 사용한다.

👉 이런 함수가 여러 겹 중첩(nested)되어 있다면 어떨까?

const five = 5; // 난 global variable!
function add5(x) { 
  function add(y) { // 난 y! x가 누구지? 하고 바깥 scope를 봤더니 x는 add5 함수의 매개변수고 인자는 3이네! 
    return x + y;
  }
  return add(five); // 난 x! add 함수의 매개변수로 five를 줄거야! 바깥 scope를 봤더니 five는 5네!
}
add5(3); // 8

위 예시에서 코드의 실행 흐름을 알 수 있다.

위에서부터 순차적으로 진행되다가 x는 five , y는 x 라는 모르는 친구를 만난다.

(모르는 친구 = 우리 반 아닌 친구 = 현재 { } 내에 없는 변수)

그 모르는 친구를 찾기 위해 x 와 y는 바로 바깥쪽 scope에 접근하고, 바깥쪽 scope에 있으면 사용하고 없으면 그 다음 바깥쪽 scope에 접근해서 찾아보는...

이런 과정을 되풀이한다. 이 과정이 바로 스코프 연쇄(scope chain)!!

그리고 가장 바깥에 있는 scope를 최상위 스코프(top-level scope) 혹은 전역 스코프(global scope)라고 부르고 전역 스코프에서 선언된 변수를 전역 변수(global variable)라고 한다.
위 예시에서 five 가 바로 전역 스코프에서 선언된 전역 변수인 것!


Scope pollution (Scope의 오염)

전역 변수(global Variable) 에 대해 주의해야할 점이 있다!
전역 변수는 해당프로그램의 어디에서나 사용할 수 있는 global namespace를 갖는다.
변수를 명시적으로 전역 스코프에서 선언하지 않더라도, 한 번도 선언되지 않은 이름으로 안쪽 스코프에서 let, const, var를 붙여주지 않고 변수를 선언하면 전역 변수가 되어버린다.

function func() {
  variable = 1; // `variable`이라는 변수가 선언된 적 없으므로, 전역 변수가 된다.
}

func();
console.log(variable); // 1 - scope 내부 변수에는 접근할 수 없어야 하는데 variable가 전역 변수가 되어 버려서 접근 가능

전역 변수는 코드의 어떤 부분에서든 아무런 제한 없이 접근하고 조작할 수 있다.
마치 WeWork 1층 한복판에 누구나 제약 없이 쓸 수 있는 맥북을 갖다 놓는것.....

let stars = 'North Star';

const callMyNightSky = () => {
  stars = 'Sirius'; // stars 변수의 값을 바꿔 버림
  
  return 'Night Sky: ' + stars;
};

console.log(callMyNightSky()); // Night Sky: Sirius
console.log(stars); // Sirius

혹은 이런 경우도 있다.

(1) callMyNightSky함수에서 새로운 stars 변수를 선언하고 싶었는데 깜빡하고 let 키워드를 작성하지 않았다...😭

(2) callMyNightSky을 호출하면 stars 변수에 "Sirius" 가 할당된다.

(3) 전역 변수 였던 stars 가 수정된 것...! 다른 함수에서 전역 변수인 stars을 사용하면 값이 수정된 "Sirius" 으로 사용하게 된다.

(4) 전역 변수는 프로그램이 종료될때까지 계속 살아있는 반면에, 지역 변수는 { } (block)이 끝나면 더 이상 변수가 살아있지 않고 쓸 수 없게 된다.

🤨 전역 변수가 계속 살아있다면, 다음과 같은 문제점이 생긴다.

  • 전역 변수에 아무런 제한 없이 접근할 수 있으므로, 프로그램의 크기가 커짐에 따라 변수의 값이 어디서 어떻게 변경될지 예측하기 힘듬
  • 전역 변수를 통해 프로그램의 너무 많은 부분이 결합(coupling) 되버림.
    (예를 들어, A.js 파일을 고쳤는데 아무런 상관도 없어 보이던 B.js 파일의 코드가 오동작하게 될 수도 있다.)
  • 코드가 전혀 다른 곳에 위치한 부분에 의존하게 되므로, 전역 변수를 사용한 코드는 가독성이 떨어지고 이해하기 어렵다.

좋은 Scoping 습관 😁
위와 같이 전역 변수가 여기저기서 수정되면 안되기 때문에 변수들은 block scope으로 최대한 나눠놔야 한다.

타이트한 scope(tightly scoping)의 변수는 코드의 품질을 UP! 시켜준다.

  • 코드가 block으로 명확하게 구분되기 때문에 코드 가독성이 좋다.
  • 코드를 단순히 나열된 것이 아니라 각각의 기능별로 block을 나누기 때문에 코드를 이해하기 쉽다.
  • 나중에 코드를 수정할 일이 있을 때, 코드를 오랜만에 보더라도 block 별로 잘 나뉘어 있어서 유지보수가 쉽다.
  • 프로그램이 끝날때까지 변수가 살아있는 것이 아니라서(block이 끝나면 지역 변수의 삶이 end...) 메모리도 절약된다.

Variable Shadowing (변수 가리기)

같은 { } 내의 단일 scope에서는 같은 이름을 갖는 서로 다른 변수가 존재할 수 없다.
하지만 스코프 연쇄가 일어나는 특성을 이용하면 같은 이름의 서로 다른 변수를 만들 수 있다.

let currencySymbol = "$";

function showMoney(amount) {
  let currencySymbol = "₩"; // currencySymbol 이라는 변수가 다시 정의됨
  console.log(currencySymbol + amount);
}

showMoney("500"); // ₩500
console.log(currencySymbol + "1000"); // $1000

showMoney 함수 내부에서 currencySymbol 변수를 재 정의 한것처럼, 바깥쪽 scope에 존재하는 변수와 같은 이름을 갖는 변수를 안쪽 scope에서 재정의할 수 있다.

그러면 안쪽 scope에서는 바깥쪽 scope에 있는 이름이 무시된다.

이런 현상을 변수 가리기(Variable Shadowing) 하고 한다.


Lexical Scoping (어휘적 스코핑)

scope는 코드가 작성된 구조에 의해서 결정되는 것이지, 함수 호출의 형태에 의해 결정되는 것이 아니다.

뭔말인고 하니....

function add5(x) {
  const five = 5;
  return add(x);
}

add5(3); // error (add is not defined)

function add(x) {
  return five + x; // error (five is not defined)
}

add라는 함수가 add5라는 함수 안에서 호출되었어도, add 함수 내부에서 add5 함수의 스코프 안에 있는 변수에 접근할 수는 없다.

스코프는 코드가 작성된 구조에 의해 결정되는 성질이기 때문에 아래와 같이 작성해야 한다.

function add5(x) {
  const five = 5;
  function add(x) {
    return five + x;
  }
  return add(x);
}
add5(3); // 8

개발 왕초보 코린이입니다!
이 내용은 혼자 동영상 강의&구글링을 통해 배운 내용을 정리하는 것으로, 제가 이해하고 넘어간 개념이 틀렸거나 더 보충할 개념이 있다면 댓글 남겨주시면 정말 감사하겠습니다!!

profile
하루는 치열하게 인생은 여유롭게

0개의 댓글