JavaScript : Scope & Closure

wonkeunC·2021년 11월 20일
0

JavaScript

목록 보기
11/15
post-thumbnail

🪴 Scope

스코프는 이름이 출동하는 문제를 덜어주고, 자동으로 메모리를 관리합니다.
변수 이름, 함수 이름, 클래스 이름과 같은 식별자본인이 선언된 위치에 따라 다른 코드에서 자신이 참조될 수 있을지 없을지를 결정되는 것이다.

목차

  • 변수의 종류와 특성
  • 전역변수와 지역변수
  • 스코프 Global,Local - Func,Block Level
  • 스코프 체인
  • 스코프 종류

🌿 변수의 종류와 특성

var boxA = 10;
boxA = 2000; // OK : 할당 가능

let boxB = 10;
boxB = 2000; // OK : 할당 가능

const boxC = 10;
boxC = 2000; // Error : const는 한번 값을 넣으면 변경할 수 없다. (변수 자체 값 변경 X)

const boxD = { test:10 }; // const boxD 안에 새로운 변수 text
boxD.test = 2000; // OK : 변수 내부의 값을 변경하는 것은 가능하다.
  • var과 let의 차이점
var a = 10;
var a = 20; // OK : var은 같은 이름으로 다시 선언해서 덮어 씌우는게 가능하다.

let b = 10;
let b = 20; // Error: 동일한 이름으로 재선언하는 것이 불가능하다.

2015년 let이 도입되는 시기만 해도 위 코드처럼 변수 재선언을 허용하는 것은 코드 가독성 측면에서 좋지 못하다는 의견이 많았다. 그렇기 때문에 let과 const는 재선언을 안되게끔 만들어졌다.
하지만, 최근 몇년 사이에 함수형 프로그래밍 페러다임이 점점 주류가 되어가고 있는데, 오히려 var처럼 변수의 재선언을 허용하는 것이 기능 측면에서 더 좋을 수 있다는 의견이 많아지고 있다.
(Variable shadowing)
뒤에 선언된 변수가 앞의 변수를 가려서 불필요한 사이드 이펙트를 방지하는 역할을 할 수 있기 때문이다.
즉, var 키워드가 오히려 재선언 기능상으로는 요즘 트렌드에 부합하다고 할 수 있다.
하지만 var이 이러한 장점이 있지만 여러 단점들도 있기 때문에 사용을 지양하고 있다.

대표적으로 스코프(Scope)에 대한 문제이다.




🌿 전역변수와 지역변수

  • 코드 전체 필드에서 전역과 지역의 개념이 있고 두 가지로 구분될 수 있다.

전역에서 선언된 변수는 전역변수라고 표현하며 해당 변수는 전역 스코프를 적용하게 되고
지역에서 선언된 변수는 지역변수라고 부르며 지역 스코프를 갖게 된다.

전역변수(global variable)
1. 전역 변수란 함수의 외부에서 선언된 변수를 의미합니다.
2. 전역 변수는 프로그램의 어디에서나 접근할 수 있으며, 프로그램이 종료되어야만 메모리에서 사라집니다.
3. 이러한 전역 변수는 메모리상의 데이터(data) 영역에 저장되며, 직접 초기화하지 않아도 0으로 자동 초기화됩니다.

지역변수(local variable)
1. 지역 변수란 '블록' 내에서 선언된 변수를 의미합니다.
2. 지역 변수는 변수가 선언된 블록 내에서만 유효하며, 블록이 종료되면 메모리에서 사라집니다.
3. 이러한 지역 변수는 메모리상의 스택(stack) 영역에 저장되며, 초기화하지 않으면 의미 없는 값(쓰레기값)으로 초기화됩니다.
4. 함수의 매개변수 또한 함수 내에서 정의되는 지역 변수로 취급됩니다.




🌿 스코프 Global,Local

  • Global Scope(전역 스코프)
    만일 변수가 모든 함수에 속하지 않고 { } 괄호안에 들어있지도 않다면 그 변수를 전역 변수라 부른다.
  • Local Scope(지역 스코프) - Function Scope, Block Scope
    코드 내 특정 구역에서만 사용할 수 있는 변수를 지역변수라고 한다.
    자바스크립트에서는 두가지 종류( 함수 스코프, 블럭 스코프 )의 지역 변수가 있다.

