JavaScriptプロパティとメソッド

2025-07-28

はじめに

JavaScriptのオブジェクト指向プログラミングにおいて、クラスとオブジェクトの基本を理解したら、次に重要なのが「プロパティ」と「メソッド」の概念です。この章では、プロパティとメソッドについて、その定義から実践的な使い方まで、詳細に解説していきます。

プロパティとは

プロパティ(Property)は、オブジェクトが持つ「データ」や「状態」を表すものです。クラスベースのオブジェクト指向言語では「メンバ変数」や「フィールド」と呼ばれることもあります。変数と似ていますが、以下の違いを明確にしています。

項目変数プロパティ
所属独立しているオブジェクトに属する
アクセス直接 nameobject.property
スコープありオブジェクト内
宣言let / const / varオブジェクト内で定義

変数は「箱」、プロパティは「オブジェクトが持つ特徴」と考えると理解しやすいです。

プロパティの基本

class Person {
  constructor(name, age) {
    this.name = name; // nameプロパティ
    this.age = age;   // ageプロパティ
  }
}

const person1 = new Person('山田太郎', 30);
console.log(person1.name); // "山田太郎"
console.log(person1.age);  // 30

この例では、nameageがPersonクラスのプロパティです。constructorメソッド内でthisを使ってプロパティを定義しています。

プロパティの種類

JavaScriptのプロパティにはいくつかの種類があります:

  1. インスタンスプロパティ:各インスタンスごとに固有の値を持つ
  2. 静的プロパティ(スタティックプロパティ):クラス全体で共有される
  3. プライベートプロパティ:クラス内部からのみアクセス可能(ES2022以降)

インスタンスプロパティの例

class Car {
  constructor(brand) {
    this.brand = brand; // インスタンスプロパティ
  }
}

const myCar = new Car('Toyota');
const yourCar = new Car('Honda');

console.log(myCar.brand);  // "Toyota" - インスタンスごとに異なる
console.log(yourCar.brand); // "Honda" - インスタンスごとに異なる

静的プロパティの例

class MathUtil {
  static PI = 3.14159; // 静的プロパティ
}

console.log(MathUtil.PI); // 3.14159 - インスタンスを作らずにアクセス可能

プライベートプロパティの例(ES2022以降)

class BankAccount {
  #balance; // プライベートプロパティ(#で宣言)

  constructor(initialBalance) {
    this.#balance = initialBalance;
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount(1000);
console.log(account.getBalance()); // 1000
// console.log(account.#balance); // エラー:クラス外からはアクセス不可

プロパティの操作

プロパティの値を取得したり変更したりする基本的な操作:

class Product {
  constructor(name, price) {
    this.name = name;
    this.price = price;
  }
}

const laptop = new Product('Laptop', 1000);

// プロパティの取得
console.log(laptop.name);  // "Laptop"
console.log(laptop.price); // 1000

// プロパティの変更
laptop.price = 1200;
console.log(laptop.price); // 1200

// 新しいプロパティの追加(動的に)
laptop.stock = 50;
console.log(laptop.stock); // 50

メソッドとは

メソッド(Method)は、オブジェクトが持つ「機能」や「振る舞い」を表すものです。クラス内で定義された関数と考えることができます。

メソッドの基本

class Greeter {
  greet(name) {
    console.log(`こんにちは、${name}さん!`);
  }
}

const greeter = new Greeter();
greeter.greet('田中'); // "こんにちは、田中さん!"

この例では、greetがGreeterクラスのメソッドです。

メソッドの種類

  1. インスタンスメソッド:インスタンスを通じて呼び出す
  2. 静的メソッド(スタティックメソッド):クラス自体から呼び出す
  3. プライベートメソッド:クラス内部からのみ呼び出せる(ES2022以降)
  4. ゲッター/セッターメソッド:プロパティのようにアクセスできる特別なメソッド

インスタンスメソッドの例

class Calculator {
  add(a, b) {
    return a + b;
  }
}

const calc = new Calculator();
console.log(calc.add(5, 3)); // 8

静的メソッドの例

class StringUtil {
  static capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }
}

console.log(StringUtil.capitalize('hello')); // "Hello" - インスタンス化不要

プライベートメソッドの例(ES2022以降)

class Auth {
  #validatePassword(password) {
    return password.length >= 8;
  }

