JavaScript

자바스크립트 this

foxlee 2021. 12. 16. 16:16
  • 자신이 속한 객체의 프로퍼티를 참조하려면 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야함
  • 객체의 프로퍼티 : 객체 고유의 상태 데이터
  • 객체의 메서드 : 객체 상태 데이터를 참조/조작하는 동작
const circle = {
  radius: 5, // 프로퍼티
  getDiameter() { // 메서드
    return 2 * circle.radius; 
    // 이 메서드가 자신이 속한 객체의 프로퍼티나 다른 메서드를 참조하려면
    // 자신이 속한 객체인 circle을 참조할 수 있어야 한다.
  }
};

console.log(circle.getDiameter()); // 10
  • 위와 같이 가능하지만(circle.radius를 참조) 자기 자신을 재귀적으로 참조하는 것은 좋지 않음, 
  • 위와 같은 방법은 직관적이고 편하지만, 동일한 프로퍼티를 갖는 객체를 여러개 생성해야 하는 경우 생성자 함수를 통해 객체를 생성하는 것이 더 좋다(객체 지향 프로그래밍)

this: 자신이 속한 객체, 자신이 생성한 인스턴스를 가르키는 자기 참조 변수

  • this 는 자바스크립트 엔진에 의해 암묵적으로 생성되고, this의 값은 함수 호출 방식에 의해 동적으로 결정
// 호출 방식에 따른 this의 값
// 객체
const circle = {
  radius: 5,
  getDiameter() {
    return 2 * this.radius; // this는 메서드를 호출한 객체를 가리킨다.
  }
};

console.log(circle.getDiameter()); // 10 circle 객체가 호출함.

// 생성자 함수
function Circle(radius) {
  this.radius = radius; // this는 생성자 함수가 생성할 인스턴스를 가리킨다.
}

Circle.prototype.getDiameter = function () {
  // this는 생성자 함수가 생성할 인스턴스를 가리킨다.
  return 2 * this.radius;
};

// 인스턴스 생성
const circle = new Circle(5);
console.log(circle.getDiameter()); // 10 circle 인스턴스를 가르침
  • 호출 방식에 따른 this의 값
    • 전역 - window
    • 일반함수-window or undefined(strict mode)
    • 객체 - 호출한 객체
    • 생성자 함수 - 인스턴스

 

// this는 어디서든지 참조 가능하다.
// 1. 전역에서 this는 전역 객체 window를 가리킨다.
console.log(this); // window

// 2. 일반 함수 내부에서 this는 전역 객체 window를 가리킨다.
function square(number) {
  console.log(this); // window
  return number * number;
}
square(2);

// 3. 메서드 내부에서 this는 메서드를 호출한 객체를 가리킨다.
const person = {
  name: 'Lee',
  getName() {
    console.log(this); // person 객체이므로, {name: "Lee", getName: ƒ}
    return this.name;
  }
};
console.log(person.getName()); // Lee

// 4. 생성자 함수 내부에서 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
function Person(name) {
  this.name = name;
  console.log(this); // me 라는 인스턴스 {name: "Lee"}
}

const me = new Person('Lee');
  • 중첩 함수의 경우 바인딩을 해주시 않으면 window 에서 value를 찾음
  • 부모 함수에서 const that = this 를 통해 바인딩 해줌
var value = 1;

const obj = {
  value: 100,
  foo() {
    console.log("foo's this: ", this);  // {value: 100, foo: ƒ} // obj 객체 값

    function bar() {  // 메서드 내에서 정의한 중첩 함수
      console.log("bar's this: ", this); // window
      console.log("bar's this.value: ", this.value); // 1 // window.value 가 됨
    }
    // 이슈: 메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 **중첩 함수 내부의 this에는 전역 객체가 바인딩**된다.
    bar();
  }
};

obj.foo();

var value1 = 1;

// that - this - 컨밴션
const obj1 = {
    value1: 100,
    foo() {
        console.log("foo's this: ", this);  // {value: 100, foo: ƒ} // obj 객체 값
        const that = this; // 해결 방법
        function bar() {  // 메서드 내에서 정의한 중첩 함수
            console.log("bar's this: ", that); // window
            console.log("bar's this.value: ", that.value1); // 100
        }
        // 이슈: 메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 **중첩 함수 내부의 this에는 전역 객체가 바인딩**된다.
        bar();
    }
};

obj1.foo();

var value = 1;

const obj = {
  value: 100,
  foo() {
    const that = this;   // this 바인딩(obj)을 변수 that에 할당한다. 
    setTimeout(function () {  
      console.log(that.value); // 100 콜백 함수 내부에서 this 대신 that을 참조한다.
    }, 100);
  }
};

