[객체 프로퍼티 설정]

0. 프로퍼티 플래그와 설명자

프로퍼티는 키-값의 쌍으로만 다뤘으나 훨씬 유연한 자료구조이다.

  • 프로퍼티 플래그 값 과 함께 가지는 flag 속성이 있다. 1) writable : true 이면 값을 수정 가능 2) enumerable : true 면 반복문으로 나열 가능 3) configurable : true 이면 프로퍼티 삭제 혹은 수정 가능

프로퍼티의 flag default 값은 모두 true, 언제든 수정이 가능하다.

  • let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); 위와 같이 해당 프로퍼티의 정보를 얻을 수 있음 obj 객체명 propertyName 프로퍼티 명

  • descriptor를 찍을 땐 JSON.stringify 로 찍어준다.
    property descriptor:
    {
      "value": "John",
      "writable": true,
      "enumerable": true,
      "configurable": true
    }
    

    위와 같이 출력된다.

  • Object.defineProperty(obj, propertyName, descriptor) 위를 사용하면 프로퍼티를 만들 수 있음 descriptor : 새롭게 만들 프로퍼티/플래그 정보 기존에 만드는 프로퍼티 생성법과 다르게 이 방법으로 만들면 플래그 정보가 모두 false로 만들어짐
      Object.defineProperty(user, "name", {
    writable: false
      });
    

    이렇게 만들면 writable 을 false로 만들 수 있다.

  • enumerable 플래그 열거가 불가능한 메서드가 있다. 대표적으로 toString() ex)
      let user = {
    name: "John",
    toString() {
      return this.name;
    }
      };
    

    열거가 불가능하다는 것은 for in 구문에 호출이 안된다는 것이다. 그런데 커스텀 toString을 추가하면 for in 구문에 toString이 나타난다.

  1. 플래그
    • configurable 플래그
      구성가능함을 나타내는 플래그
      configurable 플래그가 false로 설정되어 있으면 해당 프로퍼티는 객체에서 지울 수 없음 대표적으로 내장 객체 Math의 PI (파이를 나타냄) 쓰기 열거, 구성이 불가함. configurable 플래그를 false로 설정하면 다시 바꿀 수 없음 (defineProperty 를 사용해도 불가함)
    • configurable : false에 따른 제약사항 1) configurable 플래그를 수정할 수 없음 2) enumerable 플래그를 수정할 수 없음. 3) writable: false의 값을 true로 바꿀 수 없음(true를 false로 변경하는 것은 가능함). 4) 접근자 프로퍼티 get/set을 변경할 수 없음(새롭게 만드는 것은 가능함). => 영원히 변경할 수 없는 프로퍼티를 만들 때 사용한다.
  • defineProperties 를 사용하면 여러개의 프로퍼티의 플래그 여러개를 한번에 정의 가능
  • getOwnPropertyDescriptor(obj) 이렇게 사용하면 obj(객체)의 프로퍼티를 가져올 수 있다.
    • defineProperties({새로만들 객체}, getOwnPropertyDescriptor(obj)) 이렇게 사용하면 플래그까지 복사할 수 있는 것이다.
    • for in 을 사용하여 객체 복사 하면 프로퍼티의 플래그까지 복사되지는 않는 문제가 있음
  1. 프로퍼티 getter와 setter 프로퍼티에는 두종류가 있다. 1) 데이터 프로퍼티 : 프로퍼티에 값이 할당되는 경우, 지금까지 공부하면서 본 모든 프로퍼티 2) 접근자 프로퍼티 : 본질은 함수, 값을 획득(get) 설정(set) 하는 역할 . 그런데 외부에서는 함수가 아닌 프로퍼티처럼 보임

접근자 프로퍼티 코드 예시)

  let obj = {
    get propName() {
      // getter, obj.propName을 실행할 때 실행되는 코드
    },
  
    set propName(value) {
      // setter, obj.propName = value를 실행할 때 실행되는 코드
    }
  };

getter 메서드 : obj.propName을 실행할 때 실행 setter 메서드 : obj.propName = value 처럼 값을 할당할 때 실행됨

get 은 그냥 쓰면 되고 set에 대한 코드만 보면..

  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }

