CS/프로그래밍

this

고래강이 2023. 12. 7. 08:57

개요

  • this의 정의
  • 함수호출방식과 this 바인딩

this의 정의

Javascript의 예약어로써 함수의 호출 방식에 따라 this에 바인딩되는 객체가 달라진다.
  • 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수
  • 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있다
  • Javascript엔진에 의해 암묵적으로 생성 ( + 호출시 내부에 인자와 this가 전달된다)
  • 일반적으로 객체의 메서드 내부 또는 생성자 함수 내부에서만 의미가 있다. (참조 변수이기때문에)
  • 지역변수처럼 사용 가능
느낌상 객체 내부에서 사용하는 것을 많이 떠올리게 된다. value라는 값을 가지고 있는 객체에 foo라는 함수를 할당하려할 때 그 함수에서 value를 사용하려 할 때 this.value 이런 느낌

 

바인딩)

식별자와 값을 연결하는 과정으로 변수의 경우 이름과 확보된 메모리 공간의 주소를 바인딩한다.
this바인딩은 this가 가리킬 개게를 바인딩하는 것

 


함수호출방식과 this바인딩

함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아닌 호출 시에 동적으로 결정된다
렉시컬스코프와 혼동하지 않도록 유의하자

 

호출방식)

  1. 함수호출
  2. 메소드 호출
  3. 생성자 함수 호출
  4. apply / call / bind 호출

 

1. 함수 호출)

전역객체(Global Object)란 모든 객체의 유일한 최상위 객체이며 Browser에서는 window, Node.js에서는 global객체를 의미한다.
  • 호출단계에서 this는 어떤값을 바인딩하는지를 유심히 살펴보고 원인을 파악해보자!
  • 기본적으로 설계 상 오류인 부분이 있는데 어떤 것인지 찾아보자

기본적으로 this는 전역객체에 바인딩이 된다 (전역함수와 내부함수 모두 전역객체에 바인딩 된다)

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    console.log("foo's this: ",  this);  // obj
    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();
  • 일반 함수, 메소드, 콜백함수 어디든 사용된 내부함수의 this는 전역객체를 바인딩한다.
  • 설계단계의 결함으로 this의 원래 역할을 제대로 수행할 수 없다 (내부함수를 사용하여 자신의 작업을 돕게 할 수 없음)

회피방법)

  1. 변수를 선언하여 this를 할당하는 방법
  2. 명시적으로 바인딩할 수 있는 apply / call / bind 메소드 이용
var value = 1;

// this를 변수에 할당
var obj = {
  value: 100,
  foo: function() {
    var that = this;  // Workaround : this === obj

    console.log("foo's this: ",  this);  // obj
    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

      console.log("bar's that: ",  that); // obj
      console.log("bar's that.value: ", that.value); // 100
    }
    bar();
  }
};

obj.foo();
// 주어진 메소드를 이용
var value = 1;

var obj = {
  value: 100,
  foo: function() {
    console.log("foo's this: ",  this);  // obj
    console.log("foo's this.value: ",  this.value); // 100
    function bar(a, b) {
      console.log("bar's this: ",  this); // obj
      console.log("bar's this.value: ", this.value); // 100
      console.log("bar's arguments: ", arguments);
    }
    bar.apply(obj, [1, 2]);
    bar.call(obj, 1, 2);
    bar.bind(obj)(1, 2);
  }
};

obj.foo();

 

 

2. 메소드 호출)

함수가 객체의 프로퍼티 값이라면 메소드로 호출되며 이떄 this는 해당 메소드를 소유한 객체에 바인딩된다.

 

var obj1 = {
  name: 'Lee',
  sayName: function() {
    console.log(this.name);
  }
}

var obj2 = {
  name: 'Kim'
}

obj2.sayName = obj1.sayName;

obj1.sayName();
obj2.sayName();

 

3. 생성자 함수 호출)

JAVA와 같은 객체지향언어와는 다르게 Javascript는 기존함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 작동한다 
// 생성자 함수
function Person(name) {
  this.name = name;
}

var me = new Person('Lee');
console.log(me); // Person {name: "Lee"}

// new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수로 동작하지 않는다.
var you = Person('Kim');
console.log(you); // undefined

 

