[JavaScript講座] オブジェクトとプロトタイプ

当ページのリンクには広告が含まれています。
目次

オブジェクトリテラル {} とプロパティ

一番シンプルなオブジェクト

const user = {
  name: "Taro",
  age: 20,
};
  • {} が「オブジェクトリテラル」
  • name / age が「プロパティ名(キー)」
  • "Taro" / 20 が「プロパティ値」

プロパティは「キーと値のペア」の集まりです。

値の型は何でもOK

const user = {
  name: "Taro",          // 文字列
  age: 20,               // 数値
  isAdmin: false,        // 真偽値
  favorites: ["JS"],     // 配列
  address: { city: "東京" }, // オブジェクト
  greet: function () {   // 関数(メソッド)
    console.log("こんにちは");
  },
};

メソッド記法(短縮形)

関数プロパティは、短く書けます:

const user = {
  name: "Taro",
  greet() {
    console.log(`こんにちは、${this.name}です`);
  },
};

user.greet(); // こんにちは、Taroです
  • greet: function () { ... } の省略形

プロパティ名と識別子

プロパティ名は基本的には 文字列 として扱われます。

const obj = {
  foo: 1,
  "bar-baz": 2,
};
  • foo"foo" も「キー文字列 'foo'」として解釈される
  • ただし、識別子のルールを守らない名前(例:"bar-baz")は、ドット記法でアクセスできません

プロパティ名には Symbol も使えますが、ここでは「キー=(ほぼ)文字列」と思っておいてOKです。

プロパティアクセス(ドット / ブラケット)

ドット記法

const user = { name: "Taro", age: 20 };

console.log(user.name); // "Taro"
console.log(user.age);  // 20
  • 一番よく使う形
  • 右側は 有効な識別子 である必要がある
    • 先頭に数字NG、スペースや記号NG

ブラケット記法

const user = { name: "Taro", age: 20 };

console.log(user["name"]); // "Taro"
console.log(user["age"]);  // 20
  • "プロパティ名文字列"[] の中に書く
  • ドット記法で書けるものはどちらでもOK

ブラケット記法の真価は、「動的なキー」を使えることです。

const prop = "name";
const user = { name: "Taro" };

console.log(user[prop]); // "Taro"(user["name"])

APIレスポンスなどで「どのキーが来るかわからない」ようなときに必須です。

ドットで書けないプロパティ名

const obj = {
  "foo-bar": 1,
  "123": 2,
};

console.log(obj["foo-bar"]); // 1
console.log(obj["123"]);     // 2

// console.log(obj.foo-bar); // NG(構文としてエラー or 計算扱い)
// console.log(obj.123);     // NG(構文エラー)
  • スペース・ハイフン・先頭数字などがあるプロパティ名 → ブラケット必須

オブジェクトは「参照型」として振る舞う

ここが重要ポイントです。

プリミティブとの違い

プリミティブ(数値・文字列・真偽値など)は 値そのもの がコピーされます。

let a = 1;
let b = a; // 値 1 がコピーされる

b = 2;

console.log(a); // 1
console.log(b); // 2

オブジェクトは 参照 がコピーされます。

const obj1 = { value: 1 };
const obj2 = obj1; // 「同じオブジェクト」への参照がコピーされる

obj2.value = 2;

console.log(obj1.value); // 2 ← 変わる
console.log(obj2.value); // 2
  • obj1obj2 は「別の箱」だが、中身として指しているオブジェクトは同じ

参照共有のイメージ

obj1 → 🧱(value: 1) ← obj2

このように、2つの変数が同じ「箱(オブジェクト)」を指しているイメージです。

オブジェクト同士の比較

const a = { x: 1 };
const b = { x: 1 };
const c = a;

console.log(a === b); // false  (中身が同じでも、別オブジェクト)
console.log(a === c); // true   (同じオブジェクトを指している)
  • === は「同じオブジェクトを指しているかどうか」で比較する
  • 「中身が同じかどうか」を見てくれない(それは自分で比較する必要がある)

「コピーしたつもりが共有していた」バグに注意

const original = { nested: { value: 1 } };

