初めに
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.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()
構文: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 は引き続きプロトタイプベースです
継承とプロトタイプチェーン
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
などオブジェクトデータのプロパティ。
const person = {
id: 1,
name: 'Taro'
}
Object nameperson
、Property nameid
、name
などがオブジェクトの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 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での変更が不可能になります。
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()
で見つけ出すことができません。
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' }
(writable
、enumerable
いずれも初期値が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
を設置すると、再びenumerable
、configurable
、get
、set
の値を変更できません。
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
上のように外部からオブジェクトへのプロパティ設置によって、get
とset
で値の取り出しや保存を処理している。
たとえばperson
オブジェクトで設置したget
とset
は、person.name
からset
を経由して値をperson._name
へ保存し、そして再びperson.name
をアクセスするとget
はperson._name
から値を取り出し、または指定の処理をしてから値を返します。
一般のプロパティと、get
とset
による処理されたプロパティとの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には、value
、writable
、enumerable
、configurable
が存在し、
Accessor Propertiesには、value
、writable
、get
とset
が存在している。
内部にすでに定義されているプロパティから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
writable
、enumerable
、configurable
をtrue
から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()
でフリーズ状態を確認できます。