【STEP.26】
オブジェクトの操作方法を学ぼう!

オブジェクトを作成する

連想配列と同じく、以下のリテラル構文でオブジェクトを作成できます。(正しくは、JavaScriptの世界ではオブジェクトを連想配列として利用しているにすぎません。)

オブジェクトリテラル

{ property: values, ...}
property  プロパティ名
values    値

たとえば以下は、namebirthプロパティ、toStringメソッドを持ったactorオブジェクトの例です。

let actor = {
  name: '横浜流星',
  birth: new Date(1996, 8, 16),
  toString: function() {
    return this.name + '(' + this.birth.toLocaleDateString() + '生まれ)';	
  }
}
console.log(actor.toString());
// 結果:横浜流星(1996/9/16生まれ)

プロパティ値には文字列/日付だけでなく、関数オブジェクトを指定できます。

JavaScriptでは、厳密にはメソッドという独立した概念はなく、「関数オブジェクトを持つプロパティがメソッドと見なされる」のです。

メソッドの簡易構文

ES2015以降の環境では、class構文に合わせて、オブジェクトリテラルでも、

メソッド名(引数,...) {...メソッド本体...}

のような表現が可能になっています。

そのため、上記のソースコード4~6行目は、以下のように書き替えが可能です。

toString() {
  return this.name + '(' + this.birth.toLocaleDateString() + '生まれ)';
}

プロパティを動的に生成する【Computed property names】

プロパティ名をブラケットでくくることで、プロパティ名を式の値から生成できます。(Computed property namesといいます。)

let num = 0;
let actor = {
  name: '横浜流星',
  birth: new Date(1996, 8, 16),
  ['feature' + ++num]: '俳優',
  ['feature' + ++num]: '174cm',
  ['feature' + ++num]: 'O型',
};
console.log(actor);
// 結果:{name: "横浜流星", birth: Mon Sep 16 1996 00:00:00 GMT+0900 (日本標準時), feature1: "俳優", feature2: "174cm", feature3: "O型"}

この例であれば、変数numをインクリメントしていくことで、feature1,2,3…のようなプロパティ名が生成されます。

ブラケット構文は、classブロックでメソッド名を指定する際にも利用できます。

変数を同名のプロパティに割り当てる

プロパティ名が、その値が格納された変数名と同じ場合には、値の指定を省略できます。

よって、以下のサンプルのactor1とactor2はいずれも同じ意味です。

let name = '横浜流星';
let birth = new Date(1996, 8, 16);
let actor1 = { name: name, birth: birth };
let actor2 = { name, birth };

console.log(actor1);
// 結果:{name: "横浜流星", birth: Mon Sep 16 1996 00:00:00 GMT+0900 (日本標準時)}
console.log(actor2);
// 結果:{name: "横浜流星", birth: Mon Sep 16 1996 00:00:00 GMT+0900 (日本標準時)}

ここではたかだかプロパティが2個程度ですが、それでもずいぶんとコードがシンプルになることが確認できます。

オブジェクトを削除する【delete演算子】

delete演算子を利用することで、オブジェクトの指定されたプロパティを削除できます。

削除に成功した場合、delete演算子はtrueを、失敗した場合にはfalseを返します。

let interest = { indoor: 'reading', outdoor: 'karate'};
console.log(delete interest.indoor);
// 結果:true
console.log(interest.indoor);
// 結果:undefined
		
let member = { name: '横浜流星', age: 23, hobby: interest };
console.log(delete member.hobby);
// 結果:true
console.log(interest);
// 結果:{outdoor: "karate"}
console.log(member);
// 結果:{name: "横浜流星", age: 23}	
console.log(delete member.job);
// 結果:true

console.log(delete member.hobby);」のように、プロパティ値がオブジェクトであった場合、プロパティが削除されるだけで参照先のオブジェクトが削除されるわけではありません。

console.log(delete member.job);」のように、存在しないプロパティを削除した場合にも、delete演算子は何もせずに、ただtrueを返します。

不変オブジェクトを定義する【preventExtensions/seal/freeze】

不変オブジェクトとは、生成した後は値を変更できないオブジェクトのことです。

オブジェクトを不変にすることで、他からオブジェクトの状態を変えられてしまう恐れがないため、実装/利用が簡単になる、意図しないバグの混入を防げる、などのメリットがあります。

不変オブジェクトを定義するために、JavaScriptでは次に掲げる表のようなメソッドを用意しています。

これらのメソッドを利用することで、プロパティの操作を制限できます。

