JS 정리 #5

Hyun·2024년 4월 5일
0

23. 메서드와 this

객체: 사용자(user), 주문(order) 등과 같이 실제 존재하는 개체(entity)를 표현하고자 할 떄 생성됨.

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

이처럼 사용자를 나타내는 객체 user도 특정한 행동을 할 수 있다.

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

23.1. 메서드 만들기

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

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

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

객체 user에게 인사할 수 있는 능력을 부여.
객체 프로퍼티 user.sayHi에 함수를 할당해준다.

메서드(method) : 객체 프로퍼티에 할당된 함수 (예시에서는 user에 할당된 sayHi가 메서드)

let user = {
  // ...
};

// 함수 선언
function sayHi() {
  alert("안녕하세요!");
};

// 선언된 함수를 메서드로 등록
user.sayHi = sayHi;

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

이미 정의된 함수를 이용해서 메서드를 만들 수도 있다.

객체 지향 프로그래밍이란(object-oriented programming, OOP) ? 객체를 사용하여 개체를 표현하는 방식.

23.2. 메서드 단축 구문

객체 리터럴 안에 메서드를 선언할 때 사용할 수 있는 단축 문법

// 아래 두 객체는 동일하게 동작합니다.

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

// 단축 구문을 사용하니 더 깔끔해 보이네요.
user = {
  sayHi() { // "sayHi: function()"과 동일합니다.
    alert("Hello");
  }
};

위처럼 function을 생략해도 메서드를 정의할 수 있다.

23.3. 메서드와 this

메서드는 객체에 저장된 정보에 접근할 수 있어야 제 역할을 할 수 있다.
대부분의 메서드가 객체 프로퍼티의 값을 활용.

메서드 내부에서 this 키워드를 사용하면 객체에 접근할 수 있다.

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

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

};

user.sayHi(); // John

user.sayHi()가 실행되는 동안, this는 user를 나타낸다.
this를 사용하지 않고 외부 변수를 참조해 객체에 접근하는 것도 가능하다.

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

  sayHi() {
    alert(user.name); // 'this' 대신 'user'를 이용함
  }

};

그런데 이렇게 외부 변수를 사용해 객체를 참조하면,

예상치 못한 에러가 발생할 수 있다.

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

  sayHi() {
    alert( user.name ); // Error: Cannot read property 'name' of null
  }

};


let admin = user;
user = null; // user를 null로 덮어씁니다.

admin.sayHi(); // sayHi()가 엉뚱한 객체를 참고하면서 에러가 발생했습니다.

user 를 복사해서 다른 변수에 할당하고, user 는 다른 값으로 덮어 씌우면 sayHi()는 원치 않는 값(null)을 참조하게 된다.

alert 함수가 user.name 대신 this.name 을 인수로 받았다면 에러가 발생하지 않았다.

23.4. 자유로운 this

자바스크립트에서는 다른 프로그래밍 언어와 다르게 모든 함수에서 this를 사용할 수 있다.

function sayHi() {
  alert( this.name );
}
// 문법 에러가 발생하지 않는다.

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

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

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

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

// 'this'는 '점(.) 앞의' 객체를 참조하기 때문에
// this 값이 달라짐
user.f(); // John  (this == user)
admin.f(); // Admin  (this == admin)

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

객체 없이 호출하기: this == undefined

function sayHi() {
  alert(this);
}

sayHi(); // undefined

함수 본문에 this가 사용되었다면, 객체 컨텍스트 내에서 함수를 호출한다고 예상하면 된다.

(*) 여기 조금 헷갈림

23.5. this가 없는 화살표 함수

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

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

user.sayHi(); // 보라

arrow() 의 this는 외부 함수 user.sayHi()의 this가 된다.
별개의 this 가 만들어지는 건 원치 않고 외부 컨텍스트에 있는 this를 이용하고 싶은 경우 화살표 함수가 유용하다.

24. new 연산자와 생성자 함수

객체 리터럴{}을 사용하면 객체를 쉽게 만들 수 있다.
그러나, 유사한 객체를 여러 개 만들어야 할 때가 생기기도 한다.
(복수의 사용자, 메뉴 내 다양한 아이템을 객체로 표현하려고 할 때)

'new' 연산자와 생성자 함수를 사용해서 만들 수 있다.

24.1. 생성자 함수

생성자 함수 (constructor function)와 일반 함수에 기술적인 차이는 X

생성자 함수가 따르는 관례

  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를 반환.

new User(...)이 실행되면 무슨 일이 일어나는지 보자.

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

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

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

이제 let user = new User("보라") 아래 코드를 입력한 것과 동일하게 동작한다.

let user = {
  name: "보라",
  isAdmin: false
};

객체 리터럴 문법으로 일일이 객체를 만드는 방법보다 훨씬 간단하고 읽기 쉽게 객체를 만들 수 있어진다.