위와 같이 쓴다.

  obj.fullName = "Alice Cooper" 라고 코딩했다면

name 에 Alice surname 에 Cooper 가 입력된다.

  • 접근자 프로퍼티의 설명자는 writable, value는 없음
    get, set, enumerable, configurable 이렇게 되어있음 그러므로 프로퍼티 설정에서 get과 value를 같이 쓸수 없다.

[프로토타입과 프로토타입 상속]

  • 프로토 타입 상속 객체지향 언어에서 나오는 상속의 개념과 유사한 듯 하다. user라는 객체가 있는데 이 객체의 속성을 그대로 가져가면서 속성이 추가되는 admin이나 guest가 있을 경우 프로토타입 상속이란 것을 해야한단다.

  • [[prototype]] 객체는 [[prototype]]라는 숨김프로퍼티를 갖는다 (옛날 함수에서 렉시컬 환경을 갖는것과 유사한 개념이다.) [[prototype]] 은 다른 객체에 대한 참조를 프로퍼티 값으로 가진다. 그 참조 대상을 “프로토 타입” 이라고 부른다.
  • 프로토타입의 동작 : 객체의 프로퍼티에서 값을 찾다가 못찾으면 프로토타입에서 찾게됨

  • 프로토타입은 숨김 프로퍼티이지만 값을 설정할 수 있다. “__proto__” 사용 코드 예시
      let animal = {
    eats: true
      };
      let rabbit = {
    jumps: true
      };
      rabbit.__proto__ = animal;
      // rabbit 의 프로토타입을 animal로 설정
    
  • 프로토타입 체인 animal <= rabbit <= lonEar 이런 식으로 상속할 수 있다. 프로토타입 체이닝에 제약조건 1) 순환 참조(circular reference)는 허용되지 않음. __proto__를 이용해 “닫힌 형태”로 다른 객체 참조시 에러 발생 2) __proto__의 값은 객체나 null만 가능, 다른 자료형은 무시
  • 객체엔 오직 하나의 [[Portotype]]만 있을 수 있다. 객체는 두 개의 객체를 상속받지 못함.

  • write 시 프로토타입 anmial(prototype) : eats 가 있고 => rabbit(상속) : eats를 만들어 정의한 경우 rabbit의 eats가 우선 호출이다.

  • 프로토타입과 ‘this’ ```javascript // animal엔 다양한 메서드가 있습니다. let animal = { walk() { if (!this.isSleeping) { alert(동물이 걸어갑니다.); } }, sleep() { this.isSleeping = true; } };

let rabbit = { name: “하얀 토끼”, proto: animal };

// rabbit의 프로퍼티 isSleeping을 true로 변경합니다. rabbit.sleep();

alert(rabbit.isSleeping); // true alert(animal.isSleeping); // undefined (프로토타입에는 isSleeping이라는 프로퍼티가 없습니다.)

  위의 코드에서 
  animal => rabbit 의 상속 체인에서
  rabbit 에서 sleep 호출 시 this.isSleeping 은 rabbti에서 만들어진다고 봐야한다.
  메서드의 경우는 호출하여 사용하는 느낌이고 그 안의 this.value는 해당 객체 안에서 만들어지는 개념이다.

  - 상속과 for in
  Object.keys(obj)로 객체의 키값을 검색하면 해당 객체의 키 값만 검색되지만 
  for in 구문으로 객체를 돌 때는 상속해주는 프로토타입의 키까지 모두 순회한다.
  obj.hasOwnProperty(key)를 사용하면 상속된 프로퍼티 말고 해당 객체에서만 만들어진 프로퍼티일 때만 true로 반환한다.
  
3. 함수의 prototype 프로퍼티
  위의 내용을 하기 전에...
  먼저 "객체:기본" 에서 배웠던 new 연산자를 한번 더 집고 넘어가야할 필요성을 느꼈다.
  3-1. new 연산자
  - 생성자 함수
  객체를 만들 때 보통은 {...} 이런식으로 "리터럴 방식"을 사용하는 것이 일반적이다.
  그런데 개발하다보면, 유사한 객체를 여러개 만들어야 할 때가 생긴다.
  그 때 사용하는 것이 new 연산자, 생성자 함수이다.
  
  * 생성자 함수의 관례
    1) 함수 이름의 첫 글자는 대문자로 시작
    2) 반드시 "new" 연산자를 붙여 실행
    예시
  ```javascript
    let user = new User("Jack");

    function User(name){
      this.name = name;
    }
