All Articles

JavaScript This에 대해 알아보기

Reference

Javascript Info, “this”
Javascript Info 의 내용을 번역하고, 일부 제 의견을 추가했습니다.
혹여 게재한 내용에 문제가 있다면 알려주세요. 바로 조취하겠습니다.


Object Methods, “This”

객체는 이런식으로 구성됩니다.

let user = { 
  name: "John", 
  age: 30, 
};

객체는 보통 현실 세계의 개체를 표현하기 위해 만들어집니다. 가령 사용자와 같은 것들이 있을 수 있습니다.

그리고 현실 세계에서 사용자는 여러가지 행동을 할 수 있습니다. 쇼핑 카트에서 어떤 것을 고른다던지, 로그인을 한다던지 등등등~

자바스크립트에서 이런 행동을 표현 할 때, property에서의 function, 즉 메서드로 나타냅니다. 메서드가 무엇인지는 이후에 설명하도록 하겠습니다~

시작하기 위해서, 유저 객체가 “Hello” 라고 인사하도록 만들어 보겠습니다.

let user = {
	name: "John",
	age: 30,
};

user.sayHi = function() {
	alert("Hello");
};

user.sayHi();

여기서 우리는 함수 표현식을 사용했습니다. 그리고 user 객체 안의 user.sayHi 라는 property에 사용한 함수를 할당했습니다.

그리고 호출하면 끝! 유저는 우리에게 “Hello” 라고 인사하네요.

이때 객체의 프로퍼티에 할당된 함수를 우리는 Method 라고 부릅니다.

개체를 나타내기 위해서 객체를 사용하여 코드를 짰을 때, 우리는 이것을 “Object-oriented Programming” 이라고 부릅니다. “OOP” 라고 흔히 하는 그것입니다.

올바른 객체를 어떻게 선택하고, 객체 끼리 상호작용이 잘 되도록 어떻게 구성 하는지… 이런 논의의 결과로 만들어진 것이 바로 Architecture 입니다.

원글 작성자는 이런 개념을 공부하기 위해 “Design Patterns: Elements of Reusable Object-Oriented Software” 라는 책을 추천하고 있습니다.


Method shorthand

Object literal 에서 메서드를 짧게 줄이는 방법이 있습니다.

// 아래 두 예제는 동일합니다.

user = {
	sayHi: function() {
		alert("Hello");
	}
};

// Method 가 짧아지는게 보이시죠?
user = {
	sayHi() {
		alert("Hello");
	}
};

참 신기하네요. function 선언 없이 함수를 선언하여 사용할 수 있습니다.

다만, 완전히 식별 가능한 표기법은 아닙니다. 이 놀라운 일은 Object의 inheritance 와 연관된 미묘한 차이 때문입니다. 여기서는 중요한게 아니니 넘어가도록 하겠습니다. 대부분의 경우에 하단의 짧은 문법을 선호합니다.


“this” in methods

this란 축약하자면, 어떤 작업 수행을 위해 object에 저장된 정보에 접근하는데 필요한 object method 입니다. 예를들어서 위에서 user.sayHi() 라는 함수를 만들었죠? 이 함수에는 user의 이름이 필요할 수도 있습니다.

user = {
	sayHi() {
		alert("Hello, John!");
	}
};

이때 객체에 접근하기 위해서, “this” 키워드를 사용할 수 있습니다.

“this”의 value는 object의 “dot” 표시 전입니다.

예제를 보시죠

let user = {
	name: "John",
	age: 30,
	
	sayHi() {
		alert(`Hello, ${this.name}`);
	}
};

user.sayHi();     // "Hello, John"

user.sayHi() 를 호출했을 때 this.name 부가 John 이라는 문자열을 호출한 것을 볼 수 있습니다.

이건 정확히 아래의 예제와 똑같이 작용하는 것을 알 수 있어요.

let user = {
	name: "John",
	age: 30,
	
	sayHi() {
		alert(`Hello, ${user.name}`);
	}
};