🔎 Function ScopeBlock Scope 비교하기

📝 다른 프로그래밍 언어에서는 Function, Block Scope를 둘로 나눠서 구분하지 않는다.
{ } 내부에 변수를 선언했는데 내부에 선언한 변수가 바깥으로 튀어나오는 것을 대부분 좋아하지 않는다.
그렇기 때문에 Function Scope를 가지고 있는 var 를 좋아하지 않는다.

"var"

function add () {
  ...function scope...
}
const eventHandler = () => {
  ...function scope...
}
  
------ ----- ----- ---- ---- ---- ----

"let, const"
  
if (condition) {
  ...block scope..
}
switch(item) {
  ...block scope...
}
for (const item of items) {
  ... block scope...
}
while (condition) {
  ...block scope...
}
  
"var는 Function Scope에서 동작하고"
"let, const는 Block Scope에서 동작한다"

Function Scope : var

함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다.
즉, 함수 내부에서 선언한 변수는 지역 변수이며 함수 외부에서 선언한 변수는 모두 전역 변수이다.

❗️ Function Scope에서 동작하는 varBlcok Scope에서 동작시키면 var은 블록 { } 을 생략 시켜버린다.

// Block Scope
var box = 10;

if(true) {
 var box = 2000;
  console.log(box); // 2000
}

console.log(box); // 2000 <-- 원래의 경우 10이 나와야 하는데 
// var이 { } 블록을 무시해서 블록 안에서 선언한 var box = 2000; 이 할당되었다.

✅ 하지만 var이 Function Scope에서 선언할 경우 함수 바깥과 독립적으로 동작한다.

// Function Scope
var box = 10;

function add () {
 var box = 2000;  <--"바깥으로 빠져나오지 않고 함수 안에서만 사용 가능하다."
  console.log(box); // 2000 ✅
}

console.log(box); // 10

Block Scope : let

모든 코드 블록(함수, if 문, for 문, while 문, try/catch 문 등) 내에서 선언된 변수는 코드 블록 내에서만 유효하며 코드 블록 외부에서는 참조할 수 없다. 즉, 코드 블록 내부에서 선언한 변수는 지역 변수이다.

❗️ Block Scope는 Function Scope보다 범위가 좁다.
let은 Block Scope이든 Function Scope이든 내부에 선언된게 바깥으로 빠져나오지 않는다.

// Block Scope
let box = 10;

if(true) {
 let box = 2000; ✅
  console.log(box); // 2000 ✅
}

console.log(box); // 10
// Function Scope
let box = 10;

function add () {
 let box = 2000;  <-- ✅
  console.log(box); // 2000 ✅
}

console.log(box); // 10



클로저(Closure)

폐쇠된 공간에 대한 접근 권한을 가진 함수가 클로저라고 합니다.
사전적 의미 : 중단하다, 폐쇠하다 가 있습니다.

이러한 클로저의 특성을 이용한다면 비공개 private한 데이터를 가진 객체를 만들 수 있습니다.

앞서 스코프에서 말씀 드렸듯이,
JavaScript에는 Function Scope가 있고, 함수 내부에서 정의된 변수라면 함수의 어느 부분에서든 접근할 수 있었습니다.
이 말은 즉,
내부 함수에서 자신을 포함하는 외부 함수의 Scope에 접근할 수 있다는 얘기입니다.

var outer = function(){  // 부모 함수
 var a = 1;
  
  var inner = function(){ // 자식 함수
    var b = 5;
    var c = 6;
    
    a = a + b + c;
    console.log(a);
  }
  inner();
}

outer();

결과 :
12

inner() 함수에서는 자기보다 상위인 부모 함수안에 있는 변수에 접근을 할 수 있는 권한이 있다.
inner() 함수가 outer() 함수 안에 있는 var a = 1; 값에 접근 할 수 있었기 때문에 12이라는 결과를 얻어낼 수 있었다.