위의 함수에서 new 연산자를 쓰면 this라는 애가 위에 생기는 것과 마찬가지란다.
    function User(name){
      // this = {};
      this.name = name;
      // return this;
    }
위의 주석과 같이 동작하는 것이다.

3-2. 함수의 프로토타입 프로퍼티

  //아래_코드_참조
    let animal = {
      eats: true
    };

    function Rabbit(name) {
      this.name = name;
    }

    Rabbit.prototype = animal;

    let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal

    alert( rabbit.eats ); // true

  Rabbit 함수  new 연산자를 써서 rabbit이라는 객체가 생성된다.
  let rabbit{
    name : "White Rabbit";
    prototype : "animal";
  }

위와 같이 생성되는 것이다.

3-3. 함수의 프로토타입 프로퍼티와 constructor 프로퍼티 함수의 프로토타입 프로퍼티의 기본 값은 constructor이다. 아래와 같다.

  function Rabbit() {}
  // 기본 prototype:
  // Rabbit.prototype = { constructor: Rabbit }

  alert( Rabbit.prototype.constructor == Rabbit ); // true

만약…

  let rabbit = new Rabbit();

이렇게 객체를 하나 만들었다면… rabbit의 프로토타입 프로퍼티로 constructor를 사용할 수 있다. 그러므로..

  rabbit.constructor == Rabbit 

이 되는 것이다.

그러면 이런 사용도 가능하다.

  let rabbit2 = rabbit.constructor();

이렇게 기존의 객체를 가지고 다른 객체를 하나 만드는 것도 가능하다.

그러나 constructor는 그냥 프로퍼티일 뿐이다. 만약 함수의 프로토타입 프로퍼티를 딴 값으로 설정했다면 constructor를 못쓰는 것이다. 프로토타입 프로퍼티 안에 프로퍼티를 넣어서 만들수는 있다.

  1. 네이티브 프로토타입 내장 생성자 함수에서 프로토타입 프로퍼티를 사용한다.
  •   Object.prototype
      let obj = {};
      alert( obj ); // "[object Object]" ?
    

    위의 코드에서 “[object Object]” 를 찍어주는 코드는 도데체 어디에 있는가.

  let obj = {}; //의 의미는 
  obj = new Object(); //와 같다.

Object란 객체를 만들어주는 함수인 것이다. 근데 이 Object의 프로토타입이 있는데 거대한 객체이다.

그래서 obj.toString() 을 호출할 때 Object.prototype 에서 가져온다.

  • 네이티브 프로토타입 조작하기 ```javascript String.prototype.show = function() { alert(this); };

“BOOM!”.show(); // BOOM!

  String 타입의 프로토타입으로 접근 한 후 메서드를 추가하면 맘대로 쓸 수도 있다.
  하지만 프로토타입은 전역으로 영향을 끼치기에 그렇게 좋은 방법은 아니다.

  폴리필을 만들 때 쓰인다.

  폴리필 : 자바스크립트 명세서에 있는 메서드와 동일한 기능을 하는 메서드 구현체. 
  명세서에는 정의되어 있으나 특정 자바스크립트 엔진에서는 해당 기능이 구현되어있지 않을 때 사용

  - 프로토타입에서 빌려오기
  ```javascript
  let obj = {
    0: "Hello",
    1: "world!",
    length: 2,
  };
  
  obj.join = Array.prototype.join;
  
  alert( obj.join(',') ); // Hello,world!

위의 경우 obj라는 객체에서 Array.prototype 안의 join 기능을 사용하기 위해 빌려온 것이다.

  1. 모던한 프로토타입 사용법
    • Object.create(obj) obj를 프로토 타입으로 설정
    • Object.getPrototypeOf(obj) obj의 프로토타입을 가져옴
    • Object.setPrototypeOf(obj, {}) 프로토 타입을 변경함.