Objects

Introduction to Objects

다른 기능들도 있지만 기본적으로 객체는 문자열을 값으로 매핑합니다. JS 객체는 다른 객체(프로토타입)로부터 프로퍼티를 상속받고 이를 prototypal inheritance라 합니다.

신기하게도 프로퍼티 이름으로 빈 문자열도 가능합니다:

let a = { '': 1 };
console.log(a['']);

상속받지 않은 객체의 프로퍼티를 own property라 합니다.

각 프로퍼티는 아래 세 attribute들을 가집니다:

  • writable: 프로퍼티 값을 설정할 수 있는지
  • enumerable: for/in 루프로 순회할 수 있는지
  • configurable: 프로퍼티를 삭제하거나 attribute를 수정할 수 있는지

Creating Objects

중요한 문장이라고 생각해서 그대로 긁어왔어요:

Remember: almost all objects have a prototype, but only a relatively small number of objects have prototype property. It is these objects with prototype properties that define the prototypes for all the other objects.

Object.create의 인자는 생성된 객체의 프로토타입이 됩니다:

let obj1 = {};
console.log(String(obj1));

let obj2 = Object.create(null);
console.log(String(obj2));

Querying and Setting Properties

일반 객체가 연관 배열(딕셔너리)로 자주 활용되기는 하지만 Map 클래스가 더 나은 경우가 있습니다.

같은 이름의 프로퍼티가 프로토타입 체인의 다른 곳에 위치할 수 있습니다:

let a = { x: 1 };
let b = Object.create(a);
console.log(a, b);

b.x = 2;
console.log(a, b);

위 코드와 같은 특징 덕분에 상속받은 프로퍼티를 선택적으로 override할 수 있도록합니다. 다만 setter 메서드 관련해서 예외가 있습니다:

let parent = {
  set val(x) {
    this.y = 1;
  },
};

let child = Object.create(parent);

child.val = 2; // child에 프로퍼티가 추가되지 않고 parent의 setter가 호출됩니다.
console.log(parent, child); // setter에서 추가한 프로퍼티는 child에 추가됩니다.

Deleting Properties

delete 연산자는 객체 자신의 프로퍼티만 삭제합니다.

암묵적으로/명시적으로 선언된 변수들의 차이가 delete 연산자에 관련하여 있습니다:

// 원래는 var로 선언한 경우 삭제가 안되어야하는데
// 콘솔 컴포넌트를 eval로 구현해서 차이가 생기는 것 같아요 (확인 필요)
// 브라우저 콘솔에서 실행해보세요 :(
var a = 10;
console.log(globalThis.a);
delete globalThis.a;
console.log(globalThis.a);

b = 20;
console.log(globalThis.b);
delete globalThis.b;
console.log(globalThis.b);

Testing Properties

let a = Object.create({ x: 1 });
a.y = 2;

console.log('x' in a);
console.log(a.hasOwnProperty('x'));

// 스스로의 enumerable한 프로퍼티인지
console.log(a.propertyIsEnumerable('x'));
console.log(a.propertyIsEnumerable('y'));

프로퍼티 유무 확인에서 in 연산자와 !== undefined의 차이:

let a = { x: undefined };
console.log('x' in a, a.x !== undefined);
console.log('y' in a, a.y !== undefined);

Enumerating Properties

let a = { x: 1 };
let b = Object.create(a);
b.y = 2;
b[Symbol()] = 3;

// own&inherited enumerable string
for (i in b) console.log(i);
// own enumerable string
console.log(Object.keys(b));
// own (non)enumerable string
console.log(Object.getOwnPropertyNames(b));
// own (non)enumerable symbols
console.log(Object.getOwnPropertySymbols(b));
// own (non)enumeratble string&symbols
console.log(Reflect.ownKeys(b));

각종 메서드들의 프로퍼티 순회 순서입니다:

  1. 0이상의 자연수를 순서대로
  2. 문자열은 추가한 순서대로(리터럴에 있는 순서대로)
  3. 심볼도 추가한 순서대로

for-in의 순서는 표준에 자세히 명시되어있진 않다는데,,, 최근에 수정된 것 같습니다:

stackoverflow.comDoes ES6 introduce a well-defined order of enumeration for object properties?Does ES6 introduce a well-defined order of enumeration for object properties? var o = { '1': 1, 'a': 2, 'b': 3 } Object.keys(o); // ["1", "a", "b"] - is this ordering guaranteed by ES6? fo...

Extending Objects

객체의 복사가 자주 있어서 ES6에서 Object.assign()을 추가했습니다. 두번째 이후 인자들의 enumerable/own/string+symbol 프로퍼티들을 첫번째 인자 객체로 복사합니다.

Serializing Objects

console.log(JSON.stringify([NaN, Infinity, -Infinity]));
console.log(JSON.parse(JSON.stringify(new Date()))); // parse 후에도 문자열입니다.

stringify는 own enumerable property만 처리합니다.

Object Methods

Object.prototype에서 상속되는 메서드들:

let point = {
  x: 3,
  y: 4,

  toString() {
    return `(${this.x}, ${this.y})`;
  },

  toLocaleString() {
    return `(${this.x.toLocaleString()}, ${this.y.toLocaleString()})`;
  },

  valueOf() {
    return Math.hypot(this.x, this.y);
  },

  // Object.prototype에는 없지만 JSON.stringify가 봅니다
  toJSON() {
    return this.toString();
  },
};

console.log(String(point), Number(point), JSON.stringify(point));

Extended Object Literal Syntax

심볼은 opaque value입니다:

In computer science, an opaque data type is a data type whose concrete data structure is not defined in an interface.

en.wikipedia.orgOpaque data type - Wikipedia

Symbol은 객체가 아닌 원시값이고 Symbol()은 생성자 함수가 아닌 팩토리 함수입니다.

Symbol이 값을 완전히 숨길 수는 없는데 Object.getOwnPropertySymbols()로 심볼을 얻어내고 프로퍼티를 수정 혹은 삭제할 수 있기 때문입니다. 프로퍼티가 덮어씌워지는 사고 방지(?)의 목적으로 생각하면 될 것 같아요.

스프레드 연산자는 enumerable own 프로퍼티를 복사합니다. Mdn 문서에 스프레드 연산자와 Object.assign과의 차이에 대한 재밌는 예제 코드가 있어서 가져왔어요. 전자는 setter를 호출하지만 후자는 그렇지 않습니다.

const objectAssign = Object.assign(
  {
    set foo(val) {
      console.log(val);
    },
  },
  { foo: 1 },
);
// Logs "1"; objectAssign.foo is still the original setter

const spread = {
  set foo(val) {
    console.log(val);
  },
  ...{ foo: 1 },
};
// Nothing is logged; spread.foo is 1

developer.mozilla.orgSpread syntax (...) - JavaScript | MDNThe spread (...) syntax allows an iterable, such as an array or string, to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected. In an object literal, the spread syntax enumerates the properties of an object and adds the key-value pairs to the object being created.

아래 두 코드는 동일합니다. 후자가 더 간략해요:

let square = {
  area: function () {
    return this.side ** 2;
  },
  side: 10,
};

square = {
  area() {
    return this.side ** 2;
  },
  size: 10,
};

getter가 정의되는 위치와 enumerable함에 대한 참고 링크:

stackoverflow.comSetting an ES6 class getter to enumerableI have an ES6 class (transcompiled with babeljs) with a getter property. I understand that these properties are not enumerable by default. However, I do not understand why I am not able to make the