[JavaScript] 객체(2) - 메서드와 this, new 연산자와 생성자 함수, 옵셔널 체이닝

김서현·2023년 4월 21일

JavaScript 스터디

목록 보기
4/8
post-thumbnail

메서드와 this

자바스크립트에선 객체의 프로퍼티에 함수를 할당해 객체에게 행동할 수 있는 능력을 부여해준다.

메서드 만들기

메서드 : 객체 프로퍼티에 할당된 함수
ex) 객체 user에게 인사할 수 있는 능력 부여

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

user.sayHi = function() {
  alert("안녕하세요!");
};

user.sayHi(); // 안녕하세요!

-> sayHiuser에 할당된 메서드!

메서드 단축 구문

객체 리터럴 안에 메서드를 선언할 때

// 아래 두 객체는 동일하게 동작합니다.
user = {
  sayHi: function() {
    alert("Hello");
  }
};

// 단축 구문을 사용
user = {
  sayHi() { // "sayHi: function()"과 동일합니다.
    alert("Hello");
  }
};

메서드와 this

메서드에서 외부 변수를 참조해 객체에 접근해보자

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

  sayHi() {
    alert(user.name); // 외부 변수 : user
  }

};

잘 작동한다. 그러나,
❗ 이렇게 외부 변수를 사용해 객체를 참조하면 예상치 못한 에러가 발생할 수 있다!
user를 복사해서 다른 변수(admin)에 할당하고, user는 전혀 다른 값으로 덮어썼다고 가정하면, sayHi()는 원치 않는 값(null)를 참조할 것이다.

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

  sayHi() {
    alert( user.name ); 
  }

};

let admin = user;
user = null; // user를 null로 덮어씁니다. --> user의 name은 사라짐!

admin.sayHi(); // // Error: Cannot read property 'name' of null

🔽 그렇다면!!
메서드 내부에서 this 키워드를 사용하면 객체에 접근할 수 있다.
👉 this : 메서드를 호출할 때 사용된 객체를 나타냄

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

  sayHi() {
    // 'this'는 '현재 객체'를 나타냅니다.
    alert(this.name);
  }

};

user.sayHi(); // John

자유로운 this

this가 가리키는 것은 런타임에 결정된다.
동일한 함수더라도 다른 객체에서 호출하면 this가 참조하는 값이 달라진다.

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

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

// 별개의 객체에서 동일한 함수를 사용함
user.f = sayHi;
admin.f = sayHi;

user.f(); // John  (this == user)
admin.f(); // Admin  (this == admin)

admin['f'](); // 참고 : Admin (점과 대괄호는 동일하게 동작함)

객체 없이 호출하기

객체가 없어도 호출할 수 있다.
🔽 "use strict" 엄격 모드에서

function sayHi() {
  alert(this);
}

sayHi(); // undefined

그러나 엄격모드가 아니라면?
this가 전역 객체를 참조한다. 따라서 브라우저 환경이면 thiswindow라는 객체를 참조한다.
👉 주의하고 개발하기!


this가 없는 화살표 함수

화살표 함수는 '고유한' this를 가지지 않는다.
화살표 함수에서 this를 참조하면, 화살표 함수가 아닌 '평범한' 외부 함수에서 this 값을 가져온다.

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

user.sayHi(); // 보라

👉 함수 arrow()thisarrow()가 아닌 user.sayHi()를 가져온다.

위와 같이 별개의 this가 만들어지는 건 원하지 않고, 외부 컨텍스트에 있는 this를 이용하고 싶을 때 화살표 함수를 이용하면 된다!



new 연산자와 생성자 함수

유사한 객체를 여러 개 만들어야 할 때 유용한 생성자 함수!
👉 재사용할 수 있는 객체 생성 코드를 구현

생성자 함수

일반 함수와의 기술적 차이는 없다!

관례
1. 험수 이름 첫 글자는 대문자로 시작
2. 반드시 'new' 연산자를 붙여 실행

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("보라");

alert(user.name); // 보라
alert(user.isAdmin); // false

new User(...)를 써서 함수를 실행하면 어떤 알고리즘이 동작할까?
1. 빈 객체를 만들어 this에 할당
2. 함수 본 문 실행. this에 새로운 프로퍼티를 추가해 this를 수정
3. this를 반환 ( => user에 할당되는 것! )
🔽그 과정을 보자면🔽

function User(name) {
  // this = {};  (빈 객체가 암시적으로 만들어짐)

  // 새로운 프로퍼티를 this에 추가함
  this.name = name;
  this.isAdmin = false;

  // return this;  (this가 암시적으로 반환됨)
}

➕ 재사용할 필요가 없는 복잡한 객체를 만들어야 한다면?
익명 함수 이용

let user = new function() {
  this.name = "John";
  this.isAdmin = false;
  // 사용자 객체를 만들기 위한 여러 코드.
  // 지역 변수, 복잡한 로직, 구문 등의
  // 다양한 코드가 여기에 들어갑니다.
};

