• [JS] 코어자바스크립트 - 03 this

    2024. 10. 16.

    by. 지은이: 김지은

    728x90

     

     

    01. 상황에 따라 달라지는 this

    자바스크립트에서 this는 함수가 호출되는 방식에 따라 가리키는 대상이 달라진다.

     

    1) 전역 공간에서의 this

    브라우저 환경에서는 this가 전역 객체인 window를 가리키며, Node.js 환경에서는 전역 객체인 global을 가리킨다.

     

    전역 실행 컨텍스트는 자바스크립트 코드가 실행되기 시작할 때 생성죄는데 그 안에서 this는 전역 객체를 참조하도록 설정 되어있음

     

    2) 메서드로서 호출할 때 그 메서드 내부에서의 this

    함수: 독립적으로 동작하는 코드의 묶음, 함수로 호출하면 전역객체가 this

    const func = (x) => {
        console.log(this, x);
    };
    
    func(1); // this는 전역 객체 (브라우저에서는 window)

     

    메서드: 객체의 프로퍼티에 할당된 함수로, 메서드를 호출할 때 그 메서드를 실행하는 객체가 this

    const obj = {
        method: function (x) {
            console.log(this, x);
        }
    };
    
    obj.method(1); // 메서드 호출: this는 obj

     

    함수의 호출과 메서드의 호출의 구분 방법은 함수 앞에 점(.)이 있을 떄 메서드, 점이 없으면 함수로서 호출

    그래서 어떤 함수를 메서드로서 호출하는 경우 호출 주체는 바로 함수명 앞의 객체 (점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this)

     

    3) 함수로서 호출할 때 그 함수 내부에서의 this

    함수로서 함수를 호출 할 때 this는 호출 주체에 대한 정보를 가지지 않는데 이 경우 호출한 주체(객체)를 명시하지 않고 개발자가 직접 코드를 실행하기 때문에 this가 무엇을 가리킬지 알 수 없다.

     

    메서드 내부에서 정의하고 실행한 함수의 this는 '설계상의 오류'로 인해 실제 동작과 다르게 예측하곤 한다.

    const obj1 = {
        outer: function () {
            console.log(this); // (1)
    
            const innerFunc = function () {
                console.log(this); // (2)
            };
            innerFunc();
            
            const obj2 = {
                innerMethod: innerFunc
            };
            obj2.innerMethod(); // (3)
        }
    };
    
    obj1.outer();

    (1) obj1{outer: ƒ} (2) 전역객체 (window) (3) obj2{innerMethod: ƒ} 가 출력

     

    innerFunc는 obj1.outer()에 정의되어있지만, 일반 함수로 호출되었기 때문에 this가 전역 객체를 참조

    obj2.innerMethod()는 obj2의 메서드로 호출되었기 때문에 this는 obj2를 가리킨다.

     

    같은 함수임에도 바인딩되는 this의 대상이 서로 달라졌다.

    오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지 없는지가 관건인 것인데

    이렇게 this가 의도와 다르게 동작할 때 이를 우회할 방법이 있다.

     

    const obj1 = {
        outer: function () {
            console.log(this); // (1) {outer: f}
    
            const innerFunc = function () {
                console.log(this); // (2) window {...}
            };
            innerFunc();
            
            const self = this; // `this`를 `self` 변수에 저장
            const innerFunc2 = function() {
                console.log(self); // (2) {outer: f}
            }
            
            innerFunc2(); 
        }
    };
    
    obj1.outer();

    outer 함수에 this 값을 변수에 저장하고 내부 함수에서 그 변수를 사용하면 원하는 객체의 this값을 유지할 수 있다.

     

    ES6에선 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자,

    this를 바인딩 하지 않는 화살표 함수(arrow function)를 새로 도입했다.

    화살표 함수는 상위 스코프의 this를 그대로 참조하는데 자신만의 this를 갖지 않고 자신이 선언된 위치의 this를 사용

    const obj = {
        outer: function () {
            console.log(this); // (1) obj
            const innerFunc = () => {
                console.log(this); // (2) obj
            };
            innerFunc();
        }
    };
    
    obj.outer();

    (1)은 obj를 가리키며

    (2)는 innerFunc는 화살표 함수이므로, 상위 스코프인 outer 함수의 this를 그대로 사용하여 obj를 출력

     

    4) 콜백함수 호출 시 그 함수 내부에서의 this

    setTimeout(function() {
      console.log(this); // window
    }, 300);
    
    [1, 2, 3, 4, 5].forEach(function(x) {
      console.log(this, x); // window, 1 ~ window, 5
    });
    
    document.body.innerHTML += '<button id="myButton">Click me</button>';
    
    document.querySelector('#myButton').addEventListener('click', function(e) {
      console.log(this); // 클릭된 버튼 요소
      console.log(e); // 이벤트 객체
    });
    1. setTimeout은 기본적으로 콜백함수를 실행할 때 this를 지정하지 않으므로, 전역 객체가 this로 바인딩
    2. forEach도 this를 지정하지 않기 때문에 전역 객체 this 참조
    3. addEventListener에서 this는 이벤트가 발생한 요소에 바인딩 되어 클릭된 버튼이 this

     

    5) 생성자 함수 내부에서의 this

    생성자 함수: 객체를 만드는 툴(템플릿)처럼 사용되는 함수로 공통된 속성을 가진 여러 객체를 효율적으로 만들기 위해 사용

     

    예를 들어, 고양이를 프로그래밍으로 나타내고 싶다면 각 고양이는 '야옹'하고 울고 이름과 나이를 가지고 있을텐데

    이 공통된 특성을 가지고 고양이 객체를 만들기 위해 생성자 함수를 사용할 수 있다.

     

    function Cat(name, age) {
      this.bark = '야옹'; 
      this.name = name;  
      this.age = age;
    }
    
    const choco = new Cat('초코', 7); 
    const nabi = new Cat('나비', 5);
    
    console.log(choco);  // Cat { bark: '야옹', name: '초코', age: 7 }
    console.log(nabi);   // Cat { bark: '야옹', name: '나비', age: 5 }

    Cat이란 함수에 name, age를 매개변수로 받아서 각각의 고양이 객체에 할당.

    new 키워드로 생성자 함수를 호출하면 새로운 객체가 만들어지는데 이때 함수 내부에서의 this는 새롭게 생성된 객체를 기리킨다.

     

    02. 명시적으로 this를 바인딩하는 방법

    1) call 메서드: 원하는 값으로 this를 직접 지정할 수 있는 기능

    func.call(thisArg[, arg1[, arg2[, ...]]])
    const func = function(a, b, c) {
      console.log(this, a, b, c);
    };
    
    func(1, 2, 3);  // 전역 객체(Window {...}), 1, 2, 3
    
    func.call({x: 1}, 4, 5, 6);  // {x: 1}, 4, 5, 6

    func(1, 2, 3,)을 그냥 호출하면 this는 기본적으로 전역 객체를 참조하는데

    call 메서드를 추가하면 this는 {x: 1} 객체로 바인딩이 된다.

     

    2) apply 메서드: call 메서드와 비슷하지만, call 메서드는 인자를 하나씩 나열하여 넘겼지만 apply 메서드는 인자를 배열로 넘긴다.

    func.apply(thisArg, [argsArray]);
    const func = function(a, b, c) {
      console.log(this, a, b, c);
    };
    
    func.apply({ x: 1 }, [4, 5, 6]); // { x: 1 }, 4, 5, 6
    
    
    const obj = {
      a: 1,
      method: function(x, y) {
        console.log(this.a, x, y); 
      }
    };
    
    obj.method.apply({ a: 4 }, [5, 6]); // 4, 5, 6

    func 함수를 호출할 때 apply 메서드를 사용해 this를 {x: 1}로 바인딩하고

    obj.method의 this를 {a: 4}로 바인딩하여 배열의 요소를 매개변수로 전달

     

    3) bind 메서드: call과 비슷하지만, 즉시 호출하지 않고 주어진 this 값과 인수들을 바탕으로 새로운 함수 반환

    func.bind(thisArg[, arg1[, arg2[, ...]]])
    const func = function(a, b, c, d) {
      console.log(this, a, b, c, d);
    };
    
    func(1, 2, 3, 4); // Window {...}, 1, 2, 3, 4
    
    const bindFunc1 = func.bind({ x: 1 });
    bindFunc1(5, 6, 7, 8); // {x: 1}, 5, 6, 7, 8
    
    const bindFunc2 = func.bind({ x: 1 }, 4, 5);
    bindFunc2(6, 7); // {x: 1}, 4, 5, 6, 7
    bindFunc2(8, 9); // {x: 1}, 4, 5, 8, 9

    기본적으로 func을 호출하면 this는 전역 객체를 참조하고, 

    bindFunc1에 새로운 함수 func.bind({ x: 1 })를 생성하는데 이때 this는 {x: 1}로 설정

    bindFunc2는 this를 {x: 1}로 설정하고 첫 번째, 두 번쨰 인수로 4와 5를 미리 지정하여 bindFunc2(6, 7)을 호출하면 this는 여전히 {x: 1}

     

    bind 메서드를 사용해서 새로 만든 함수에는 독특한 성질이 있는데 name 프로퍼티이며, 'bound'라는 접두어가 붙는다.

    자바스크립트 함수에서 name은 함수의 이름을 보여준다.

    const func = function(a, b, c, d) {
      console.log(this, a, b, c, d);
    };
    
    const bindFunc = func.bind({ x: 1 }, 4, 5);
    console.log(func.name);      // func
    console.log(bindFunc.name);  // bound func

    이처럼 bind로 만든 함수는 'bound'라는 접두어가 붙은 이름을 갖기 때문에 코드를 추적할 때 더 쉽게 알아볼 수 있다.

     

    4) 화살표 함수의 예외사항

    화살표함수는 this를 갖지 않고 자신이 정의된 스코프에서 가장 가까운 this를 사용

    const obj = {
        outer() {
            console.log(this); //obj
            const innerFunc = () => {
                console.log(this); // obj
            };
            innerFunc();
        }
    };
    
    obj.outer();

     

    5) 별도의 인자로 this를 받는 경우(콜백 함수 내에서의 this)

    콜백함수: 특정 함수의 인자로 전달되어 실행되는 함수로

    콜백함수를 인자로 받는 메서드 중 일부는 this로 사용하고 싶은 객체를 지정할 수 있다.(thisArg)

    이런 형태는 배열 메서드(forEach, Map, filter 등)에서 많이 포진되어있다.

     

    const report = {
        sum: 0,
        count: 0,
        add() {
            const args = Array.prototype.slice.call(arguments);
            args.forEach(function(entry) {
                this.sum += entry; // this는 report를 가리킴
                this.count++;
            }, this); // thisArg로 report를 전달
        },
        average() {
            return this.sum / this.count;
        }
    };
    
    report.add(60, 85, 95);
    console.log(report.sum, report.count, report.average()); // 240 3 80

    report.add(60, 85, 95)를 호출 시 arguments를 배열로 변환하여 args 변수에 저장 -> args = [60, 85, 95]

    forEach메서드를 사용하여 args 배열의 각 요소를 순회하는데

    두 번째 인자로 this를 전달하여 thisArg를 설정했기 때문에 forEach 내부의 콜백함수에서의 this는 report 객체를 가리킨다.

    그래서 최종 출력 결과는 240 3 80

     

     

     

     

    댓글