  createAccount(username, password) {
    if (!this.#validatePassword(password)) {
      throw new Error('パスワードは8文字以上必要です');
    }
    // アカウント作成処理...
  }
}

const auth = new Auth();
// auth.#validatePassword('test'); // エラー:外部から呼び出せない
auth.createAccount('user1', 'password123'); // OK

ゲッター/セッターメソッドの例

class Temperature {
  constructor(celsius) {
    this._celsius = celsius;
  }

  // ゲッター
  get fahrenheit() {
    return this._celsius * 9/5 + 32;
  }

  // セッター
  set fahrenheit(value) {
    this._celsius = (value - 32) * 5/9;
  }
}

const temp = new Temperature(25);
console.log(temp.fahrenheit); // 77 - ゲッターが呼ばれる
temp.fahrenheit = 100;        // セッターが呼ばれる
console.log(temp._celsius);   // 37.777...

メソッドの実践的な使用例

より実践的なメソッドの使用例を見てみましょう:

class ShoppingCart {
  constructor() {
    this.items = [];
  }

  // 商品を追加するメソッド
  addItem(product, quantity = 1) {
    this.items.push({ product, quantity });
  }

  // 合計金額を計算するメソッド
  calculateTotal() {
    return this.items.reduce((total, item) => {
      return total + (item.product.price * item.quantity);
    }, 0);
  }

  // カートの内容を表示するメソッド
  displayCart() {
    console.log('カートの内容:');
    this.items.forEach(item => {
      console.log(`${item.product.name} x ${item.quantity}`);
    });
    console.log(`合計: ¥${this.calculateTotal()}`);
  }
}

// 使用例
const cart = new ShoppingCart();
const product1 = { name: 'ノートPC', price: 150000 };
const product2 = { name: 'マウス', price: 5000 };

cart.addItem(product1);
cart.addItem(product2, 2);
cart.displayCart();
/*
カートの内容:
ノートPC x 1
マウス x 2
合計: ¥160000
*/

プロパティとメソッドの関係

プロパティとメソッドは密接に関連しています。メソッドは多くの場合、プロパティの値を操作したり、プロパティに基づいた計算を行ったりします。

プロパティを操作するメソッドの例

class BankAccount {
  constructor(initialBalance) {
    this.balance = initialBalance;
  }

  // 入金メソッド
  deposit(amount) {
    if (amount <= 0) {
      console.log('入金額は正の値でなければなりません');
      return;
    }
    this.balance += amount;
    console.log(`¥${amount}入金しました。新しい残高: ¥${this.balance}`);
  }

  // 出金メソッド
  withdraw(amount) {
    if (amount <= 0) {
      console.log('出金額は正の値でなければなりません');
      return;
    }
    if (amount > this.balance) {
      console.log('残高不足です');
      return;
    }
    this.balance -= amount;
    console.log(`¥${amount}出金しました。新しい残高: ¥${this.balance}`);
  }

  // 残高確認メソッド
  checkBalance() {
    console.log(`現在の残高: ¥${this.balance}`);
  }
}

// 使用例
const account = new BankAccount(10000);
account.deposit(5000);    // ¥5000入金しました。新しい残高: ¥15000
account.withdraw(2000);    // ¥2000出金しました。新しい残高: ¥13000
account.checkBalance();   // 現在の残高: ¥13000

プロパティとメソッドの設計原則

効果的なオブジェクト設計のためには、プロパティとメソッドを適切に設計する必要があります。以下にいくつかの重要な原則を紹介します。

カプセル化(Encapsulation)

オブジェクトの内部状態(プロパティ)はできるだけ外部から直接アクセスさせず、メソッドを通じて操作するようにします。

悪い例:

class User {
  constructor() {
    this.password = 'default'; // パスワードが直接公開されている
  }
}

良い例:

this.#passwordは直接アクセスすることはさせずにchangePassword()メソッドを利用してpasswordプロパティを変更させます。

class User {
  #password; // プライベートプロパティ

  constructor() {
    this.#password = 'default';
  }

  changePassword(oldPass, newPass) {
    if (this.#password === oldPass) {
      this.#password = newPass;
      return true;
    }
    return false;
  }
}

凝集度(Cohesion)

クラスは関連性の高いプロパティとメソッドだけを持つようにします。1つのクラスが多くの無関係な機能を持つと、保守性が低下します。

悪い例:

class Employee {
  // 従業員に関連するプロパティ
  constructor(name, position) {
    this.name = name;
    this.position = position;
  }

