JavaScriptのObjectについて

# 初めに
Objectの中身が一体なに？と思い、いろいろ調べてまとめてみました。

今回の参考文章：
[How to create objects in JavaScript - freeCodeCamp](https://www.freecodecamp.org/news/a-complete-guide-to-creating-objects-in-javascript-b0e2450655e8/)
[Diving Deeper in JavaScripts Objects](https://blog.bitsrc.io/diving-deeper-in-javascripts-objects-318b1e13dc12)
[Property getters and setters - javaScript.info](https://javascript.info/property-accessors)
[Property attributes: an introduction - Deep JavaScript](https://exploringjs.com/deep-js/ch_property-attributes-intro.html)

オブジェクトを創る方法
↓
値へのアクセスの仕方
↓
オブジェクのプロパティについて
↓
オブジェクトを守るメソッドを紹介
...という流れで行きたいと思います。

# How to create an Object
## object literal `{}`
一番簡単なのは[Object literals](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Grammar_and_types#object_literals)`{}`
```jsx
const person = {
  name: 'Taro',
}

person.age = 20
console.log(person.name, person.age) // Taro 20
```
`{}`に直接書き込みか、外でkeyを指定して値を付与するのもできます。

## `new`
`new`キーワードで`Object()`か、コンストラクタ関数からオブジェクトを創り出す。

### new Object()
`Object()` [Object() constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/Object)
```jsx
const nation = {
  ja: 'Japan',
  us: 'America'
}

const person = new Object(nation)
person.name = 'Taro'
person.age = 20

console.log(person)
// { ja: 'Japan', us: 'America', name: 'Taro', age: 20 }
// note: new Object() => empty object
```

### new constructor function
```jsx
function Person(name, age) {
  this.name = name
  this.age = age
}

const Taro = new Person('Taro', 20)
const Jiro = new Person('Jiro', 18)

console.log(Taro) // Person { name: 'Taro', age: 20 }
console.log(Jiro) // Person { name: 'Jiro', age: 18 }
// note: instance object
```
`new Object()`には別のオブジェクトを引数として、プロトタイプチェーンを使わない単に新しいオブジェクトを創るとき使える。

`new constructor function`は、`this`で新しいインスタンスオブジェクトを創り、コンストラクタ関数のプロトタイプチェーンによって拡張するのに使う。

もちろん`new Object()`がプロトタイプチェーンを使えないわけでもありませんが、
```jsx
console.log(person instanceof Object) // true
console.log(Object.getPrototypeOf(person) === Object.prototype) // true

const obj = {}
console.log(Object.getPrototypeOf(person) === Object.getPrototypeOf(obj)) // true
```
`Object()`の`.__proto__`（プロトタイプチェーンの参照先）はすべてのオブジェクト共通しているので、`.prototype`への変更がすべてのオブジェクトに適用するということになってしまいます。

`.__proto__`、`.prototype`、インスタンスについて詳しくは前の文章にご参考になればと。
[JavaScriptの.__proto__とprototypeとinstance](https://qiita.com/hu-yu/items/e1b7d59d9f406e73977b#tldr)
[JavaScriptのPrototypeについて](https://qiita.com/hu-yu/items/d6d9f3ca0f1dd98f2bcc)

## Obejct.create()
```jsx
const Person = Object.create({})
console.log(Person)
```
`Obejct.create()`は`new`と似ていますが、`Obejct.create()`のほうがもっとシンプルに扱いやすく、そして与えられた引数を`prototype`にすることができます！
```jsx
const Person = {
  firstName: 'Default',
  lastName: 'Default',
  greeting: function () {
    return `Hello, ${this.firstName} ${this.lastName}!`
  }
}

const Taro = Object.create(Person)

console.log(Taro) // {}

Taro.firstName = 'Taro'
Taro.lastName = 'Yamada'
console.log(Taro.greeting()) // Hello, Taro Yamada!

console.log(Taro) // { firstName: 'Taro', lastName: 'Yamada' }
console.log(Object.getPrototypeOf(Taro))
// {
//   firstName: 'Default',
//   lastName: 'Default',
//   greeting: [Function: greeting]
// }
```
`const Taro = Object.create(Person)`では`Taro`は空のオブジェクト、`Person`オブジェクト自体が`Taro`のプロトタイプ（原型）になります。つまり何か共通して、継承してほしいプロパティやメソッドを入れようとするなら`Person`を変更すれば済みます！

また、その性質で`new Object()`のように毎回インスタンスを創ると本体のプロパティを複製しなくてもいいというメモリーにやさしいメリットもあります。必要なだけ上書き（overwrite）していいのです。

そして`Obejct.create()`の内部構造を書いてみると、
```jsx
function createObject(obj) {
  function F() { }
  F.prototype = obj
  return new F()
}

const Taro = createObject(Person)

console.log(Taro) // {}

Taro.firstName = 'Taro'
Taro.lastName = 'Yamada'
console.log(Taro.greeting()) // Hello, Taro Yamada!

console.log(Taro) // { firstName: 'Taro', lastName: 'Yamada' }
console.log(Object.getPrototypeOf(Taro))
// {
//   firstName: 'Default',
//   lastName: 'Default',
//   greeting: [Function: greeting]
// }
console.log(Object.getPrototypeOf(Taro) === Person) // true
```
[JavaScriptのinheritanceについて part1](https://qiita.com/hu-yu/items/13635160a4616d6d1c0d#by-function-)の**by function() {}**と同じロジックで創られたと気づきました。

簡単に言うと、`Object.prototype`と`Person`オブジェクトの間に`function F() { }`という媒介を入れました。

`function F() { }`はJavaScriptすべてのプロパティとメソッドを継承した`Function`のインスタンスとして、元の`prototype`であった`Function.prototype`から`Person`へ置き換えて、`Person`という`prototype`を持つインスタンスを創り出す。
（`Person`オブジェクトも`Function`からのインスタンスであったため`Function.prototype`を持っているから`Person`へ置き換えてもプロトタイプチェーンに問題ありませんでした。）

そして`Person`オブジェクトの中身を変えても影響を受けるのは、`Person`という`prototype`を持つ`new F()`なので、`Object.prototype`には影響しません。

## Object.assign() *excluding nested object
[Object.assign()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
構文：`Object.assign(target, ...sources)`
ネスト無しのオブジェクトなら、`Object.assign()`で**shallow copy**ができ、また複数のオブジェクトを整合することができます。プロパティキー（property key）が同じであった場合、後から上書きされる。変更されたオブジェクトは一番目の引数（target）に返します。
```jsx
let obj1 = {
  name: 'Nick',
  age: 20,
  obj: {
    id: 1,
  },
};

let obj2 = Object.assign({}, obj1);
console.log(obj1.name === obj2.name); // true
console.log(obj1.obj === obj2.obj); // true

obj2.name = 'Nick';
console.log(obj1.name === obj2.name); // true // compare by primitive value

obj2.name = 'Lucy';
obj2.obj = {
  id: 1,
};
console.log(obj1.name === obj2.name); // false
console.log(obj1.obj === obj2.obj); // false // compare by reference sharing
```


## ES6 class
```jsx
class Person {
  constructor(fname, lname) {
    this.firstName = fname
    this.lastName = lname
  }
}

const person = new Person('Taro', 'yamada')

console.log(person) // Person { firstName: 'Taro', lastName: 'yamada' }
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
```
ES6クラスで創ったインスタンスは、コンストラクタ関数と`new`でのインスタンスと同じ`prototype`を指しています。
```jsx
function Person(fname, lname) {
  this.firstName = fname
  this.lastName = lname
}

const person = new Person('Taro', 'yamada')

console.log(person) // Person { firstName: 'Taro', lastName: 'yamada' }
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
```
ES6クラスは`new`とほぼ同じ機能しているように見えます、そしてMDNでは
>class キーワードは ES6 で導入されましたが、シンタックスシュガーであり、JavaScript は引き続きプロトタイプベースです
[継承とプロトタイプチェーン](https://developer.mozilla.org/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)

`new`も`class`も**prototype-based**継承方法ですが、実のところ動きが少し違います。その違いは後ほど別の文章でまとめていきたいと思います。

# The structure of objects
JavaScriptのObjectは**key-value pairs**（キーバリューペア）、関係データベースのような仕組みで構成されています。
`Data Properties`：`Object name`と`Property name`などオブジェクトデータのプロパティ。
`Property Accessor`：プロパティへのアクセス方法で、ブラケット表記法とドット表記法があります。
`Property Descriptors`：指定オブジェクトのプロパティ構成の記述子のことです。
`Property Attributes`：`Property name(key)`に隠された`Attribute`（特性、特徴）のことです。

## Data Properties
`Data Properties`：`Object name`と`Property name`などオブジェクトデータのプロパティ。
```jsx
const person = {
  id: 1,
  name: 'Taro'
}
```
Object name`person`、Property name`id`、`name`などがオブジェクトのData　Propertiesです。

## Property Accessor
[Property accessors](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Property_Accessors)
`Property Accessor`：プロパティへのアクセス方法で、ブラケット表記法とドット表記法があります。
### bracket notation `[]`(Computed Member Access)
```jsx
const person = {}
person['firstName'] = 'Taro'
person['lastNanme'] = 'Yamada'
console.log(person.firstName, person.lastNanme)
```
### dot notation `.`(Member Access)
```jsx
person.nation = 'Japan'
person.birthPlace = 'Tokyo'
console.log(person)
// {
//   firstName: 'Taro',
//   lastName: 'Yamada',
//   nation: 'Japan',
//   birthPlace: 'Tokyo'
// }
```

## Property Descriptors & Property attributes
### Property Descriptor methods
`Property Descriptors`：指定オブジェクトのプロパティ構成の記述子のことです。
`Object.getOwnPropertyDescriptor()`は単一のpropertyのattributeを返します。
```jsx
console.log(Object.getOwnPropertyDescriptor(person, 'firstName'))
// { value: 'Taro', writable: true, enumerable: true, configurable: true }
```
`Object.getOwnPropertyDescriptors()`はオブジェクト全体propertyのattributeを返します。アクセサーで単一記述子のvalueもアクセスできます。
```jsx
const descriptors = Object.getOwnPropertyDescriptors(person)
console.log(descriptors.nation.value) // Japan
console.log(descriptors.birthPlace.value) // Tokyo
console.log(descriptors.birthPlace.writable) // true
```
###Property Attributes
`Property Attributes`：`Property name(key)`に隠された`Attribute`（特性、特徴）のことです。
`Object.getOwnPropertyDescriptor()`メソッドから、オブジェクトのプロパティには、
  種類  |  アトリビュート名とタイプ  |  初期値  |
:-----------|:-------|:-------|
Data property | value: any | undefined|
... | writable: boolean | false|
Accessor property | get: (this: any) => any | undefined|
... | set: (this: any, v: any) => void | undefined|
All properties | configurabel: boolean | false|
... | enumerable: boolean | false|

**(`writable`、`enumerable`、`configurable`初期値は`false`ですが、プロパティが生成される時点にすべて`true`になる)**


**[[value]]：key-value pairsのvalue。
[[writable]]：valueが変更できるかどうか。
[[enumerable]]：列挙可能であるかどうか。（反復処理`for...in`など）
[[configurable]]：設定の変更が可能であるかどうか**
があります。

そして
**[[get]] (Accessor property)
[[set]] (Accessor property)**
もあります。
これらは上の四つと違って、関数としてプロパティを設置したりアクセスしたりメソッドです。（getter/setterとも呼ばれるそうです。）

### writable: false
**value**への`Object.defineProperty()`やreassignでの変更が不可能になります。
```jsx
const person = {}

Object.defineProperty(person, 'firstName', {
  value: 'Taro',
  writable: false
})

console.log(person.firstName) // Taro

person.firstName = 'Jiro'
console.log(person.firstName) // Taro
```

### enumerable: false
反復処理`for...in`か`Object.keys()`で見つけ出すことができません。
```jsx
Object.defineProperties(person, {
  firstName: {
    value: 'Taro',
    enumerable: false,
  },
  lastName: {
    value: 'Yamada',
    enumerable: true
  }
})

for (const key in person) {
  console.log(key, person[key])
}
// lastName Yamada

const keys = Object.keys(person)
console.log(keys) // [ 'lastName' ]
```

### configurable: false
```jsx
Object.defineProperties(person, {
  firstName: {
    value: 'Taro',
    writable: true,
    enumerable: true,
    configurable: false
  },
  lastName: {
    value: 'Yamada',
    writable: true,
    enumerable: true,
    configurable: true
  }
})

console.log(person) // { firstName: 'Taro', lastName: 'Yamada' }
```
（`writable`、`enumerable`いずれも初期値が`false`なので、デモのために変更しておきました。）

- `delete`演算子は使用できません。
```jsx
console.log(delete person.firstName) // false
// note: in strict mode, it will throw TypeError

// console.log(delete person.lastName) // true
```

- `data property`から`accessor property`(`get`/`set`)へプロパティの設置や変更ができません。逆もまた然りです。（**getter/setter**の例をご参照ください。）
```jsx
person.firstName = 'Jiro'
console.log(person.firstName) // Jiro

Object.defineProperty(person, 'firstName', {
  value: 'Jiro'
})
console.log(person.firstName) // Jiro
```

- `Object.defineProperty()`で一度`configurable: false`を設置すると、再び`enumerable`、`configurable`、`get`、`set`の値を変更できません。
`writable: true`なら`writable: false`へ変更できますが、`writable: false`では`true`へ変更できません。
`writable: true`であれば`value`の変更は可能です。
```jsx
Object.defineProperty(person, 'firstName', {
  value: 'Jiro',
  writable: false,
})

console.log(person.firstName) // Jiro
console.log(Object.getOwnPropertyDescriptor(person, 'firstName'))
// {
//   value: 'Jiro',
//   writable: false,
//   enumerable: true,
//   configurable: false
// }

Object.defineProperty(person, 'firstName', {
  writable: true,
})
// TypeError: Cannot redefine property: firstName
```


### `get` & `set`
```jsx
const person = {
  get name() {
    return this._name.toUpperCase()
  },
  set name(value) {
    this._name = value
  },
  get id() {
    return this._id.toString(2)
  },
  set id(value) {
    this._id = value
  }
}

person.name = 'Taro'
console.log(person.name) // TARO

person.id = 5
console.log(person.id) // 101

console.log(person)
// { name: [Getter/Setter], id: [Getter/Setter], _name: 'Taro', _id: 5 }
console.log(person._name) // Taro
console.log(person._id) // 5
```
上のように外部からオブジェクトへのプロパティ設置によって、`get`と`set`で値の取り出しや保存を処理している。
たとえば`person`オブジェクトで設置した`get`と`set`は、`person.name`から`set`を経由して値を`person._name`へ保存し、そして再び`person.name`をアクセスすると`get`は`person._name`から値を取り出し、または指定の処理をしてから値を返します。

一般のプロパティと、`get`と`set`による処理されたプロパティとの`descriptor`は、
```jsx
console.log(Object.getOwnPropertyDescriptors(person))
// {
//   name: {
//     get: [Function: get name],
//     set: [Function: set name],
//     enumerable: true,
//     configurable: true
//   },
//   id: {
//     get: [Function: get id],
//     set: [Function: set id],
//     enumerable: true,
//     configurable: true
//   },
//   _name: {
//     value: 'Taro',
//     writable: true,
//     enumerable: true,
//     configurable: true
//   },
//   _id: {
//     value: 5,
//     writable: true,
//     enumerable: true,
//     configurable: true
//   }
// }
```
**Data properties**には、`value`、`writable`、`enumerable`、`configurable`が存在し、
**Accessor Properties**には、`value`、`writable`、`get`と`set`が存在している。

内部にすでに定義されているプロパティから`get`するのもできます。
```jsx
const person = {
  firstName: 'Taro',
  lastName: 'Yamada',

  get greeting() {
    return `Hello, ${this.firstName} ${this.lastName}`
  }
}

console.log(person.greeting) // Hello, Taro Yamada
```


# Methods for protecting Objects
## Object.preventExtensions()
`Object.preventExtensions()`はオブジェクトの拡張を抑えるメソッドです。
```jsx
const person = {
  id: 1
}

Object.preventExtensions(person)
person.name = 'Taro'
console.log(person)

console.log(delete person.id) // true
console.log(person) // {}
```
拡張できませんが、`delete`運算子で既存のプロパティを削除することができます。

拡張防止だけなので、
```jsx
Object.defineProperty(person, 'id', {
  writable: false,
  enumerable: false,
  configurable: false
})

console.log(delete person.id) // false
```
`writable`、`enumerable`、`configurable`を`true`から`false`への変更が許します。

`Object.isExtensible()`メソッドで拡張可能か確認できます。
```jsx
console.log(Object.isExtensible(person)) // false
```

## Object.seal()
`Object.seal()`は、
```jsx
Object.seal(person)
console.log(delete person.id) // false

person.name = 'Taro'
console.log(person) // { id: 1 }

person.id = 5
console.log(person)  // { id: 5 }
```
- オブジェクトの拡張を許さない。
- 強制的にプロパティを`configurable: false`にする。
- 既存のプロパティが`writable: true`の場合は、プロパティの**value**が変更できる。
```jsx
console.log(Object.getOwnPropertyDescriptor(person, 'id'))
// { value: 1, writable: true, enumerable: true, configurable: true }

Object.seal(person)
console.log(Object.getOwnPropertyDescriptor(person, 'id'))
// { value: 1, writable: true, enumerable: true, configurable: false }
```
（`Object.defineProperty()`によるattribuesの再定義はできません。）

簡単にいうと、`Object.seal()`はオブジェクトの構成を固定にする。拡張や削除を許さない。プロパティの**value**は場合によって上書きできる。

```jsx
console.log(Object.isSealed(person)) // true
```
`Object.isSealed()`でシール状態を確認できます。

## Object.freeze()
`Object.freeze()`は`Object.seal()`とほぼ同じですが、
```jsx
console.log(Object.getOwnPropertyDescriptor(person, 'id'))
// { value: 1, writable: true, enumerable: true, configurable: true }

Object.freeze(person)
console.log(Object.getOwnPropertyDescriptor(person, 'id'))
// { value: 1, writable: false, enumerable: true, configurable: false }
```
- プロパティの特性（attributes）へすべての変更は許さない。
- **value**への上書きは許さない。

```jsx
console.log(Object.isFrozen(person)) // true
```
`Object.isFrozen()`でフリーズ状態を確認できます。