JavaScript

[JavaScript] This, Closure

하나쓰 2024. 1. 21. 08:00
728x90
반응형

this, closure

this

this란?

자바스크립트에서의 this는 함수 호출 방식에 따라서 결정됨

함수의 호출방식

  • 메소드로서의 호출

메소드로 호출 시(ex: obj1.addNums) 자신을 호출한 대상이 this

  • 함수로서의 호출

함수를 독립적으로 호출 시 호출주체를 알 수 없으므로 this 지정 x -> 때문에 this는 전역 객체를 가리킴
또한 메소드의 내부라고 해도 함수로서 호출하면 this는 전역객체를 의미함

  • 콜백함수로 호출될 때 그 함수 내부에서의 this

콜백함수도 함수이기 때문에 this는 전역객체를 참조하지만, 콜백함수를 넘겨받은 함수에서 별도로 this를 지정한 경우 예외적으로 그 대상을 참조함

  • 생성자 함수 호출

this는 인스턴스를 가리킴

  • apply, call, bind 호출

apply, call, bind는 원하는 this를 바인딩할 수 있는 메소드임

  1. apply
var func = function (a, b, c) {
console.log(this, a, b, c);
};

func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 } 4 5 6

var obj = {
a: 1,
method: function (x, y) {
console.log(this.a, x, y);
}
};

obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6
  1. call
var func = function(a,b,c) {
   console.log(this, a,b,c,)
}

func(1,2,3) // window{…} 1 2 3

func.call({x: 1}, 2,3,4) // {x:1} 2 3 4
  1. bind
    call이나 apply랑 비슷해보이지만 call과 다르게 즉시 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 리턴하는 메소드
var func = function (a,b,c,d) {
  console.log(this, a,b,c,d)
}
func(1,2,3,4) // this는 window 객체 가리킴

var bindFunc = func.bind({x:1})
bindFunc(5,6,7,8) // {x:1}, 5, 6, 7, 8

// 부분 적용함수 
var bindFunc2 = func.bind({x:1, 4, 5}) 
bindFunc2(6,7) // {x:1}, 4, 5, 6, 7

Closure

Closure란?

외부함수의 생명주기가 이미 종료되었음에도 내부함수가 외부 함수의 컨텍스트에 접근할 수 있는 것을 말함 → 이미 종료된 외부함수의 변수 등을 여전의 참조(외부함수의 렉시컬 환경을 여전히 참조)할 수 있음

어떻게 접근할 수 있냐면 가비지 컬렉터가 보고 외부함수의 렉시컬환경을 아직 참조하는 곳이 있어 냅두기 때문임

예시
const x = 1;

function outer() {
  const x = 10;
  const inner = function () {
    console.log(x); // 10
  };
  return inner;
}

const innerFunc = outer();
innerFunc();

const innerFunc = outer() <- 이 부분에서 outer 함수를 호출하고 그 결과인 inner 함수를 innerFunc에 할당함
outer 함수는 inner 함수를 반환한 후 콜스택에서 사라짐. 하지만 outer 함수의 렉시컬 환경은 inner 함수가 여전히 참조하고 있으므로 사라지지 않음
innerFunc() <- inner 함수를 호출하면 이 함수는 자신이 선언됐던 렉시컬 환경인 outer 함수의 환경을 참조하여, outer 함수 내부에 선언된 x 변수를 사용할 수 있음. 이렇게 함수가 자신이 선언된 환경을 기억하는 특성이 바로 클로저임
inner 함수는 console.log(x)를 실행한 후 콜스택에서 사라짐
따라서 함수가 생성될 때 렉시컬 환경을 기억하고, 이후에도 그 환경에 접근할 수 있는 이런 특성이 클로저임. 이를 통해 함수 외부에서 접근할 수 없는 private한 변수를 만들 수 있음

 

그러면 클로저는 왜 사용하는 걸까?

  1. 상태유지
    현재 상태를 기억하고 변경된 최신 상태를 유지하는 것
예시
function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  }
}

let counter1 = makeCounter();

console.log(counter1());  // 0
console.log(counter1());  // 1
  1. 전역 변수 사용 억제
    자신이 생성되었을 때의 lexical 환경을 기억하기 때문
예시
let count = 0;

function makeCounter() {
  return function () {
    return ++count;
  };
}

let counter1 = makeCounter();

console.log(counter1(), "before"); // 0
count = 1000;
console.log(counter1(), "after"); // 1001
  1. 정보의 은닉
예시
// 생성자 함수
function Person(name, age) {
  this.name = name; //public
  let _age = age; //private

  this.sayHi = function () {
    console.log(`Hi! My name is ${this.name}. I am ${_age}.`);
  };
}

const me = new Person("Choi", 33);
me.sayHi(); // Hi!, My name is Choi. I am 33.
console.log(me.name); // Choi
console.log(me._age); // undefined

const you = new Person("Lee", 30);
you.sayHi(); // Hi! My name is Lee. I am 30.
console.log(you.name); // Lee
console.log(you.age); // undefined

위 코드에서 me._age나 you.age에 접근하면 undefined가 나오는 이유
_age는 Person 함수의 내부에서만 사용하는 변수라서 외부에서 접근할 수 없음
변수 _age는 Person 함수 내부에서만 접근가능하고(은닉화), 함수가 종료되면 콜스택에서 사라지지만 클로저로 인해 메소드 SayHi 함수에서 계속 접근이 가능함

me.name이 되는 이유는 Person 객체의 인스턴스의 name 속성을 나타내고, 이 속성은 어디서든 접근가능하고 변경할 수 있는 public 속성이기 때문임. 따라서 me.name이나 you.name이라고 하면 그 속성값이 나옴

반응형