👉 재사용은 막으면서 코드를 캡슐화


생성자와 return 문

생성자 함수엔 보통 return 문이 없다.
👉 반환해야 할 것은 모두 this에 저장되고, this는 자동으로 반환되기 때문

❓ 만약 return문이 있다면?

  • 객체를 return 한다면 this 대신 객체가 반환
  • 아무것도 return 하지 않거나 원시형을 return 한다면 return문 무시
function BigUser() {

  this.name = "원숭이";

  return { name: "고릴라" };  // <-- this가 아닌 새로운 객체를 반환함
}

alert( new BigUser().name );  // 고릴라
function SmallUser() {

  this.name = "원숭이";

  return; // <-- this를 반환함
}

alert( new SmallUser().name );  // 원숭이

생성자 내 메서드

생성자 함수로 객체 내부에 메서드도 추가해줄 수 있다.

function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( "제 이름은 " + this.name + "입니다." );
  };
}

let bora = new User("이보라");

bora.sayHi(); // 제 이름은 이보라입니다.

/*
bora = {
   name: "이보라",
   sayHi: function() { ... }
}
*/


옵셔널 체이닝 '?.'

프로퍼티가 없는 중첩 객체를 에러 없이 안전하게 접근할 수 있다.

옵셔널 체이닝이 필요한 이유

ex) 사용자가 여러 명 있는데 그중 몇 명은 주소 정보를 가지고 있지 않다고 가정해보자.
이럴 때 user.address.street을 사용해 주소 정보에 접근하면 에러가 발생할 수 있다!

let user = {}; // 주소 정보가 없는 사용자

alert(user.address.street); // TypeError: Cannot read property 'street' of undefined

👉 address는 undefined로 들어오지만, 그 이후 street까지 접근 불가

ex2) 브라우저에서 동작하는 코드를 개발할 때 페이지에 존재하지 않는 요소에 접근한다면 ㅜㄴ제 발생

// querySelector(...) 호출 결과가 null인 경우 에러 발생
let html = document.querySelector('.my-element').innerHTML;

옵셔널 체이닝

옵셔널 체이닝 ?.은 앞의 평가 대상이 undefinednull이면 평가를 멈추고 undefined를 반환한다.

ex1) 이전 ex1)에 적용해보자

let user = {}; // 주소 정보가 없는 사용자

alert( user?.address?.street ); // undefined, 에러가 발생하지 않습니다.

❗ 옵셔널 체이닝을 남용해서는 안된다.
위 예시에서 논리상 user은 반드시 있어야 하는데 address는 필수값이 아니다.
따라서 user.address?.street을 사용하는 것이 바람직하다.

?. 앞 변수는 꼭 선언되어 있어야 한다.
변수 user가 선언되어있지 않으면 user?.anything 평가시 에러가 발생한다.


단락 평가

단락 평가 : ?.는 왼쪽 평가대상에 값이 없으면 즉시 평가를 멈춘다.

let user = null;
let x = 0;

user?.sayHi(x++); // 아무 일도 일어나지 않습니다.

alert(x); // 0, x는 증가하지 않습니다.

?.()?.[]

?.은 연산자가 아니고 함수나 대괄호와 함께 동작하는 특별한 문법 구조체이다.

  • 존재 여부가 확실치 않은 함수를 호출할 때 ?.()
let user1 = {
  admin() {
    alert("관리자 계정입니다.");
  }
}

let user2 = {};

user1.admin?.(); // 관리자 계정입니다.
user2.admin?.(); // ?.에서 단락평가에 의해 멈추고 함수를 실행하지 않음.
  • 존재 여부가 확실치 않은 객체 프로퍼티에 접근할 때 ?.[]
let user1 = {
  firstName: "Violet"
};

let user2 = null; // user2는 권한이 없는 사용자라고 가정해봅시다.

let key = "firstName";

alert( user1?.[key] ); // Violet
alert( user2?.[key] ); // undefined

alert( user1?.[key]?.something?.not?.existing); // undefined

?.delete와 조합해 사용도 가능하다

delete user?.name; // user가 존재하면 user.name을 삭제

?. 읽기나 삭제는 가능하지만, 쓰기에는 사용할 수 없다.

// user가 존재할 경우 user.name에 값을 쓰려는 의도

user?.name = "Violet"; // SyntaxError: Invalid left-hand side in assignment

👉 에러가 발생하는 이유 : undefined = "Violet"이 되기 때문.

1개의 댓글

comment-user-thumbnail
2023년 4월 30일

타입스크립트를 쓰면서 옵셔널 체이닝을 많이 사용했었는데 그냥 에러를 없애기 위해 남용하는 게 아니라 말씀해주신 주의사항을 잘 고려하면서 사용해야겠다는 생각이 들었어요!! 좋은 아티클 감사합니다 ㅎㅎ

답글 달기