javascript basics #4 오브젝트 메소드 this

Jake Seo·2020년 8월 24일
1

javascript-basics

목록 보기
4/7

javascript basics #4 오브젝트 메소드 this

오브젝트 개념

오브젝트는 주로 현실 세계의 실체들을 표현하기 위해서 만들어진다. 이를테면 users, orders와 같은 것들이 있다.

let user = {
  name: "Jake Seo",
  age: 28
};

실제 세계에서는 user 라는 실체가 행동을 할 수 있다 : 장바구니에 물건을 담거나, 로그인을 하거나, 로그아웃을 할 수 있다.

행동은 오브젝트 프로퍼티의 함수들(메소드)로 표현된다.

메소드 예제

user에게 hello라고 말하는 방법을 가르쳐보자.

let user = {
  name: "Jake Seo",
  age: 28
};

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

user.sayHello(); // Hello!

위에서 함수표현식을 이용하여 함수를 만들고 user.sayHello 에 할당해주었다. 이렇게 어떤 오브젝트가 갖는 함수를 method라고 한다.

위처럼 함수 표현식을 이용하지 않고, 미리 선언한 함수를 이용하여 method를 만드는 방법도 있다.

function sayHello() {
  alert("Hello!");
}

user.sayHello = sayHello;
user.syaHello(); // Hello!

객체지향 프로그래밍(OOP란?)

어떠한 실체를 표현하기 위해서 객체를 사용할 때, 그러한 행위를 객체지향 프로그래밍(object-oriented programming)이라고 한다. 짧게 말해 OOP라고 한다.

OOP는 매우 커다란 개념이다. 올바른 실체를 선택하는 방법? 실체들 사이의 상호작용을 조직하는 방법? 이러한 것들을 아키텍쳐라고 한다. 그리고 이러한 주제에 대한 좋은 책들이 있는데 "Design Patterns: Elements of Reusable Object-Oriented Software", "Object-Oriented Analysis and Design with Application" 과 같은 책이 있다.

메소드 짧게 표현하기

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

위와 같은 표현식은

user = {
  sayHello() {
    alert("Hello");
  }
}

위와 같이 짧게 표현될 수 있다.

사실 위의 두 표현은 완전히 일치하는 것은 아니다. 오브젝트 상속에 대한 미묘한 차이가 있긴 하지만 나중에 커버하도록 하겠다. 그런데 거의 모든 경우에서 짧은 케이스가 선호된다.

메소드에서의 "This"

오브젝트의 메소드가 오브젝트 내부의 데이터에 접근하는 일은 매우 흔하다.

이를테면, 이전에 만들었던 user.sayHello()username을 필요로 한다고 가정해보자.

let user = {
  name : "Jake Seo",
  age : 28,
  sayHello() {
    // this는 현재 오브젝트를 의미한다.
    alert(`Hello ${this.name}`);
  }
}

여기서의 this 키워드는 user를 가리키기 위해 사용되었다.

물론, this 대신에 그냥 user를 바로 써도 외부 변수를 참조하기 때문에 작동은 할 수 있다.

let user = {
  name : "Jake Seo",
  age : 28,
  sayHello() {
    // this는 현재 오브젝트를 의미한다.
    alert(`Hello ${user.name}`);
  }
}

위와 같이 작성해도 결과가 잘 나온다.

그런데 만일

let user2 = user;
user = null;

user2.sayHello();

위와 같은 작업을 한다면, Cannot read property 'name' of null at ... 과 같은 오류가 발생한다.

그런데 만일,

alert(`Hello ${user.name}`);

이 부분을 user 대신 this로 하였으면 자기 자신 객체의 name을 참조하여 얼마든지 사용이 가능했을 것이다.

this는 어딘가에 묶여있지 않다.

자바스크립트에서 키워드 this는 다른 많은 프로그래밍 언어와는 다르게 행동한다. 일단 this 키워드는 어떠한 함수 내부에서도 사용될 수 있다.

다음과 같은 코드를 이용해도 아무런 에러도 발생하지 않는다.

function sayHello() {
  alert(this.name);
}

this의 값은 컨텍스트에 의존하여 런타임 동안에 연산된다.

예를 들면 여기에 같은 함수가 다른 두개의 오브젝트에 할당되고 호출 시에 다른 "this"를 갖는다.

let user = {name: "John"};
let admin = {name: "Ann"};

function sayHello() {
  alert(this.name);
}

// 두 개의 오브젝트에서 같은 함수를 사용한다.
user.sayHello = sayHello;
admin.sayHello = sayHello;

user.sayHello(); // John
admin.sayHello(); // Ann

user["sayHello"]();
admin["sayHello"]();

오브젝트 없이 호출 시에는 this === undefined가 된다.

오브젝트 없이도 함수 호출이 가능하다.

function sayHello() {
  alert(this);
}

sayHello(); // undefined

엄밀히 말하자면 strict mode에서는 undefined가 나오겠지만, 브라우저에서는 window객체가 나올 것이다.