// 「コピーしたつもり」でも、実は参照共有
const copy = original;

copy.nested.value = 999;

console.log(original.nested.value); // 999

これは意図的に共有したいときは便利ですが、
「独立したコピーが欲しかったのに」という場合はバグになります。

ここではひとまず:

「オブジェクト変数を代入しても、オブジェクト本体が複製されるわけではない」

という点をしっかり押さえておいてください。

プロトタイプチェーンとプロパティ探索

「見えない親オブジェクト」= [[Prototype]]

JavaScript のオブジェクトは、内部的に

「自分の親オブジェクト(プロトタイプ)への参照」

を 1 つ持っています。
仕様ではこれを [[Prototype]] と表記します(内部スロットなので直接は触れないイメージ)。

イメージ:

obj ----> [[Prototype]] ----> さらに [[Prototype]] ----> ... ----> null

この「鎖」が プロトタイプチェーン です。

プロパティ探索アルゴリズム

obj.prop を読むとき、エンジンは次の順で探します:

  1. obj 自身が prop を持っているか?
    • 持っていればそれを返して終了
  2. なければ obj.[[Prototype]](親オブジェクト)を見に行く
  3. それでもなければ、さらにその親…とたどっていく
  4. 最後の親([[Prototype]]null)まで行ってもなければ undefined

コードで見ると:

const parent = { kind: "parent" };
const child = Object.create(parent); // parent をプロトタイプに持つオブジェクトを作る
child.name = "child";

console.log(child.name); // "child"     (自分のプロパティ)
console.log(child.kind); // "parent"    (親のプロパティ)
console.log(child.toString); // Object.prototype 由来のメソッド
  • child には kind はない
  • 親(parent)が kind を持っているので、それが返ってくる
  • さらにその親は Object.prototype になっており、toString などが定義されている

Object.getPrototypeOf__proto__

実際にプロトタイプを覗くには:

const parent = { kind: "parent" };
const child = Object.create(parent);

console.log(Object.getPrototypeOf(child) === parent); // true

ブラウザ環境などでは、慣習的に __proto__ というプロパティでも見えます(仕様上は非推奨)。

console.log(child.__proto__ === parent); // true(環境によっては見える)
  • 学習用にはわかりやすいですが、実務的には Object.getPrototypeOf / Object.setPrototypeOf を使う方が推奨されます。

「自分のプロパティ」と「継承されたプロパティ」

const parent = { kind: "parent" };
const child = Object.create(parent);
child.name = "child";

console.log(child.name); // "child"
console.log(child.kind); // "parent"

console.log(child.hasOwnProperty("name")); // true
console.log(child.hasOwnProperty("kind")); // false
  • hasOwnProperty は「このオブジェクト自身が持っているか」だけを見る
  • プロトタイプ経由で見えているプロパティは false

hasOwnProperty 自体も Object.prototype から継承されているメソッドです。

上書き(シャドーイング)

子オブジェクトで同じ名前のプロパティを定義すると、親のプロパティを「隠す」 形になります。

const parent = { value: 1 };
const child = Object.create(parent);

console.log(child.value); // 1(親由来)

child.value = 2;

console.log(child.value);       // 2(自分のプロパティ)
console.log(parent.value);      // 1(親は変わってない)
console.log(child.hasOwnProperty("value")); // true
  • 最初は親の value が見えていた
  • 子で value を定義すると、「自分の value」が優先される
  • これを「プロパティのシャドーイング(shadowing)」と呼びます。

Object.create で継承チェーンを作る

Object.create を使ったシンプルな継承の例です。

const animal = {
  walk() {
    console.log("I am walking");
  },
};

const dog = Object.create(animal); // animal をプロトタイプに
dog.bark = function () {
  console.log("わん!");
};

dog.walk(); // "I am walking" (animal 由来)
dog.bark(); // "わん!"       (dog 自身)
  • dogwalk を持っていない
  • プロトタイプ(animal)をたどって walk を見つけて実行している

<<前へ(this 完全理解とクロージャ)

>>次へ(コンストラクタ関数と class 構文)

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次