user.sayHi();     // "Hello, John"

보시면 “this”“dot” 표기 전의 user를 가리키고 있는 것을 알 수 있습니다.

다만 이렇게 객체를 직접 가리키는 표기법은 신뢰하기 어려운 코드가 될 수 있어요.

우리가 user 객체의 정보를 다른 변수에 복사 하기로 결정 했다고 가정해 봅시다!

let admin = user;
{ name: "John", age: 30, sayHi: f };

// user 객체는 필요가 없어서 지웠다고 가정해보겠습니다.
user = null;

// 그리고 sayHi는
	sayHi() {
		alert(`Hello, ${user.name}`);
	}
};

sayHi 함수는 user의 정보를 그대로 복사했기 때문에, admin.name이 아닌 user.name을 호출합니다. 하지만 user 객체는 이미 지워졌기 때문에, admin에서 sayHi() 를 호출 했을때 우리는 오류가 나타나는 것을 볼 수 있습니다.

만약 “this” 를 사용했다면, 오류를 볼 일이 없었겠죠? 🙂


“this” is not bound

자바스크립트에서 “this” 키워드는 다른 프로그래밍 언어랑 좀 다르게 동작된다고 합니다. “this”는 어떤 함수 속에서든 사용될 수 있어요. 아래와 같은 코드에서도 에러가 발생하지 않습니다 :)

function sayHi() {
	alert ( this.name );
};
sayHi();   // 단지 아무것도 호출되지 않을뿐입니다. this가 가리키는 것이 없으니까요.
					 // 실제로 this.name 없이 비운 상태로 alert를 호출해도 정상 작동합니다.

“this” 의 value는 실행하는 동안의 context에 따라서 평가돼요. 물론 그 내용은 어떤 것이든 가능합니다.

서로 다른 두 객체에 같은 sayHi() 함수를 할당 했을 때, 어떤 결과가 나오는지 살펴보겠습니다.

const CEO = { name: "Alice" };
const CTO = { name: "Simon" };

function sayHi() {
	alert( `Hello, I'm ${this.name}.` );
}

// 두 객체에 같은 함수를 사용해 보겠습니다.
CEO.greeting = sayHi;
CTO.greeting = sayHi;

// 객체의 메서드를 호출했을 때 두 객체가 가리키는 "this"는 각각 다른 것을 알 수 있습니다.
CEO.greeting();   // "Hello, I'm Alice."  Alice (this === CEO)
CTO.greeting();   // "Hello, I'm Simon."  Simon (this === CTO)

class 예제를 활용해서 작성해 보겠습니다.

class Member {
	constructor(name, age) {
		this.name = name;
		this.age = age;
	}
	sayHi() {
		alert(`Hello, I'm ${this.name}. ${this.age}`);
	}
}

const CEO = new Member("Alice", 35);
const CTO = new Member("Simon", 40);

console.log(CEO, CTO);

CEO.sayHi();
CTO.sayHi();

constructor의 this는 새 객체를 생성할 때 적용되고, sayHi() 함수의 this는 실행시켰을 때 적용되는 것을 알 수 있습니다.

⭐️⭐️ 결국 this가 가리키는 것은 실행되고 있는 object 라는 것을 알면 되겠습니다.


