JavaScript - this

이소라·2022년 11월 14일
0

JavaScript

목록 보기
11/22

1. this

  • 객체

    • 프로퍼티
      • 객체의 상태를 나타냄
    • 메서드
      • 객체의 동작을 나타냄
      • 프로퍼티를 참조하고 변경할 수 있어야 함
      • 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 함
  • this

    • 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수(self-referencing variable)
    • this 바인딩은 함수 호출 방식에 의해 동적으로 결정됨
      • this 바인딩 : this와 this가 가리킬 객체를 연결하는 것



2. 함수 호출 방식과 this 바인딩

  • this 바인딩은 함수 호출 방식에 따라 동적으로 결정됨
    • 렉시컬 스코프와 this 바인딩의 결정 시기가 다름
      • 렉시컬 스코프 : 함수 정의 평가 시점
      • this 바인딩 : 함수 호출 시점
    • 함수 호출 방식
      1. 일반 함수 호출
      2. 메서드 호출
      3. 생성자 함수 호출
      4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출

2.1 일반 함수 호출

  • 일반 함수로 호출된 모든 함수(중첩 함수, 콜백 함수 포함) 내부의 this에는 전역 객체가 바인딩됨
    • strict mode가 적용된 일반 함수 내부의 this에는 undefined가 바인딩됨
    • 중첩 함수나 콜백 함수도 일반 함수로 호출되면 함수 내부의 this에 전역 객체가 바인딩됨
      • 외부 함수인 메서드와 중첩 함수 또는 콜백 함수의 this가 일치하지 않는 문제점이 생김
function foo() {
  console.log("foo's this: ", this); // window
  function bar() {
    console.log("bar's this: ", this); // window
  }
  bar();
}
foo();

// stict mode의 경우
function foo() {
  'use strict';
  console.log("foo's this: ", this); // undefined
  function bar() {
    console.log("bar's this: ", this); // undefined
  }
  bar();
}
foo();
// 중첩 함수를 일반 함수로 호출할 경우
var value = 1;

const obj = {
  value: 100,
  
  // 메서드
  foo() {
    console.log("foo's this: ", this); // {value: 100, foo: f}
    console.log("foo's this.value: ", this.value); // 100
    
    // 중첩 함수
    function bar() {
      console.log("bar's this: ", this); // window
    console.log("bar's this.value: ", this.value); // 1
    }
    
    // 일반 함수 호출
    bar();
  }
};

// 메서드 호출
obj.foo();
// 콜백 함수를 일반 함수로 호출할 경우
var value = 1;

const obj = {
  value: 100,
  // 메서드
  foo() {
    console.log("foo's this: ", this); // {value: 100, foo: f};
    // 콜백 함수를 일반 함수로 호출함
    setTimeout(function() {
      console.log("callback's this: ", this); // window
      console.log("callback's this.value: ", this.value); // 1
    }, 100);
  }
}

obj.foo();
  • 외부 함수인 메서드와 중첩 함수 또는 콜백 함수의 this가 일치시키기 위한 방법
    1. 외부 함수의 지역 변수에 this를 할당하고, 중첩 함수 또는 콜백 함수에서 지역 변수를 참조함
    2. Function.prototype.call/apply/bind 메서드를 사용하여 this를 명시적으로 바인딩함
    3. 화살표 함수를 사용하여 this 바인딩을 일치시킴
      • 화살표 함수 내부의 this = 상위 스코프의 this
// 외부 함수의 지역 변수 that에 this를 할당함
var value = 1;

const obj = {
  value: 100,
  
  foo() {
    const that = this; // {value: 100, foo: f}
    
    setTimeout(function() {
      console.log(that.value); // 100
    }, 100);
  }
}

obj.foo();


// bind 메서드를 사용하여 this를 명시적으로 바인딩함
var value = 1;

const obj = {
  value: 100,
  
  foo() {
    setTimeout(function() {
      console.log(this.value); // 100
    }.bind(this), 100);
  }
}

obj.foo();


// 화살표 함수를 사용하여 상위 스코프의 this를 가리키게 함
var value = 1;

const obj = {
  value: 100,
  
  foo() {
    setTimeout(() => {
      console.log(this.value); // 100
    }, 100);
  }
}

obj.foo();

2.2 메서드 호출

  • 메서드 내부의 this에는 메서드를 호출한 객체가 바인딩됨
    • 메서드를 호출한 객체 = 객체.메서드의 객체
    • 메서드
      • 프로퍼티에 바인딩된 함수 객체
      • 객체와 독립적으로 존재하는 별도의 객체
      • 다른 객체의 메서드가 될 수 있음
      • 일반 변수에 할당하여 일반 함수로 호출 가능함
const person = {
  name: 'Lee',
  // 메서드
  getName() {
    // 메서드 내부의 this는 메서드를 호출한 객체에 바인딩됨
    return this.name;
  }
};
// getName 메서드를 호출한 객체 : person
console.log(person.getName()); // Lee



// 메서드는 다른 객체의 메서드가 될 수 있음
const anotherPerson = {
  name: 'Kim'
}
// getName  메서드를 anotherPerson 객체의 메서드로 할당
anotherPerson.getName = person.getName;
// getName 메서드를 호출한 객체 : anotherPerson
console.log(anotherPerson.getName()); // Kim


// 메서드를 변수에 할당하여 일반 함수로 호출할 수 있음
const getName = person.getName;
// 일반 함수로 호출할 경우 함수 내부의 this : 전역객체 window
console.log(getName()); // '' (window.name) 
  • 프로토타입 메서드 내부에서 사용된 this도 해당 메서드를 호출한 객체에 바인딩됨
    • 인스턴스에서 메서드를 호출할 경우, 메서드 내부의 this = 인스턴스
    • 객체의 프로토타입에서 메서드를 호출할 경우, 메서드 내부의 this = 프로토타입