일반적으로 이러한 호출은 프로그래밍 에러이다. this가 함수 안에 있다는 것은 오브젝트 단위에서 해당 함수를 호출한다고 기대한다.

묶여있지 않은 this의 영향

만일 다른 프로그래밍 언어를 하다 왔다면, 묶여있는 this에 대해 익숙할 것이다. this가 묶여있는 경우에는 오직 자신의 오브젝트만 가리킨다.

자바스크립트에서는 this가 자유롭다. 실행시간에 결정되는 값이고 메소드가 어디에서 정의되었는지에 의존하지 않는다. 하지만 . 전에 어떤 오브젝트가 쓰여져 있었는지에 대해 의존한다.

실행 시간에 결정되는 this는 장단점이 있다. 한 편으로는 다른 오브젝트들에게도 재사용이 가능하다. 다른 한편으로는 더 많은 실수를 유발할 가능성이 커진다.

현재는 이 this가 잘 디자인되었는지 판단할 때는 아니다. 어떻게 사용해야되는지에 대해 알아보고 어떻게 이익을 취할 수 있고 어떻게 버그를 피할 수 있는지 알아보자.

화살표 함수는 this가 없다

화살표 함수는 특별하다. 화살표 함수에게는 자신만의 this가 존재하지 않는다. 만일 우리가 this를 특정한 함수에서 참조했다면, 외부의 일반 함수로부터 가져올 것이다.

외부의 user.sayHello() 메소드로부터 arrow()this를 사용하는 예제가 여기 있다.

let user = {
  firstName: "Jake",
  sayHello() {
    let arrow = () => alert(this.firstName);
    arrow();
  }
};

user.sayHello(); // "Jake"
let user = {
  firstName: "Jake",
  sayHello() {
    let arrow = function() { alert(this.firstName) };
    arrow();
  }
};

user.sayHello(); // undefined

이게 바로 화살표 함수의 특별한 기능이다. 따로 분리된 this를 사용하길 원하지 않을 때 이러한 특성이 매우 유용하다. this를 그저 외부 컨텍스트로부터 가져온다.

요약

  • 오브젝트 내부에 포함된 함수들을 method라고 부른다.
  • method는 object.doSomething()과 같은 방식으로 호출 가능하다.
  • method는 자신의 objectthis 키워드를 이용하여 부를 수 있다.

this는 런타임에 정의된다.

  • 함수가 선언되었을 때, 함수가 this를 사용한다고 해도 this는 함수가 호출될 때까지는 정의되지 않는다.

  • this를 이용한 함수는 오브젝트간에 복사될 수 있다.

  • 함수가 "method" 문법에서 호출될 때, object.method(), this의 값이 그 때만큼은 .앞의 object를 가리킨다.

  • 화살표 함수는 this를 갖지 않는 것도 꼭 기억하자. 화살표 함수 내부에서 this를 사용하면 바로 위의 스코프에서 this를 참조한다.

문제 1

아래의 코드를 실행시키면 결과는?

function makeUser() {
  return {
    name: "Jake",
    ref: this
  };
};

let user = makeUser();

정답은 에러발생이다.

이유는 this가 정의되는 시점은 호출될 때이기 때문이다.

makeUser() 내부의 this의 값은 undefined이다. 왜냐하면 함수로서 호출되었지, .과 함께 메소드로서 호출되지 않았기 때문이다.

this의 값은 모든 함수에 대한 것이다. 코드 블록과 오브젝트 리터럴은 영향을 미치지 않는다.

그래서 ref: this는 오직 함수의 현재의 this만 갖는다.

우리는 함수를 다시 작성하고 똑같이 undefined를 갖는 this를 만들어낼 수 있다.

function makeUser(){
  return this; // 이 경우에는 오브젝트 리터럴이 없다.
}

alert(makeUser().name);

보다시피 alert(makeUser().name)은 이전 예제인 alert(user.ref.name)과 같은 결과를 보인다.

여기 반대되는 경우가 있다.

function makeUser() {
  return {
    name: "John",
    ref() {
      return this;
    }
  };
};

let user = makeUser();

alert(user.ref().name); // John

이렇게 하면 작동한다. 그리고 this. 전의 오브젝트를 갖는다.

문제 2

체이닝이 되게 만들어보자.

let ladder = {
  step: 0,
  up() {
    this.step++;
  },
  down() {
    this.step--;
  },
  showStep: function() { // shows the current step
    alert( this.step );
  }
};

위의 코드는

ladder.up();
ladder.up();
ladder.up();
ladder.down();
ladder.showStep(); // 2; 

위와 같은 식으로는 잘 동작하는데,

ladder.up().up().up().down().showStep();

위와 같은 형식으로는 잘 동작하지 않는다.

let ladder = {
  step: 0,
  up() {
    this.step++;
	return this;
  },
  down() {
    this.step--;
	return this;
  },
  showStep: function() { // shows the current step
    alert( this.step );
    return this;
  }
};

위와 같이 작성하면 Chaining도 잘 된다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글