  // 従業員に関連するメソッド
  calculateSalary() { /* ... */ }

  // 無関係なメソッド
  sendCompanyWideEmail() { /* ... */ } // これは別のクラスにすべき
}

適切な抽象化

実装の詳細を隠蔽し、必要なインターフェースだけを公開します。

class CoffeeMachine {
  #waterLevel; // 内部状態は隠蔽

  constructor() {
    this.#waterLevel = 0;
  }

  // 公開するインターフェース
  addWater(amount) {
    if (amount > 0) {
      this.#waterLevel += amount;
    }
  }

  makeCoffee() {
    if (this.#waterLevel < 1) {
      console.log('水が足りません');
      return null;
    }
    this.#waterLevel -= 1;
    return 'コーヒー';
  }
}

プロパティとメソッドの応用テクニック

動的なプロパティ名

ES6以降では、計算されたプロパティ名を使用できます:

オブジェクトのキー(プロパティ名)を変数や計算結果で決められる書き方になります。
中括弧 {} の中で [ ] を使って指定します。例の場合は this[propName] が該当します。

const propName = 'age';

class Person {
  constructor(name, age) {
    this.name = name;
    this[propName] = age; // 動的なプロパティ名
  }
}

const person = new Person('田中', 25);
console.log(person.age); // 25

メソッドチェーン

メソッドがthisを返すことで、メソッドチェーンが可能になります:

thisを返却することで各メソッドの呼び出し結果が再び同じオブジェクトを返すことができます。

class Calculator {
  constructor(value = 0) {
    this.value = value;
  }

  add(num) {
    this.value += num;
    return this; // thisを返す
  }

  subtract(num) {
    this.value -= num;
    return this;
  }

  multiply(num) {
    this.value *= num;
    return this;
  }
}

const calc = new Calculator();
const result = calc.add(5).multiply(2).subtract(3).value;
console.log(result); // (0 + 5) * 2 - 3 = 7

プロパティの存在確認

in演算子やhasOwnPropertyメソッドでプロパティの存在を確認できます:

hasOwnProperty メソッドは、オブジェクトが特定のプロパティを「自分自身」で持っているか」を調べるメソッドです。これは継承元(Object.prototypeなど)から引き継いだプロパティはカウントせず、そのオブジェクトの直接のプロパティだけを判定します。

class Person {
  constructor(name) {
    this.name = name;
  }
}

const person = new Person('佐藤');

console.log('name' in person);          // true
console.log(person.hasOwnProperty('name')); // true
console.log('toString' in person);     // true(継承されたプロパティ)
console.log(person.hasOwnProperty('toString')); // false

プロトタイプとメソッド

