ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 객체
    javascript 2023. 4. 22. 05:28

    객체

    객체란 ‘키(key): 값(value)’ 쌍으로 구성된 프로퍼티(property) 를 여러 개 넣을 수 있는 것을 말한다. 함수형 프로그래밍 언어로 시작했지만, 객제 지향의 특징을 가지고 있는 언어인 자바스크립트에서 객체는 정말 빼놓을 수 없는 개념이다. 객치지향 프로그래밍을 공부하다 보면 객체는 이 세상 모든 것을 의미할 수 있다고 하는데, 코드로 생각하면 파이썬의 딕셔너리 형태 정도로 생각해볼 수 있을 것 같다. 

     

    모던 자바스크립트 튜토리얼에서는 객체를 서랍장으로 비유한다. 이름을 붙인 파일에 자료를 넣고 서랍장에 저장을 하는 것이다.

    빈 서랍장, 즉 빈 객체는 아래와 같이 만든다. 

    let user = new Object(); // '객체 생성자' 문법
    let user = {};  // '객체 리터럴' 문법

     

    리터럴과 프로퍼티

    let user = {     // 객체
      name: "John",  // 키: "name",  값: "John"
      age: 30        // 키: "age", 값: 30
    };

    이렇게 user라는 객체에서 name이라는 키에 "John"이라는 자료형이 들어가졌다. 엔 문자형, 엔 모든 자료형이 허용된다. 여기서 name: "John", age: 30 은 각각 프로퍼티라고 한다. 프로퍼티는 일종의 '값'이라고 생각하면 된다. 그래서 name: "John"은 첫 번째 프로퍼티, age: 30는 두 번째 프로퍼티이고 여기서 키는 프로퍼티 키, 혹은 프로퍼티 이름이라고도 불린다. 

     

    프로퍼티 값을 읽어올 때에는 다음과 같이 읽어올 수 있다. 

    alert( user.name ); // John
    alert( user.age ); // 30

    이렇게 객체에서 키로 접근하면 값을 얻을 수 있다.

     

    user.isAdmin = true; // 프로퍼티 추가
    delete user.age;     // 프로퍼티 삭제

    위와 같이 프로퍼티를 추가하거나 삭제할 수도 있다.

     

    let user = {
      name: "John",
      age: 30,
      "likes birds": true  // 복수의 단어는 따옴표로 묶어야 합니다.
    };
    
    // set
    user["likes birds"] = true;
    
    // get
    alert(user["likes birds"]); // true
    
    // delete
    delete user["likes birds"];

    하지만 만일 name이나 age처럼 단일 단어가 아니라 두 개 이상의 단어를 조합해서 lieks birds라는 키를 만들고 싶다면, 따옴표로 묶어주어야한다. 또한 값을 읽어오거나 삭제할 때에도 그냥 바로 likes birds라고 작성하면 컴퓨터가 인식하지 못하기 때문에, 위와 같이 대괄호 안에 넣어주어야한다. 

     

    let user = {
      name: "John",
      age: 30
    };
    
    let key = prompt("사용자의 어떤 정보를 얻고 싶으신가요?", "name");
    
    // 변수로 접근
    alert( user[key] ); // John (프롬프트 창에 "name"을 입력한 경우)

    마찬가지로 promt처럼 어떤 값을 얻어오는 경우에도 대괄호 표기법을 쓴다. 점 표기법을 쓰면 undefined가 나오게 된다. key에 어떤 단어가 올 지 모르기 때문에 대괄호 표기법으로 표기하는 것이 아닐까 싶다. 

     

    단축 프로퍼티

    function makeUser(name, age) {
      return {
        name: name,
        age: age,
        // ...등등
      };
    }
    
    let user = makeUser("John", 30);
    alert(user.name); // John

    이렇게 객체 자체를 생성하지 않고, return값으로 만들어둔 객체에 값을 set해주는 방법도 있다. 마치 c언어의 구조체같다. 위에서 name:name, 의 경우는 name, 으로만 작성해도 된다. 

    let user = { name: "John", age: 30 };
    
    alert( "age" in user ); // user.age가 존재하므로 true가 출력됩니다.
    alert( "blabla" in user ); // user.blabla는 존재하지 않기 때문에 false가 출력됩니다.

    이렇게 객체 안에 해당하는 키가 있는지를 in 연산자로 검사할 수도 있다.

     

    let obj = {
      test: undefined
    };
    
    alert( obj.test ); // 값이 `undefined`이므로, 얼럿 창엔 undefined가 출력됩니다. 그런데 프로퍼티 test는 존재합니다.
    
    alert( "test" in obj ); // `in`을 사용하면 프로퍼티 유무를 제대로 확인할 수 있습니다(true가 출력됨).

     만일 값이 undefined인지 실제로 값이 없는 것인지를 알기 위해서는 위와 같이 검사해볼 수 있다!

     

    let user = {
      name: "John",
      age: 30,
      isAdmin: true
    };
    
    for (let key in user) {
      // 키
      alert( key );  // name, age, isAdmin
      // 키에 해당하는 값
      alert( user[key] ); // John, 30, true
    }

    파이썬처럼 in 연산자를 이용해서 for문을 돌려볼 수도 있다. 이 때, key값을 부르면 key만 나오고 user[key]로 부르면 값이 나온다. 

     

    참조에 의한 객체 복사

    let user = {
      name: "John"
    };

    위와 같이 객체를 저장하는데, 객체는 사실 메모리 내 어딘가에 저장이 되는 것이고, user는 객체를 참조할 수 있는 값이 저장되는 것이다. 즉, 만일 객체 user를 복사하려고 하면, 객체 자체 값이 아니라 객체의 참조 값이 복사되게 된다.

    let user = { name: 'John' };
    
    let admin = user;
    
    admin.name = 'Pete'; // 'admin' 참조 값에 의해 변경됨
    
    alert(user.name); // 'Pete'가 출력됨. 'user' 참조 값을 이용해 변경사항을 확인함

    즉, 만일 user를 복사해서 admin을 만들어도, user객체 admin객체가 따로 있는 것이 아니라, 두 객체 모두 같은 곳을 참조하고 있기 때문에, 한 군데에서 데이터를 변경한다고 했을 때, 다른 곳에서도 변경된 값을 확인할 수 있다. 그렇다면, 만일 객체를 완전 복사하고 싶다면 어떻게 해야할까?

    let user = {
      name: "John",
      age: 30
    };
    
    let clone = {}; // 새로운 빈 객체
    
    // 빈 객체에 user 프로퍼티 전부를 복사해 넣습니다.
    for (let key in user) {
      clone[key] = user[key];
    }
    
    // 이제 clone은 완전히 독립적인 복제본이 되었습니다.
    clone.name = "Pete"; // clone의 데이터를 변경합니다.
    
    alert( user.name ); // 기존 객체에는 여전히 John이 있습니다.

    위와 같이 키값에 해당하는 값까지 하나씩 복사하면 완전히 복사할 수 있다. 아래와 같이 Object.assign을 이용해서 간단하게 복사하는 방법도 있다. 

    let user = {
      name: "John",
      age: 30
    };
    
    let clone = Object.assign({}, user);

    그런데 과연 위의 두 방법이 깊은 복사일까? 그렇지 않다. 

    let user = {
      name: "John",
      sizes: {
        height: 182,
        width: 50
      }
    };

    만일 위와 같은 객체가 있다면 name은 잘 복사가 되지만, sizes 안의 프로퍼티들은 값을 복사하지 못하고 참조값을 복사하게 된다. 따라서, user객체를 완벽하게 복사하고 싶다면, sizes프로퍼티도 반복문을 돌려가면서 복사해주어야한다. 

     

    메서드와 this

    객체가 어떤 행동을 할 수 있는 주체라면, 메서드는 그 행동을 의미하는 것이다. 

    let user = {
      name: "John",
      age: 30
    };
    
    // 첫번째 방법
    user.sayHi = function() {
      alert("안녕하세요!");
    };
    
    user.sayHi(); // 안녕하세요!
    
    // 두번째 방법 
    // 함수 선언
    function sayHi() {
      alert("안녕하세요!");
    };
    
    // 선언된 함수를 메서드로 등록
    user.sayHi = sayHi;
    
    user.sayHi(); // 안녕하세요!

    안녕하세요!라고 인사하는 행동인 sayHi 메소드로 만들어주는 과정이다. 첫번째 방법처럼 바로 메소드를 선언해줄 수도 있고, 두번째 방법처럼 함수를 선언한 뒤 등록하는 방법이 있다. 

     

    this

    this는 현재 객체를 의미한다. 

    let user = {
      name: "John",
      age: 30,
    
      sayHi() {
        // 'this'는 '현재 객체'를 나타냅니다.
        alert(this.name);
      }
    
    };
    
    user.sayHi(); // John
    위와 같이 this는 user를 가리킨다. 즉 여기서의 this는 user이다. 만일 alert(person.name)이라고 다른 객체를 가리킨다면 여기서 person은 this가 되고, person 객체를 가리킬 것이다. 
     
     

    생성자 함수

    생성자 함수는 아래의 관례를 꼭 따라야한다. 

    1. 함수 이름의 첫 글자는 대문자로 시작합니다.
    2. 반드시 'new' 연산자를 붙여 실행합니다.

    만일 new 연산자를 붙이지 않는다면, 객체 값은 undefined가 된다. 

    User라는 생성자함수를 만들면 다음과 같다. 여기서 this={}, return this는 직접 쓰지 않아도 자동적으로 만들어지고 반환된다고 생각하면 된다. 

    function User(name) {
      // this = {};  (빈 객체가 암시적으로 만들어짐)
    
      // 새로운 프로퍼티를 this에 추가함
      this.name = name;
      this.isAdmin = false;
    
      // return this;  (this가 암시적으로 반환됨)
    }

    만일 위의 코드에 더해서 let user = new User("보라"); 이렇게 생성자를 호출한다면 이는

    let user = {
      name: "보라",
      isAdmin: false
    };
     
    와 같은 내용의 코드가 된다. 
     

    생성자와 return문

    function BigUser() {
    
      this.name = "원숭이";
    
      return { name: "고릴라" };  // <-- this가 아닌 새로운 객체를 반환함
    }
    
    alert( new BigUser().name );  // 고릴라
     
     생성자 함수를 만들면 return this가 보이지 않아도 자동으로 생성된다고 위에서 언급했다. 하지만 위와 같이 객체를 return한다면 this가 아닌 객체가 반환된다. 
    function SmallUser() {
    
      this.name = "원숭이";
    
      return; // <-- this를 반환함
    }
    
    alert( new SmallUser().name );  // 원숭이

    만일 위와 같이 쓴다면, return문이 무시되고, 보이지 않지만 자동으로 생성된 return this가 적용된다.

     

    옵셔널 체이닝 '?.'

    옵셔널 체이닝은 정보가 가지고 있지 않은 경우에 접근했을 때를 대비하여 만들어진 최신 기술이다. 사용자 정보가 있다고 했을 때 누구는 주소 정보가 있고 누구는 주소 정보가 없을 수 있다. 만일, 주소 정보가 없는데 주소 정보에 접근했다면 주소값이 없다는 타입 에러를 뱉어낼 것이다. 즉, 호출 결과가 undefined 인지 아닌지 판명할 수 있는 것이 필요하다. 그것이 바로 옵셔널 체이닝, ? 이다. 

    let user = null;
    
    alert( user?.address ); // undefined
    alert( user?.address?.street ); // undefined

    위처럼 address에 접근할 때에 ?를 이용해서 접근할 수 있다. 이렇게 옵셔널 체이닝을 해주면 더이상 타입에러가 나지 않는다. 

     

     

    📚 참고자료

    모던 자바스크립트 튜토리얼

    'javascript' 카테고리의 다른 글

    함수 심화와 this  (0) 2023.06.24
    5월 데보션 테크 세미나 클라우드 비용 최적화 후기  (1) 2023.05.28
    자바스크립트 함수 심화  (1) 2023.05.13
    자료구조와 자료형  (0) 2023.05.07
    자바스크립트 기본  (0) 2023.04.09

    댓글

Designed by Tistory.