ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] this 예약어
    프로그래밍 언어/JavaScript 2023. 12. 7. 08:56

    개요

    • 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

    댓글

Designed by Tistory.