function Person(name) {
  this.name = name;
}

Person.prototype.getName = function () {
  return this.name;
};

const me = new Person('Lee');
// getName 메서드를 호출한 객체 : me
console.log(me.getName()); // Lee

Person.prototype.name = 'Kim';
// getName 메서드를 호출한 객체 : Person.prototype
console.log(Person.prototype.getName()); // Kim

2.3 생성자 함수 호출

  • 생성자 함수 내부의 this에는 생성자 함수가 생성할 인스턴스가 바인딩됨
    • 생성자 함수
      • 객체(인스턴스)를 생성하는 함수
      • new 연산자와 함께 함수를 호출하면 생성자 함수로 동작함
      • new 연산자 없이 함수를 호출하면 일반 함수로 동작함
function Circle(radius) {
  // 생성자 함수 내부의 this : 생성자 함수가 생성할 인스턴스(객체)
  this.radius = radius;
  this.getDiameter = function () {
    return 2 * this.radius;
  };
  return this;
}
// 반지름이 5인 Circle 객체를 생성함
const circle1 = new Circle(5);
// 반지름이 10인 Circle 객체를 생성함
const circle2 = new Circle(10);

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


// new 연산자 없이 함수를 호출하면 일반 함수로 동작함
const circle3 = Circle(15);
// 일반 함수로 호출할 경우 this : 전역 객체
console.log(circle3); // window
console.log(radius); // 15

2.4 Function.prototype.apply/call/bind 메서드에 의한 간접 호출

  • apply, call, bind 메서드

    • Function.prototype의 메서드
    • 모든 함수가 상속받아 사용할 수 있음
  • apply, call 메서드

    • 본질적인 기능 : 함수를 호출하는 것
    • 첫 번째 인수 : this로 사용할 객체
    • 나머지 인수
      • apply : 배열 형태 또는 유사 배열 객체
      • call : ,로 구분된 인수 리스트
    • 반환값 : 호출된 함수의 반환값
// 사용법
Function.prototype.apply(thisArg, [, argsArray])

Function.prototype.call(thisArg, [, arg1, [, arg2, ...]])


// 예시
function getThisBinding() {
  return this;
}
// this로 사용할 객체
const thisArg = { a: 1 };

console.log(getThisBinding().apply(thisArg), [1, 2, 3]); // { a: 1 }
console.log(getThisBinding().call(thisArg), 1, 2, 3); // { a: 1 }
  • apply, call 메서드의 대표적인 용도 : arguments 객체와 같은 유사 배열 객체에 배열 메서드를 사용하는 경우
function convertToArray() {
  // arguments 객체를 배열로 변환함
  const arr = Array.prototype.slice.call(arguments);
  // const arr = Array.prototype.slice.call(arguments);
  console.log(arr); // [1, 2, 3]
}

convertToArray(1, 2, 3);
  • bind 메서드
    • apply, call 메서드와 달리 함수를 호출하지 않음
    • 첫 번째 인수 : this로 사용할 객체
    • 나머지 인수 : 반환할 함수에 바인딩할 값
    • 반환값 : 주어진 인수로 this, 인수 바인딩하여 새롭게 생성한 함수 반환
// 사용법
Function.prototype.bind(thisArg, [arg1], [arg2], ...)

// 예시
function getThisBinding() {
  return this;
}
// this로 사용할 객체
const thisArg = { a: 1 };
// thisArg로 this 바인딩이 교체된 getThisBinding을 생성하여 반환함
console.log(getThisBinding().bind(thisArg)); // getThisBinding
// bind 메서드는 함수를 호출하지 않으므로 함수를 명시적으로 호출해야함
console.log(getThisBinding().bind(thisArg)()); // { a: 1 }
  • bind 메서드는 외부 함수인 메서드와 중첩 함수 또는 콜백 함수의 this가 일치시키기 위해 주로 사용됨
const person = {
  name: 'Lee',
  foo(callback) {
    // bind 메서드로 callback 함수 내부의 this 바인딩을 전달
    setTimeout(callback.bind(this), 100);
  }
}

person.foo(function () {
  console.log(`My name is ${this.name}`); // My name is Lee
});
  • 객체 내 전체 메서드들에 this 바인딩을 할 때 반복문을 사용함
for (let key in user) {
  if (typeof user[key] == 'function') {
    user[key] = user[key].bind(user);
  }
}
  • bind 메서드를 사용하여 this 뿐만 아니라 인수도 바인딩할 수 있음
function multiply(a, b) {
  return a * b;
}

let double = multiply.bind(null, 2);

console.log(double(3)); // = mul(2, 3) = 6
console.log(double(4)); // = mul(2, 4) = 6
console.log(double(5)); // = mul(2, 5) = 6
  • bind 메서드가 반환하는 묶인 함수는 함수 생성 시점의 실행 컨텍스트만 기억함
    • 한번 bind를 적용하면 다시 bind를 사용하여 컨텍스트를 정의할 수 없음
function foo() {
  console.log(this.name); // John
}

const f = foo.bind({name: "John"}).bind({name: "Ann"});

f();

Conclusion

  • this 바인딩은 함수 호출 방식에 따라 동적으로 결정됨
함수 호출 방식this 바인딩
일반 함수 호출전역 객체
메서드 호출메서드를 호출한 객체
생성자 함수 호출생성자 함수가 생성할 인스턴스
Function.prototype.apply/call/bind 메서드에 의한 간접 호출Function.prototype.apply/call/bind 메서드에 첫 번째 인수로 전달한 객체

Reference

0개의 댓글