JavaScriptのクラスはプロトタイプベースの継承を抽象化したものです。メソッドは実際にはプロトタイプオブジェクトに定義されます。

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name}が音を出します`);
  }
}

// これは以下とほぼ同等
function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  console.log(`${this.name}が音を出します`);
};

このプロトタイプの仕組みを理解することで、より高度なオブジェクト操作が可能になります。

まとめ

この章では、JavaScriptのオブジェクト指向プログラミングにおけるプロパティとメソッドについて詳しく学びました。

  • プロパティはオブジェクトの状態を表すデータ
  • インスタンスプロパティ、静的プロパティ、プライベートプロパティがある
  • 動的に追加・変更可能
  • メソッドはオブジェクトの振る舞いを表す関数
  • インスタンスメソッド、静的メソッド、プライベートメソッドがある
  • ゲッター/セッターでプロパティのようにアクセス可能
  • 設計原則が重要
  • カプセル化、凝集度、適切な抽象化
  • 応用テクニック
  • 動的プロパティ名、メソッドチェーン、プロパティ存在確認
  • プロトタイプの理解が深い理解につながる

プロパティとメソッドを適切に設計・使用することで、より保守性の高い、再利用可能なコードを書くことができます。次の章では、これらの概念を基に「継承」について学んでいきます。


演習問題

初級問題

問題1: 次のコードの出力結果は何ですか?

const obj = {
  name: "JavaScript",
  version: "ES6",
  getInfo: function() {
    return `${this.name} ${this.version}`;
  }
};
console.log(obj.getInfo());

問題2: オブジェクトのプロパティにアクセスする2つの方法をコード例で示してください。

問題3: 次のコードの出力結果は何ですか?

const car = {
  brand: "Toyota",
  model: "Corolla",
  year: 2020,
  display: function() {
    console.log(`${this.brand} ${this.model} (${this.year})`);
  }
};
car.display();

中級問題

問題4: 次のコードの出力結果は何ですか?また、その理由を説明してください。

const person = {
  firstName: "John",
  lastName: "Doe",
  fullName: function() {
    return this.firstName + " " + this.lastName;
  }
};
const getName = person.fullName;
console.log(getName());

問題5: 次のコードを修正して、正しく"Hello, World!"と出力されるようにしてください。

const greeting = {
  message: "Hello, World!",
  sayHello: () => {
    console.log(this.message);
  }
};
greeting.sayHello();

問題6: オブジェクトに新しいメソッドを追加するコードを書いてください。オブジェクトはcalculatorで、既にaddメソッドを持っています。新たにsubtractメソッドを追加してください。

問題7: 次のコードの出力結果は何ですか?

const book = {
  title: "JavaScript Guide",
  author: "M. Smith",
  getDetails() {
    return `Title: ${this.title}, Author: ${this.author}`;
  }
};
const details = book.getDetails;
console.log(details.call(book));

問題8: オブジェクトのプロパティを列挙する3つの方法(for...in, Object.keys(), Object.getOwnPropertyNames())の違いを説明してください。

問題9: 次のコードの出力結果は何ですか?

const circle = {
  radius: 5,
  get diameter() {
    return this.radius * 2;
  },
  set diameter(value) {
    this.radius = value / 2;
  }
};
circle.diameter = 12;
console.log(circle.radius);

上級問題

問題10: プロトタイプメソッドとインスタンスメソッドの違いを説明し、コード例で示してください。

問題11: 次のコードの出力結果は何ですか?また、その理由を説明してください。

function Person(name) {
  this.name = name;
}
Person.prototype.greet = function() {
  return `Hello, my name is ${this.name}`;
};
const john = new Person("John");
const greet = john.greet;
console.log(greet());

問題12: Object.defineProperty()を使用して、読み取り専用のプロパティを作成するコードを書いてください。


解答例

初級問題の解答

解答1:

JavaScript ES6

解答2:

const obj = { name: "Alice" };

// ドット記法
console.log(obj.name);

// ブラケット記法
console.log(obj["name"]);

解答3:

Toyota Corolla (2020)

中級問題の解答

解答4:

undefined undefined

理由: getNameに代入された時点でthisのコンテキストが失われ、グローバルオブジェクト(またはstrictモードではundefined)を参照するため。

解答5:

const greeting = {
  message: "Hello, World!",
  sayHello: function() {
    console.log(this.message);
  }
};
greeting.sayHello();

解答6:

const calculator = {
  add: function(a, b) {
    return a + b;
  }
};

calculator.subtract = function(a, b) {
  return a - b;
};

解答7:

Title: JavaScript Guide, Author: M. Smith

解答8:

  • for...in: オブジェクト自身とプロトタイプチェーンの列挙可能なプロパティを列挙
  • Object.keys(): オブジェクト自身の列挙可能なプロパティのみを配列で返す
  • Object.getOwnPropertyNames(): オブジェクト自身の列挙可能/不可を問わず全てのプロパティを配列で返す

解答9:

6

上級問題の解答

解答10:

function Car(make) {
  this.make = make;
  // インスタンスメソッド
  this.getMake = function() {
    return this.make;
  };
}

// プロトタイプメソッド
Car.prototype.getMakeProto = function() {
  return this.make;
};

const myCar = new Car("Toyota");
console.log(myCar.getMake()); // "Toyota"
console.log(myCar.getMakeProto()); // "Toyota"

// 違い: インスタンスメソッドは各インスタンスごとに作成されるが、
// プロトタイプメソッドは全てのインスタンスで共有される

解答11:

Hello, my name is undefined

理由: greet変数にメソッドを代入した時点でthisのコンテキストが失われ、グローバルオブジェクト(またはstrictモードではundefined)を参照するため。

解答12:

const obj = {};
Object.defineProperty(obj, 'readOnlyProp', {
  value: 'This is read-only',
  writable: false,
  enumerable: true,
  configurable: false
});

obj.readOnlyProp = 'Try to change'; // エラーにはならないが、値は変更されない
console.log(obj.readOnlyProp); // "This is read-only"