[Javascript] this

Choise.o·2023년 8월 5일
0

this in JS

  • 대부분 언어에서 this는 인스턴스 자신을 나타낸다.
  • 하지만 JS에서는 함수 호출방식 에 따라 this에 바인딩할 객체가 “동적으로 결정” 된다.

1. 일반 함수 호출 방식(Regular Function Call)

  • 함수명(); 으로 호출
  • this는 Global Object에 바인딩 된다. (Browser-side에서 window객체)
var name = `Kim`; // 전역변수로 선언 ⇒ 전역객체(window)의 프로퍼티로 할당됨

function foo() {
    console.log(this);        // window
    console.log(this.name);   // window.name이므로 Kim
}

foo();
  • 일반 함수 / 함수의 내부 함수 / 콜백 함수 전부 다 해당한다.
var age = 100;

function foo() {
    var age = 99;
    bar(age);       // 함수의 내부 함수
    callback(age);  // 콜백 함수
}

function bar() {
    console.log(this.age);  // window.age이므로 100
}

function callback() {
    setTimeout(function() {
        console.log(this);     // window
        console.log(this.age); // window.age이므로 100
    }, 100);
}
foo();
  • 변수 age를 const로 선언할 경우 undefined가 출력된다. ES6문법 const / let은 window 전역객체의 프로퍼티로 할당되지 않는다.

2. 도트 표기법(Dot Notation)

  • Dot Notation 이란? 특정 객체를 생성하고 그 객체의 프로퍼티에 도트(.)로 접근하는 방식
  • this는 프로퍼티를 소유하고 있는 객체에 바인딩 된다.
// Dot Notation
var age = 100;

// 리터럴 방식으로 객체 생성
var ken = {
    age: 35,
    foo: function() {
        console.log(this.age);  // ken.age이므로 35
    }
}

ken.foo();

var fn = ken.foo;
fn();    // 일반함수 호출 => window.age이므로 100


// 프로토타입 객체의 경우에도 해당 함수를 호출한 객체에 바인딩된다.
function Person(name) {
    this.name = name;
}

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

var foo = new Person(`Lee`);
console.log(foo.getName());    // foo에 바인딩되므로 Lee

Person.prototype.name = `Kim`;
console.log(Person.prototype.getName());  // prototype Object에 바인딩되므로 Kim

3. new 키워드를 사용한 함수 실행

  • 함수에 new를 붙여 호출하면 그 함수는 ‘생성자 함수’로 동작한다.
* 생성자 함수 동작 방식 *
function Person(paramName) {
	this.name = paramName;
}
var person1 = new Person('kim');  // { name:kim }

// 1) 빈 객체(Empty Object) 생성 후 생성자 함수 내의 this를 이 객체에 바인딩 
// => {}가 생성되고 this에 바인딩 되었다!

// 2) this를 통한 프로퍼티 생성
// => this.name = paramName 구문에 의해 { name:kim } 프로퍼티가 생성되었다! 

// 3) 위에서 암묵적으로 생성된 객체 반환
// 3-1) return 이 없으면 this에 바인딩된 객체 반환
// => this에 바인딩된 { name:kim } 객체가 반환되어 person1에 할당되었다!

// 3-2) return 으로 다른 객체를 반환할 수도 있음. (아래 예시)
function Stranger(strangeName) {
	this.name = strangeName;
  	return { name: 'strange' };
}

var stranger = new Stranger('park'); // { name: strange }
// 이 경우 this가 생성자 함수에서의 역할을 수행하지 못한다. ⇒ 보통 return 명시하지 않음
  • this는 생성자 함수를 실행하여 반환된 객체에 바인딩 된다.
function Person(name) {
    this.name = name;
}
var kim = new Person(`Kim`);
console.log(kim.name);      // kim.name 이므로 Kim


// 일반 함수로 호출 -> 암묵적으로 객체를 생성하여 반환하지 않는다.
// 일반 함수의 this는 전역객체에 바인딩된다.
var lee = Person(`Lee`);
console.log(lee.name);     // undefined.name 이므로 TypeError



4. 명백한 바인딩(Explicit Binding)

  • this를 명확하게 지정하는 것
  • function.prototype.apply / function.prototype.call / function.prototype.bind 사용
  • this는 지정한 객체에 바인딩 된다.
/*
* 1) func.apply(thisArg, [argsArray])
* thisArg: 함수 내부의 this에 바인딩할 객체
* argArray: 함수에 전달할 argument의 배열
*/

function Person(name) {
    this.name = name;
}
var foo = {};
// apply 는 생성자 함수 Person을 호출하고, 
// 이때 this에 객체 foo를 바인딩한다.
Person.apply(foo, [`Foo`]);

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

/*
* 2) func.call(thisArg[, arg1[, arg2 …]])
* thisArg: 함수 내부의 this에 바인딩할 객체
* arg: 함수에 전달할 argument
*/
Person.call(foo, `Foo`);

/*
* 3) func.bind(thisArg)()
* func에 인자로 전달한 thisArg가 this에 바인딩된 새로운 함수를 반환
* apply나 call처럼 함수를 실행하지 않으므로 별도로 호출해줘야 한다.
*/
Person.bind(foo)(`Foo`);

this의 동적 바인딩에 의한 문제 회피 방법

1) 일반함수 호출방식 관련 : Strict mode 에서 일반 함수 실행
⇒ this가 window객체와 의도치 않게 바인딩되어 발생하는 문제를 예방할 수 있다.

/*
* strict mode 사용 방법: 스크립트 코드 최상단에 ‘use strict’ 구문 추가
*/

‘use strict’;

var name = `Kim`;

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

foo();

2) 생성자 함수 관련 : Scope-Safe Constructor Pattern
⇒ 휴먼에러 (ex. new 키워드 누락) 으로 인해 this 가 생성자 함수 실행으로 반환된 객체와 바인딩되지 않은 문제를 예방할 수 있다.

/*
* Scope-Safe Constructor Pattern
*/
function A(arg) {
// 생성자 함수가 new 연산자와 함께 호출되면 빈객체를 생성하고 this에 바인딩한다.

  if (!(this instanceof arguments.callee)) {
    return new arguments.callee(arg);
  }

/*
* arguments.callee = 호출된 함수의 이름
* this가 호출된 함수(arguments.callee, 여기서는 A)의 인스턴스가 아니면 
* new 연산자를 사용하지 않은 것이므로 
* new와 함께 생성자 함수를 호출하여 인스턴스를 반환한다.
*/


// 프로퍼티 생성과 값의 할당
  this.value = arg ? arg : 0;
}

var a = new A(100);
var b = A(10);

console.log(a.value);  // 100
console.log(b.value);  // 10

Reference

1개의 댓글

comment-user-thumbnail
2023년 8월 5일

유익한 글이었습니다.

답글 달기