0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

JavaScriptのObjectについて

Last updated at Posted at 2022-07-18

初めに

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

今回の参考文章:
How to create objects in JavaScript - freeCodeCamp
Diving Deeper in JavaScripts Objects
Property getters and setters - javaScript.info
Property attributes: an introduction - Deep JavaScript

オブジェクトを創る方法

値へのアクセスの仕方

オブジェクのプロパティについて

オブジェクトを守るメソッドを紹介
...という流れで行きたいと思います。

How to create an Object

object literal {}

一番簡単なのはObject literals{}

const person = {
  name: 'Taro',
}

person.age = 20
console.log(person.name, person.age) // Taro 20

{}に直接書き込みか、外でkeyを指定して値を付与するのもできます。

new

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

new Object()

Object() Object() constructor

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

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()がプロトタイプチェーンを使えないわけでもありませんが、

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
JavaScriptのPrototypeについて

Obejct.create()

const Person = Object.create({})
console.log(Person)

Obejct.create()newと似ていますが、Obejct.create()のほうがもっとシンプルに扱いやすく、そして与えられた引数をprototypeにすることができます!

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()の内部構造を書いてみると、

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の**by function() {}**と同じロジックで創られたと気づきました。

簡単に言うと、Object.prototypePersonオブジェクトの間に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()
構文:Object.assign(target, ...sources)
ネスト無しのオブジェクトなら、Object.assign()shallow copyができ、また複数のオブジェクトを整合することができます。プロパティキー(property key)が同じであった場合、後から上書きされる。変更されたオブジェクトは一番目の引数(target)に返します。

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

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を指しています。

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 は引き続きプロトタイプベースです
継承とプロトタイプチェーン

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

The structure of objects

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

Data Properties

Data PropertiesObject nameProperty nameなどオブジェクトデータのプロパティ。

const person = {
  id: 1,
  name: 'Taro'
}

Object nameperson、Property nameidnameなどがオブジェクトのData Propertiesです。

Property Accessor

Property accessors
Property Accessor:プロパティへのアクセス方法で、ブラケット表記法とドット表記法があります。

bracket notation [](Computed Member Access)

const person = {}
person['firstName'] = 'Taro'
person['lastNanme'] = 'Yamada'
console.log(person.firstName, person.lastNanme)

dot notation .(Member Access)

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を返します。

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

Object.getOwnPropertyDescriptors()はオブジェクト全体propertyのattributeを返します。アクセサーで単一記述子のvalueもアクセスできます。

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 AttributesProperty 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

(writableenumerableconfigurable初期値は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での変更が不可能になります。

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...inObject.keys()で見つけ出すことができません。

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

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' }

writableenumerableいずれも初期値がfalseなので、デモのために変更しておきました。)

  • delete演算子は使用できません。
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の例をご参照ください。)
person.firstName = 'Jiro'
console.log(person.firstName) // Jiro

Object.defineProperty(person, 'firstName', {
  value: 'Jiro'
})
console.log(person.firstName) // Jiro
  • Object.defineProperty()で一度configurable: falseを設置すると、再びenumerableconfigurablegetsetの値を変更できません。
    writable: trueならwritable: falseへ変更できますが、writable: falseではtrueへ変更できません。
    writable: trueであればvalueの変更は可能です。
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

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

上のように外部からオブジェクトへのプロパティ設置によって、getsetで値の取り出しや保存を処理している。
たとえばpersonオブジェクトで設置したgetsetは、person.nameからsetを経由して値をperson._nameへ保存し、そして再びperson.nameをアクセスするとgetperson._nameから値を取り出し、または指定の処理をしてから値を返します。

一般のプロパティと、getsetによる処理されたプロパティとのdescriptorは、

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には、valuewritableenumerableconfigurableが存在し、
Accessor Propertiesには、valuewritablegetsetが存在している。

内部にすでに定義されているプロパティからgetするのもできます。

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()はオブジェクトの拡張を抑えるメソッドです。

const person = {
  id: 1
}

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

console.log(delete person.id) // true
console.log(person) // {}

拡張できませんが、delete運算子で既存のプロパティを削除することができます。

拡張防止だけなので、

Object.defineProperty(person, 'id', {
  writable: false,
  enumerable: false,
  configurable: false
})

console.log(delete person.id) // false

writableenumerableconfigurabletrueからfalseへの変更が許します。

Object.isExtensible()メソッドで拡張可能か確認できます。

console.log(Object.isExtensible(person)) // false

Object.seal()

Object.seal()は、

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が変更できる。
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は場合によって上書きできる。

console.log(Object.isSealed(person)) // true

Object.isSealed()でシール状態を確認できます。

Object.freeze()

Object.freeze()Object.seal()とほぼ同じですが、

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への上書きは許さない。
console.log(Object.isFrozen(person)) // true

Object.isFrozen()でフリーズ状態を確認できます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?