🔎
그런데 외부함수 outer() 내부함수 inner() 보다 오래 살아 있는 경우에, 외부함수 outer()에 있던 변수들은 어떻게 되는가..!

var outer = function(){  // 부모 함수
 var a = 1;
  
  var inner = function(){ // 자식 함수
    var b = 5;
    var c = 6;
    
    a = a + b + c;
    console.log(a);
  }
  return inner; // inner()함수를 실행하지 않고 return을 해준다.
}
var newInner = outer(); // newInner이라는 변수를 만들고 outer()에 대한 결과물을 담는다.

newInner(); // outer()함수를 담은 newInner 변수를 실행시킨다., 
newInner의 결과는 outer();
outer()의 결과는 inner;

inner은 아래 코드이다.
 var inner = function(){ // 자식 함수
    var b = 5;
    var c = 6;
    
    a = a + b + c;
    console.log(a);
  }

결국 newInner()가 실행된다는 의미는 inner 함수가 실행된다는 의미와 같다.

결과 : 
12

inner() 함수가 newInner 변수에 실려서 나중에 실행이 된다.
즉, newInner함수가 실행이 됬을때 그다음은 outer()가 실행이 된다.
outer()가 실행이 되고 그 다음 inner()가 실행이 된다.


var outer = function(){ // 실행 2
 var a = 1;
  
  var inner = function(){  // 실행 3
    var b = 5;
    var c = 6;
    
    a = a + b + c;
    console.log(a);
  }
  return inner; 
  "outer함수가 실행이 되고 return으로 inner함수를 내보내는 순간 outer함수는 생명이 끝난다",
  "그 이후 inner함수가 실행이 된다" <-- 내부함수 inner()가 외부함수 outer()보다 오래 살아있는 경우
}
var newInner = outer(); 

newInner(); // 실행 1

이제 생명을 다한 외부함수 outer() 안에 있는 var a = 1 는 어떻게 될까?
답은 똑같은 결과 12의 값을 출력한다.
결국 outer()inner()이 먼저 실행되기 전에 생명을 다했음에도 불과하고 inner()은 먼저 생명이 끝난 outer() 함수 안에 있는 var a = 1 값에 접근할 수 있었다.

❗️ 결국
inner()outer()가 이미 반환되어 없어진 후에도 outer()a에 대한 접근 권한을 가집니다.
이유는 : 함수는 자신을 포함하는 함수의 Scope에 접근할 수 있기 때문입니다.

그렇기 때문에 클로저는 폐쇠된 공간에 대한 접근 권한을 가진 함수라고 말합니다.
이러한 클로저의 특성을 이용한다면 비공개 private한 데이터를 가진 객체를 만들 수 있습니다.

var person = (function(){
 var age = 20;
  
  return {
   name : "simson",
    
   getAge : function() {
     console.log(age);
      return age;
    },
    
   setAge: function(box) {
    age = box;
     console.log(age);
   }
  }
})();

person.getAge(); // 출력 : 20
person.setAge(50); // 출력 : 50

age의 값을 재할당으로 바꿀 수 있을까?

person.age = 100000; // 이런식으로 값을 재할당 할 수 있을까?
person.getAge() // 출력 : 20

할 수 없다. person의 getAge() 함수를 호출 했더니 age의 초기 값인 20이 출력 되었다.
이렇게 객체 접근 방식으로 age의 값을 변경 시킬 수 없고,
이제 age의 값에 접근 하려면 getAge()와 setAge()의 함수들을 통해서만 age에 접근이 가능하다.

이러한 특징을 이용한다면 비공개 데이터를 가진 객체를 만들어 볼 수 있다!

총정리

  • 자바스크립트는 내부 함수에서 자신을 포함하는 외부 함수의 Scope에 접근할 수 있습니다.
  • 내부 함수가 살아있는 상태에서 외부 함수가 파괴되면 외부 함수의 변수들에 대한 접근 권한은 내부 함수만 가지게 됩니다.
  • 이렇게 폐쇠된 공간에 대한 접근 권한을 가진 함수가 클로저라고 합니다.
profile
개발자로 일어서는 일기

0개의 댓글