생성자의 의의: 재사용할 수 있는 객체 생성 코드를 구현한다.

  • 모든 함수는 생성자 함수가 될 수 있다.
    new를 붙여 실행한다면 어떤 함수라도 위에 언급된 알고리즘이 실행된다.
    이름의 첫 글자가 대문자인 함수는 new 를 붙여서 실행해야 한다.

24.2. new.target 과 생성자 함수

자주 쓰이지는 않는 문법.

new.target 프로퍼티를 사용하면 함수가 new와 함께 호출되었는지 아닌지를 알 수 있다.

일반적인 방법으로 함수를 호출했다면 new.target은 undefined를 반환.
밤면, new와 함께 호출한 경우엔 new.target은 함수 자체를 반환해준다.

function User() {
  alert(new.target);
}

// 'new' 없이 호출함
User(); // undefined

// 'new'를 붙여 호출함
new User(); // function User { ... }

또한 일반적인 방법으로 함수를 호출해도 new를 붙여 호출한 것과 같이 동작하도록 만들 수도 있다.

그런데 이 방법을 믿고 객체를 만드는 경우에도 new를 생략하면 코드가 정확히 무슨 일을 하는지 알기 어렵기 때문에 정말 필요한 경우에만 사용하자.

function User(name) {
  if (!new.target) { // new 없이 호출해도
    return new User(name); // new를 붙여줍니다.
  }

  this.name = name;
}

let bora = User("보라"); // 'new User'를 쓴 것처럼 바꿔줍니다.
alert(bora.name); // 보라

24.3. 생성자와 return문

생성자 함수에는 보통 return문이 없다.

반환해야 할 것들은 모두 this에 저장되고, this는 자동으로 반환되기 때문에 반환문을 명시적으로 써줄 필요가 없다.

그러나, return 문이 있다면?

  1. 객체를 return 한다면 this 대신 객체가 반환된다.
  2. 원시형을 return 한다면 return문이 무시된다.
function BigUser() {

  this.name = "원숭이";

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

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

첫 번째 규칙이 적용돼 return은 this를 무시하고 객체를 반환한다.

function SmallUser() {

  this.name = "원숭이";

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

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

두 번째 규칙이 적용돼서 this를 반환하게 된다.

인수가 없는 생성자 함수는 괄호를 생략해 호출하는 것 또한 가능하다.

24.4. 생성자 내 메서드

생성자 함수를 사용하면 매개변수를 이용해 객체 내부를 자유롭게 구성할 수 있다. (엄청난 유연성)

this에 프로퍼티를 더해주는 예시만 살펴봤는데, 메서드를 더해주는 것 또한 가능하다.

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

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

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

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

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

new User(name)는 프로퍼티 name 과 메서드 sayHi를 가진 객체를 만들어준다.

25. 옵셔널 체이닝 '?.'

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

예시를 들어보자.

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

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

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

또 다른 사례로는 브라우저에서 동작하는 코드를 개발할 때 문제가 생길 수 있다.
JS를 사용해 페이지에 존재하지 않는 요소에 접근해 요소의 정보를 가져오려 하면 문제가 발생한다.

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

명세서에 '?.'이 추가되기 전엔 이런 문제들을 해결하기 위해 && 연산자를 사용했다.

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

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

그러나 이렇게 되면 코드가 아주 길어지기 때문에 굳이 사용하지는 않는다.

25.2. 옵셔널 체이닝의 등장

'?.'은 '?.' 앞의 평가 대상이 undefined나 null이면 평가를 멈추고 undefined를 반환한다.

"설명이 장황해지지 않도록 지금부턴 평가후 결과가 null이나 undefined가 아닌 경우엔 값이 ‘있다’ 혹은 '존재한다’라고 표현하겠습니다."

let user = null;

alert( user?.address ); // undefined
alert( user?.address.street ); // undefined

user?.address로 주소를 읽으면 위와 같이 user 객체가 존재하지 않더라도 에러가 발생하지 않는다.
?.은 ?. 앞 평가 대상에만 동작되고, 확장은 되지 않는다.

user가 null이나 undefined 가 아니고 실제 값이 존재하는 경우에는 반드시 user.address 프로퍼티는 있어야 한다. 그렇지 않으면 user?.address.street의 두 번째 점 연산자에서 에러가 발생한다.

주의할 점

  • 옵셔널 체이닝 남용 X (존재하지 않아도 괜찮은 대상에만 사용해야 함)
  • ?. 앞의 변수는 꼭 선언되어 있어야 한다.

25.3. 단락 평가

단락 평가: ?.는 왼쪽 평가대상에 값이 없으면 즉시 평가를 멈추는 등의 평가 방법

함수 호출을 비롯한 ?. 오른쪽에 있는 부가 동작은 ?.의 평가를 멈췄을 때 더는 일어나지 않는다.

let user = null;
let x = 0;

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

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

25.4. ?.()와 ?.[]

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

함수 관련 예시와 함께 존재 여부가 확실치 않은 함수를 호출할 때 ?.()를 어떻게 쓸 수 있는지 알아보자.

0개의 댓글

관련 채용 정보