【プロパティへの操作を制限するメソッド】
メソッドpreventExtensionssealfreeze
プロパティ値の変更×
プロパティの削除××
プロパティの追加×××

以下は、それぞれのメソッドを利用したコードです。

'use strict';		

let member = { name: '横浜流星', age: 23 };
		
// Object.preventExtensions(member);
// Object.seal(member);
// Object.freeze(member);
		
member.name = 'Ryusei Yokohama'; // プロパティ値の変更
delete member.age; // プロパティの削除
member.job = 'actor'; // プロパティの追加

コメントアウトされた「//Object.preventExtensions(member);」「//Object.seal(member);」「//Object.freeze(member);」のコードを、それぞれ有効化した場合の結果(エラー)は、以下の通りです。

  1. Cannot add property job, object is not extensible
    (jobプロパティの追加は不可。)
  2. Cannot delete property ‘age’ of #<Object>
    (ageプロパティの削除は不可。)
  3. Cannot assign to read only property ‘name’ of object ‘#<Object>’
    (nameプロパティは読み取り専用。)
MEMO
ーStrictモードー
サンプルでStrictモードを明示的に有効化しているのは、非Strictモードでは制約違反にもかかわらず、例外が発生しないからです。(=無視されるだけで通知はされません。)

これでは問題あるコードを特定しにくいので、freeze/seal/preventExtensionsを利用する際にはStrictモードを有効にすべきです。

インスタンスを凍結する

classブロックで宣言したオブジェクトを不変にすることもできます。

コンストラクターの末尾に、「Object.freeze(this);」を追加します。

これでインスタンス(this)に対して、プロパティそのものの追加/削除はもちろん、プロパティ値の変更もできなくなります。

class Article {
  constructor(title, url, intro) {
    this.title = title;
    this.url = url;
    this.intro = intro;
    Object.freeze(this);
  }
}

オブジェクトの基本動作をカスタマイズする【Proxyオブジェクト】

Proxyオブジェクトとは

Proxyは、たとえばプロパティへのアクセス、for…in命令による列挙など、オブジェクトの基本的な操作を、アプリ固有の挙動に置き換えるためのオブジェクトです。

Proxyオブジェクト

Proxyの世界では、操作をカスタマイズする対象のオブジェクトをターゲット、カスタムの挙動(メソッド)を定義したオブジェクトをハンドラーと呼びます。

また、ハンドラーで定義されたカスタムメソッドのことをトラップといいます。

Proxyオブジェクトは、呼び出し元とターゲットとの間に挟まって、ターゲットの代わりに操作を身代わりで実行する「代理役」(Proxy)ともいえます。

Proxyの基本的な例

以下は、プロパティを設定する際に、その名前と値をログ出力するためのProxyの例です。

let obj = { name: '横浜流星', age: 23 };
		
let px = new Proxy(obj, {
  set(target, prop, value) {
    console.log(`${prop} : ${value}`);
    // 結果:belong : スターダストプロモーション
    obj[prop] = value;
  }
});
		
px.belong = 'スターダストプロモーション';

Proxyオブジェクトは、以下の構文で生成します。

Proxyコンストラクター

new Proxy(target, handler)
target    カスタマイズ対象のオブジェクト(ターゲット)
handler   ターゲットに対する操作(ハンドラー)

ハンドラーで定義できるメソッドには、次に掲げる表のようなものがあります。

【ハンドラーに登録できる主なメソッド(トラップ)】
メソッド(トラップ)戻り値呼び出しのタイミング
construct(target,args)オブジェクトnew演算子によるインスタンス化
defineProperty(target,property,descriptor)プロパティの定義の設定/変更
deleteProperty(target,property)boolean型プロパティの削除
get(target,property,receiver)任意の型プロパティ値の取得
set(target,property,value,receiver)boolean型プロパティ値の設定
apply(target,thisArg,args)任意の型applyメソッドによる関数呼び出し
setPrototypeOf(target,prototype)プロトタイプの設定
getPrototypeOf(target)オブジェクト/nullプロトタイプの取得
has(targe,property)boolean型in演算子によるメンバーの存在確認

set(target, prop, value) {...}(上のソースコードの4~8行目)」では、setメソッドを実装して、プロパティ(prop)/値(value)をログに出力しています。

obj[prop] = value;」は、本来のプロパティの設定動作です。

ここでは、ログ出力しているだけですが、実際の用途では、「プロパティ値の設定/取得に際して、値検証/変換(加工)などの処理を加える」「プロパティの設定/オブジェクト生成をトリガーに、関連する処理を呼び出す」などの用途でも、Proxyは活用できます。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です