생성자 함수 동작 방식)

  1. 빈 객체 생성 및 this바인딩
    • 생성자 함수 코드가 실행하기 전에 빈 객체를 생성해서 생성자 함수 내부에서 사용되는 this가 이 빈 객체를 바인딩하게 한다.
  2. this를 통한 프로퍼티 생성
    • 이제 this.~~~ 이렇게 붙은 애들이 전부 객체의 프로퍼티로 들어간다.
  3. 생성된 객체 반환
    • return 있으면 안된다 그러면 우리가 이제껏 만들었던 객체를 반환하는게 아니라 return을 반환하고 이렇게 되면 생성자 역할을 하지 못하는 함수이기에 return은 사용하면 안됨. 안 쓰면 명시적으로 this를 반환한다.

 

객체리터럴과 생성자 함수의 차이점)

프로토타입 객체의 차이에 있다.
  • 객체 리터럴은 Object.prototype 생성자 함수는 Person.prototype
// 객체 리터럴 방식
var foo = {
  name: 'foo',
  gender: 'male'
}

console.dir(foo);

// 생성자 함수 방식
function Person(name, gender) {
  this.name = name;
  this.gender = gender;
}

var me  = new Person('Lee', 'male');
console.dir(me);

var you = new Person('Kim', 'female');
console.dir(you);

 

생성자 함수에 new를 붙이지 않고 호출한 경우)

this는 일반함수를 호출하면 전역객체를 바인딩하고 생성자 함수를 호출하면 빈 객체에 바인딩되는데 이러한 바인딩 방식이 다르기 때무에 new가 없는 생성자 함수나 new가 있는 일반함수는 오류가 발생할 수 있다.

 

function Person(name) {
  // new없이 호출하는 경우, 전역객체에 name 프로퍼티를 추가
  this.name = name;
};

// 일반 함수로서 호출되었기 때문에 객체를 암묵적으로 생성하여 반환하지 않는다.
// 일반 함수의 this는 전역객체를 가리킨다.
var me = Person('Lee');

console.log(me); // undefined
console.log(window.name); // Lee 
// window 전역객체임에도 이상한게 나오네 바인딩이 이상하게 됬기 때문

 

 

4. apply / call / bind 호출)

this를 명시적으로 바인딩하는 방법으로 모두 Function.prototype 객체의 메소드이다

 

apply)

var Person = function (name) {
  this.name = name;
};

var foo = {};

// apply 메소드는 생성자함수 Person을 호출한다. 이때 this에 객체 foo를 바인딩한다.
Person.apply(foo, ['name']);

console.log(foo); // { name: 'name' }

 

function Person(name) {
  this.name = name;
}

Person.prototype.doSomething = function(callback) {
  if(typeof callback == 'function') {
    // --------- 1
    callback();
  }
};

function foo() {
  console.log(this.name); // --------- 2
}

var p = new Person('Lee');
p.doSomething(foo);  // undefined
  • 위 코드에서 우리는 원하는 값을 도출하지 못했다.
  • 이유는 foo()에서 바인딩하고 있는 this는 전역이기에 생성자함수에서의 this와 다른 방향을 보고있기 때문이다.

해결방안)

  • call 사용
  • bind 사용
function Person(name) {
  this.name = name;
}

Person.prototype.doSomething = function (callback) {
  if (typeof callback == 'function') {
    callback.call(this);
  }
};

function foo() {
  console.log(this.name);
}

var p = new Person('Lee');
p.doSomething(foo);  // 'Lee'

 

function Person(name) {
  this.name = name;
}

Person.prototype.doSomething = function (callback) {
  if (typeof callback == 'function') {
    // callback.call(this);
    // this가 바인딩된 새로운 함수를 호출
    callback.bind(this)();
  }
};

function foo() {
  console.log('#', this.name);
}

var p = new Person('Lee');
p.doSomething(foo);  // 'Lee'

 

 

 

 

 

 

 

참고자료 : 블로그1 , 블로그2

'CS > 프로그래밍' 카테고리의 다른 글

버블링, 캡쳐링  (0) 2023.12.07
클로저  (1) 2023.12.07
Flex 속성(CSS)  (0) 2023.12.01
TDD  (0) 2023.11.03
원시 값과 참조 값  (0) 2023.11.03