obj.foo();

var value = 1;

const obj = {
  value: 100,
  foo() {
    setTimeout(function () {
      console.log(this.value); // 100
    }.bind(this), 100); // 콜백 함수에 명시적으로 this를 바인딩한다.
  }
};

obj.foo();
var value = 1;

const obj = {
  value: 100,
  foo() {
    // 화살표 함수 내부의 this는 상위 스코프의 this를 가리킨다.
    setTimeout(() => console.log(this.value), 100); // 100
  }
};

obj.foo();
  • 객체의 메서드가 변수에 할당되면 그 메서드 안의 this 는 객체의 메서드가 아닌 일반 함수가 되기에 window에서 해당 값을 찾는다.
onst anotherPerson = {
  name: 'Kim'
};
anotherPerson.getName = person.getName;
console.log(anotherPerson.getName()); // Kim // this => object

const getName = person.getName; // getName 메서드를 변수에 할당

console.log(getName()); // '' // getName 메서드를 일반 함수로 호출 
// 호출 시 객체가 호출 하는 것이 아닌 변수에 할당된 일반 함수가 호출되며,
// 일반 함수로 호출된 getName 함수 내부의 this.name은 브라우저 환경에서 window.name과 같다.
// 브라우저 환경에서 window.name은 브라우저 창의 이름을 나타내는 빌트인 프로퍼티이며 기본값은 ''이다.
// Node.js 환경에서 this.name은 undefined​
  • 생성자 함수의 this / 생성자함수를 new 없이 생성시
function Circle(radius) {
  this.radius = radius;
  this.getDiameter = function () {
    return 2 * this.radius;
  };
}

const circle1 = new Circle(5);
const circle2 = new Circle(10);

console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20

// new 연산자와 함께 호출하지 않으면 생성자 함수로 동작하지 않는다. 
// 일반적인 함수의 호출이다.
const circle3 = Circle(15);

// 일반 함수로 호출된 Circle에는 반환문이 없으므로 암묵적으로 undefined를 반환
console.log(circle3); // undefined

// 일반 함수로 호출된 Circle 내부의 this는 전역 객체를 가리킨다.
// this.radius = radius  => window.radius = 15
console.log(radius); // 15
  • Apply, call, bind 적용
    • Apply, call 함수를 바로 실행함
    • bind 는 새로운 함수를 반환하기에 명시적으로 호출해야함
function getThisBinding() {
  return this;
}

const thisArg = { a: 1 }; / / this로 사용할 객체
console.log(getThisBinding()); // window // 일반함수의 this

// getThisBinding 함수를 호출하면서 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩한다.
console.log(getThisBinding.apply(thisArg)); // {a: 1} // this 값에 thisArg가 할당됨 - 바인딩
console.log(getThisBinding.call(thisArg)); // {a: 1} // this 값에 thisArg가 할당됨 - 바인딩

// arguments 인수 전달도 가능
function getThisBinding() {
  console.log(arguments);
  return this;
}
const thisArg = { a: 1 };
console.log(getThisBinding.apply(thisArg, [1, 2, 3])); // apply 는 배열로
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// {a: 1}

console.log(getThisBinding.call(thisArg, 1, 2, 3)); // call 는 인수 하나씩
// Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// {a: 1}


function getThisBinding() {
  return this;
}

const thisArg = { a: 1 };

// bind 메서드는 첫 번째 인수로 전달한 thisArg로 this 바인딩이 교체된
// getThisBinding 함수를 새롭게 생성해 반환한다.
console.log(getThisBinding.bind(thisArg)); // getThisBinding
// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 한다.
console.log(getThisBinding.bind(thisArg)()); // {a: 1}
  • bind 활용
const person = {
  name: 'Lee',
  foo(callback) {
    setTimeout(callback, 100);
  }
};

person.foo(function () {
  console.log(`Hi! my name is ${this.name}.`); // ② Hi! my name is .
  // 일반 함수로 호출된 콜백 함수 내부의 this 는 window 
  // 브라우저 환경에서 window.name 으로 이 값은 브라우저 창의 이름을 나타내는 빌트인 프로퍼티이며 기본값은 ''이다.
  // Node.js 환경에서 this.name은 undefined
});

// person 객체의 this를 callback 내부의 this 바인딩을
const person = {
  name: 'Lee',
  foo(callback) {
    setTimeout(callback.bind(this), 100);
  }
};

person.foo(function () {
  console.log(`Hi! my name is ${this.name}.`); // Hi! my name is Lee.
});