여러가지 경우의 수

  • 객체 없이 부를 때: this === undefined

    객체 없이 함수에서 this 를 사용하는 것도 가능합니다.

    function sayHi() {
        "use strict"
        alert(this);
    }
    
    sayHi(); // undefined

    물론 가리키는 대상이 없기 때문에, 결과 값은 undefined 입니다. 다만 undefined 표시는 strict 모드일 때만 적용됩니다. strict 모드 미적용시 (크롭탭 사용), global object인 브라우저 안에서의 ‘window’를 가리킵니다. 이 부분에서는 자바스크립트의 ‘this’ 는 유연성이 장난 아니니깐 오류에 주의해서 사용 하라는 것 같습니다.

    “use strict” 는 이름 그대로 문법을 엄격하게 적용하는 개념입니다. 원래 자바스크립트 에서는 선언하지 않은 변수에 값을 할당 해도, Hoisting의 개념에 의해서 자동으로 변수가 선언됩니다. 하지만 “use strict” 모드에서는 앞서 선언하지 않은 변수에 값을 할당할 수 없습니다.

  • Internals: Reference Type

    ** 이 부분은 edge-cases 를 이해하기 위한 advanced topic 이라고 합니다. 좀 더 복잡하고 근본적인 내용을 함유하고 있기 때문에 어려울 수 있습니다.

    복잡한 method call은 ‘this’ 의 대상을 잃어버릴 수 있습니다. 예를 들면~

    let user = {
    	name: "John",
    	hi() { alert(this.name); },
    	bye() { alert("Bye"); },
    };
    
    user.hi();  // John (가리키는 대상도 명확하고 아주 잘 작동합니다)
    
    // 그럼 user.name에 따라서 hi 혹은 bye 메서드를 호출하는 코드를 짜볼까요?
    (user.name === "John" ? user.hi : user.bye)();

    결과를 보면 아무것도 뜨지 않습니다. 참고로 말씀드리자면, 위 예제는 즉시 실행 함수로 조건식의 결과 값을 실행시키는 코드입니다. (조건식의 결과 값이 함수이기 때문에 호출이 가능합니다)

    let user = {
    	name: "John",
    	hi() { alert(user.name); },
    	bye() { alert("Bye"); },
    };
    (user.name === "John" ? user.hi : user.bye)();
    
    // 혹은
    user.name === "John" ? user.hi() : user.bye();
    // 메서드를 개별로 실행시켜도 정상 작동합니다.

    위 예제에서 this를 user로 바꿔주고, 같은 조건식을 실행시키면 올바르게 John을 출력하게 됩니다. 왜냐면 가리키는 대상이 명확 하니깐~

    다시 왜 즉시 실행 함수에서 this가 작동하지 않는지 알아보겠습니다. 이유를 알기 위해서는 obj.method() 가 어떻게 작동 하는지 알아야 합니다.

    우선 우리는 obj.method() 선언에 두 개의 작업이 있다는 것을 인지해야 합니다.

    1. 첫째는 dot 입니다. dot은 obj.method 의 property 를 검색합니다.
    2. 그리고 property 를 실행하기 위한 () 괄호 입니다.

    자, 그럼 ‘this’ 에 대한 정보가 첫 번째에서 두 번째 단계로 어떻게 넘어 가는지 알아볼까요?

    두 작업 단계를 별도의 라인으로 분리 해보겠습니다. ‘this’ 키워드는 분명히 가리키는 대상을 잃어버릴 거에요😢

    let user = {
    	name: "John",
    	hi() { alert(this.name); }
    }
    
    // property를 가져오는 것과 method를 호출하는 것을 두 개의 라인으로 분리해 보겠습니다.
    let hi = user.hi;
    hi(); // 빈 화면이 출력됩니다

    분리 과정을 살펴보겠습니다. 먼저 hi 라는 변수에 user.hi function을 할당합니다. 그리고 마지막 줄은 완전히 독립돼 있습니다. this를 찾을 수가 없어요.

    user.hi()가 작동하도록 하기 위해서, JavaScript는 한 가지 트릭을 사용합니다.

    • ’ . ’ 은 function 이 아닌 특별한 Reference Type 의 값을 리턴합니다.
    • 혹시라도 궁금하시면 링크를 살펴보세요… Hell

    Reference Type은 “명세 타입” 입니다. 명쾌하게 사용할 수는 없지만, 언어에 의해 내부적으로 사용 되어집니다.

    Reference Type의 값은 세 가지 Value 의 조합입니다.

    (base, name, strict)

    • base 는 object 입니다.
    • name 은 property name 입니다.
    • strict 는 true 입니다. (use strict 를 시행하고 있다면)

    그래서 user.hi 에 접근한 property의 결과는 function이 아니라 Reference Type의 value 입니다. (strict 모드에서)

    // Reference Type Value
    (user, "hi", true)

    괄호가 Reference Type에서 호출됐을때, Reference Type은 위의 세 가지 Value를 받는 규칙을 따릅니다. object, object의 method에 대한 완전한 정보를 받아서, this가 의도에 맞는 대상을 가리키도록 합니다. (이 경우에는 this가 가리키는 값은 user 겠죠??)

    Reference Type은 중개자 같은 특별한 internal type 입니다. ’ . ‘으로 부터 정보를 넘겨 받고, () 괄호로 그것을 호출하죠.

    그럼 다시 위에서 hi 라는 변수에 user.hi 를 할당한 예제를 볼까요?

    hi = user.hi 같은 operation 은 Reference Type 을 버리게 됩니다. 오로지 user.hi 가 가지고 있는 function 의 value 만 취하는 것이죠. 그 값을 넘겨받고 실행했을 때 당연히 ‘this’ 는 가리킬 대상을 잃어버리게 됩니다.

    결과적으로 this 의 value 는 (1)dot 을 사용해서 직접적으로 함수를 호출 했을 때 obj.method() and (2)square brackets 를 사용했을 때 obj’method’ 만 올바르게 작동합니다.

    앞으로 이런 문제를 해결하기 위해 func.bind() 와 같은 다양한 방법들을 배울 수 있을 겁니다 🤣


    Arrow functions have no “this”

    Arrow function 은 조금 특별합니다. 얘네는 스스로의 ‘this’를 가지지 않습니다. 만약 어떤 한 function 으로부터 this 를 참조한다면, 이 친구는 바깥에 있는 평범한 function 에서 ‘this’ 가 가리키는 값을 찾습니다.

    예제 코드를 한번 보겠습니다.

    let user = {
    	firstName: "Kim",
    	sayHi() {
    		let arrow = () => alert(this.firstName);
    		arrow();
    	}
    };
    user.sayHi();   // "Kim"

    이건 굉장히 특별한 특징입니다. ‘this’ 를 분리하고 싶지 않을 때 실제로 유용 하다고 하는데 (아직 잘 모르겠습니다) 어쨌든 굉장히 인상 깊습니다. 외부의 context에서 this를 가져갈 때 유용합니다. 이후에 Arrow function에 대해 더 깊이 배우게 될 것입니다.

    ** 참고사항

    함수 내부에서 일반 함수를 한번 더 정의하여 this 키워드를 사용할 경우에는, 당연히 this가 가리키는 대상을 찾지 못합니다. (this 의 실행 컨텍스트가 sayHi() 함수이기 때문)

    let user = {
    	firstName: "Kim",
    	sayHi() {
    		let arrow = function() {
    			alert(this.firstName);
    		}
    		arrow();
    	}
    };
    user.sayHi();   // "undefined"
    
    let user = {
    	firstName: "Kim",
    	sayHi() {
    		(function() {
    			alert(this.firstName);
    		})();
    	}
    };
    user.sayHi();   // "undefined"

    마무리 요약

    • 함수는 “Methods” 라고 불리는 object properties 에 저장된다.
    • Methods 는 object 가 어떤 행동을 하도록 한다. ex) object.doSomething()
    • Methods 는 ‘this’ 로 object 를 참조할 수 있다.

    this 의 값은 실행 될 때(run-time)에 정의 된다

    • function 이 선언될 때, this 를 사용할 수 있다. 하지만 this 는 function이 호출될 때 까지 어떤 값도 가지지 않는다.
    • function은 객체 끼리 복사될 수 있다.
    • method 에서 function 이 호출될 때, ( syntax: object.method() ), 호출되는 동안의 this 의 값은 object 다.

    그리고 꼭 기억해 주세요. Arrow function 은 this 를 가지지 않고, 바깥의 일반 함수로부터 this 가 가리키는 대상을 가져온다는 것!