このサイトは、只今WEB業界で活躍中のデザイナー、プログラマーの方々の情報を集めたweb統合情報サイトです。

Archives Details

JavaScript オブジェクト指向 プロトタイプなどのまとめ

JavaScript

2013.01.27

この記事は最終更新日から1年以上が経過しています。

何気にここらへんについては書いていない感じでしたので、ざっとではありますがまとめさせて頂きました。

まず、ここが変だよJavaScriptといったところで挙げられるのは

「厳密な意味でのクラスという概念がない。」ということではないでしょうか。

ここで言う「クラス」とはCSSが参照するclassではなく、

C#、Javaなどといったオブジェクト指向プログラミング言語で使用される「クラス」に該当します。

あくまで厳密な意味で無い。とのことなので、無いこともないのです。

では、どのように作るかと言いますと。

var Webcyou = function(){};

と、最もシンプルなクラスの例を挙げてみました。

var hoge = new Webcyou();

と、呼び出す際はクラスベースのオブジェクト指向構文でも用いられるnew 演算子を利用し、

インスタンス化し実行することができます。

コンストラクタ

Webcyou関数は、new演算子によって呼び出されているのですが、この関数にあたる部分を「コンストラクタ」(constructor)と呼びます。

「コンストラクタ」は新たに生成するオブジェクトを初期化する(自動的に呼び出される)関数となります。

よってJavascriptでいうクラスはオブジェクトだということになります。

コンストラクタとして関数オブジェクトを呼び出した場合、thisは新たに生成するオブジェクトを指します。

this.プロパティ名 = 値

このように、オブジェクトにプロパティを追加することもできます。

プロパティとメソッド

this.プロパティ名で「プロパティ」の追加同様に「メソッド」の追加も行うことができます。

以下はPersonクラスにnameとageのプロパティを追加し、toStringという名前のメソッドを追加した例となります。

var Person = function(name, age) {
  this.name = name;
  this.age = age;
  this.toString = function() {
    window.alert(this.name + " : " + this.age + "歳");
  };
}
var hito = new Person("ウェブ夫", "4");
hito.toString();// 「ウェブ夫 : 4歳」

toString関数を実行する「ウェブ夫:4歳」と表示致します。

このように、new演算子によってインスタンス化されたオブジェクトに対してもメンバーを追加できるのが

Javascriptの特徴となっております。

2つのインスタンス

ここでインスタンスを2つ作成しました。

var Person = function(name, age) {
  this.name = name;
  this.age = age;
}
var hito = new Person("ウェブ夫", "4");
hito.toString = function() {
  window.alert(this.name + " : " + this.age + "歳");
};
hito.toString();// 「ウェブ夫 : 4歳」
var hito2 = new Person("ウェブ子", "18");
hito2.toString();//エラーとなります。

コンストラクタからtoStringを出し、インスタンスにtoSring関数を追加しました。

その後、hitoにtoSring関数の呼び出しを行うと、「ウェブ夫:4歳」と表示されます。

その後、またhito2インスタンスの生成を行い、hito2にtoSring関数の呼び出しを行うとエラーとなります。

図にするとこんな感じでしょうか。

このことによって、インスタンスのメソッドの追加はあくまでインスタンスへの追加であり、

基であるクラス「Person」には追加されていないことが分かります。

つまり、JavaScriptは、クラスベースのオブジェクト指向言語と異なり、同一のクラスを基に作成されたインスタンスでも異なるメンバーを持つ可能性があるということになります。

また、インスタンスに追加したメンバーは、そのインスタンスのみ有効となります。

プロトタイプベース

インスタンスに追加した メソッドはそのインスタンスのみ有効。

よって、インスタンス共通のメソッドを定義しようとすると、インスタンスではなくコンストラクタに定義しないといけないことが分かった。

ここで起こっている処理について考えてみましょう。

クラス(コンストラクタ)はインスタンスを生成する度に、各インスタンスの為にメモリを確保しています。

上記の例からすると、Parsonを基に生成したhitoはname,age,toStringという3つメンバーを設定しています。

toStringに関しては言えば全てのインスタンスで全く同じ値を設定します。

メソッドが1つや2つでインスタンスも1つや2つだとまだ良いのですが、10、20登録されているクラスの場合、インスタンスを生成する度に10、20のメソッドを無駄にコピーしてしまう結果となってしまいます。

これでは、パフォーマンス低下にもつながり、好ましくはありません。

そこで利用するのがプロトタイプとなります。

プロトタイプを利用するとプロパティやメソッドを共有することができます。このプロトタイプもオブジェクトとして扱われます。

今まで出てきた、クラス(コンストラクタ)、プロトタイプ、インスタンスと、全てオブジェクトであるということが分かります。このようにJavaScriptでは、常に実体化されたオブジェクトであり新しいオブジェクトを

作成するのもクラスではなく、オブジェクトを基にしているということです。

※コンストラクタオブジェクト

クラスの名前を定義します。プロパティを追加すればクラスフィールドの様に扱え、

関数を追加すればクラスメソッドの様に扱えます。

※プロトタイプオブジェクト

JavaScriptにおけるすべてのオブジェクトはプロトタイプという名前のプロパティを公開しています。

プロトタイプオブジェクトのプロパティがクラスすべてのインスタンスに継承されます。

プロトタイプオブジェクトのプロパティの値が関数の場合はクラスのインスタンスメソッドの様に扱えます。

適宜、必要に応じてメンバーを追加することができます。

※インスタンスオブジェクト

クラスのインスタンスは、いわゆるオブジェクトです。

上記で行った様にプロパティを直接定義することができ、ほかのインスタンスとは共有されません。

と、振り返ったところで実際上記で記述したのをプロトタイプを使い置き換えます。

var Person = function(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.toString = function() {
  window.alert(this.name + " : " + this.age + "歳");
};
var hito = new Person("ウェブ夫", "4");
hito.toString(); // 「ウェブ夫 : 4歳」
var hito2 = new Person("ウェブ子", "18");
hito2.toString(); // 「ウェブ子 : 18歳」

プロトタイプオブジェクトのメリット

それでは、プロトタイプオブジェクトを使う利点について挙げていきます。

※ 必要なメモリ量の削減(節約)

メモリの節約ということで内部的に起こっていることを調べていきましょう。

プロトタイプ・オブジェクトの内容はそれぞれのインスタンスから参照されるのですが

オブジェクトのメンバーを参照する場合、以下のような順序で検索されています。

1.インスタンス自身を検索

2.そのプロトタイプを検索

図にすると以下のようになるかと思います。

このようにまず、インスタンス自身のメンバーを検索し、そのプロトタイプのメンバーを検索する順序をとっているのが分かります。

それでは、プロトタイプが提供しているメンバーをインスタンス側で変更するとどうなるでしょうか?

var Person = function(){};
Person.prototype.name = "ウェブ夫";

var hito = new Person();
var hito2 = new Person();
window.alert(hito.name + " : " + hito2.name); //ウェブ夫 : ウェブ夫
hito.name = "ウェブ蔵";
window.alert(hito.name + " : " + hito2.name); //ウェブ蔵 : ウェブ夫

nameはPerson.prototypeで宣言されたプロパティではあるのですが、あるインスタンスに対して 施された変更は、異なるインスタンスには適応されていないのが確認できます。

このことによってプロトタイプの参照は読み込みの場合のみあり、書き込みに対しては「インスタンス自身」に行われていることが分かります。

それでは、上記の処理に関して図でみてみましょう。

初期状態では、インスタンスhito,hito2ともにプロトタイプを参照しています。

インスタンスhitoのhito.nameプロパティに対して新たな値を設定したところで、

インスタンスhitoはプロトタイプオブジェクトのnameプロパティの参照をする必要がなくなります。

インスタンス自身に設定しているnameプロパティを取得するのです。

逆に削除する場合も同様となります。

オペランドとして指定された配列要素やプロパティ/メソッドを削除するためのdelete演算子を用いてプロパティを削除した場合。

var Person = function(){};
Person.prototype.name = "ウェブ夫";

var hito = new Person();
var hito2 = new Person();
hito.name = "ウェブ蔵";
window.alert(hito.name + " : " + hito2.name); //ウェブ蔵 : ウェブ夫

delete hito.name;
delete hito2.name;
window.alert(hito.name + " : " + hito2.name); //ウェブ夫 : ウェブ夫

と、それぞれのインスタンスに対してnameプロパティをdelete演算子で削除したところ、「ウェブ夫:ウェブ夫」と表示致しました。

これは、インスタンスhitoには独自のnameプロパティが存在するので、delete演算子はこの値を削除しております。また、インスタンスhito2には独自のnameプロパティが存在しないので何も行っていないことになります。

この事によって、インスタンス側でメンバーの追加、削除を行っても、プロトタイプ・オブジェクトに対しては影響を及ぼすことが無いことが分かりました。

ちなみに以下の様な記述でプロトタイプオブジェクトのメンバーを削除することも可能です。

delete Person.prototype.name

このように行うとプロトタイプオブジェクトのメンバーを削除することができ、また、undefind(未定義)値を設定することによって擬似的にインスタンス側でプロトタイプオブジェクトが提供するメンバーを無効化することもできます。delete演算子はプロパティそのものを削除するのに対して、undefind(未定義)はプロパティの存在はそのままで値を未定義していることになります。

var Person = function(){};
Person.prototype.name = "ウェブ夫";
Person.prototype.age = "4歳";
var hito = new Person();
hito.name = undefined;
for(key in hito){
  window.alert(key +":" + hito[key]); 
}//name:undefined age:4歳

for inで列挙した場合、nameプロパティが表示されるのが確認できます。

プロトタイプオブジェクトの変更

プロトタイプオブジェクトを使いインスタンスが参照する場合のもう一つ大きな利点があります。

インスタンスを生成した後、プロトタイプオブジェクトにメンバーを追加しても、追加したメンバーを認識してくれるという点です。

どういう事かと言いますと、

var Person = function(){};
Person.prototype.name = "ウェブ夫";
var hito = new Person();
Person.prototype.age = "4歳";
window.alert("年齢:" + hito.age); //年齢:4歳

このように、インスタンスhitoを生成した後、ageを追加し呼び出してもプロパティageは呼び出してくれます。

オブジェクトリテラル定義

オブジェクトリテラルとは、中括弧({})の中に、プロパティ名と値のペアをカンマ(,)で区切って記述する方法となります。

これを行うことによって、Person.prototype.メンバー名 = 〜〜など記述すること無く、簡潔にまた柔軟性を持ったコードの記述が行えます。

var Person = function(name,age){
  this.name = name;
  this.age = age;
};
Person.prototype = {
  talks : function(){
    window.alert(this.name + "です。はじめまして!"); //年齢:4歳
  },
  toString : function(){
window.alert( this.name + ":" + this.age + "歳"); //年齢:4歳
  }
};
var hito = new Person("ウェブ夫","4");
hito.talks(); //ウェブ夫です。はじめまして!
hito.toString(); //ウェブ夫:4歳

プロトタイプ・チェーン

最後にプロトタイプチェーンとなんですが「チェーン」とは訳すると鎖。プロトタイプベースのオブジェクト指向は鎖の様に繋がって継承するとでも覚えていただければ。

var Person = function(){}
Person.prototype = {
  hello : function(){
    window.alert("こんちには!"); 
  }
};
var hito = function(){};
hito.prototype = new Person();
hito.prototype.talks = function(){
  window.alert("最近どう?クルクルってパスタ巻いてる?"); 
};
var webO = new hito();
webO.hello(); //こんにちは!
webO.talks(); //最近どう?クルクルってパスタ巻いてる?

このようにhitoのプロトタイプにクラス(コンスラクタ)Personを格納し、更にそのプロトタイプにメンバーを追加し、また新たにインスタンスwebOを生成、それぞれに設定したhello,talksを呼び出すと問題無く呼び出してくれます。

図にすると以下のようになります。

helloメソッドを実行すると、まず、インスタンスwebO自身のメンバーを検索します。無いので、hitoクラス(コンストラクタ)のプロトタイプを検索します。また存在しないので、先程のプロトタイプの基となるオブジェクトPersonのプロトタイプを検索します。

と、このようにプロトタイプにインスタンスを設定することによってインスタンス間の継承関係を構築することができます。

さらに追加すると、追加した分、階層をさかのぼり最上位のObject.オブジェクト prototypeまでメンバーの検索が行われます。

Comment

Related Article

「二段階認証?」という方も 5分で覚える パスワードレス WebAuthnのまとめ

2019.07.07

上級者向け JavaScript 問題集 「javascript-questions」日本語翻訳担当してます。

2019.06.22

SPAサイトでの認証認可 JWT✗Rails5✗Nuxt.js

2019.03.24

Nuxt.js と auth-module (@nuxtjs/auth)で、JWT(JSON Web Tokens)& OAuth 認証 ログイン

2019.02.21

ブラウザフィンガープリントは、Cookieの代用となるのか? JSライブラリ Fingerprintjs2など。

2019.02.03

Vue.jsでSPA、Vuex使用するなら Nuxt.jsが超絶便利な件。

2018.12.23

nodeのない環境にnodeを導入(HTML5ゲームも提供)し続け、1年数ヶ月経過後、かなり開発環境が整ってきたお話。

2018.09.26

Vue.js + SSR (Universal JavaScript)環境を、「NUXT.js」でサクッと構築する方法。

2017.10.09

フロントエンド開発が捗る JSON Serverについて

2017.07.10

技術書、参考書のしおりには【BOOK DARTS】がオススメ!

2017.07.01

CATEGORY LIST

LATEST NEWS

PythonでGUIアプリ開発「PyQt」を使った感想

Python

2019.07.18

「二段階認証?」という方も 5分で覚える パスワードレス WebAuthnのまとめ

JavaScript

2019.07.07

上級者向け JavaScript 問題集 「javascript-questions」日本語翻訳担当してます。

JavaScript

2019.06.22

Wordpress サイトで、長時間 500 Internal Server Error発生。解決した方法とは。

WordPress

2019.06.18

Ubuntu 18.04 LTSで OpenGL開発

ubuntu

2019.05.20

MacOS Mojave (10.14.4)の Xcode に、過去のMacOSのSDKをインストール

C++

2019.05.19

クローンソフトの「EaseUS Disk Copy」を使って、OSのバックアップを取ってみた。

tool

2019.05.06

Rails5 gemでmysql2が インストールできない

Ruby

2019.05.05

平成最後のお買い物!!「Logicool MX Master 2 Wireless Mouse」と「K780 マルチデバイス BLUETOOTH® キーボード」で快適PC生活。

ubuntu

2019.04.27

Amazon 87時間のビックセール タイムセール祭りで購入したもの

イベント

2019.04.23

Androidに Linuxを簡単にインストールするアプリ「UserLAnd」

Linux

2019.04.16

SPAサイトでの認証認可 JWT✗Rails5✗Nuxt.js

JavaScript

2019.03.24

RANKING

Follow

SPONSOR

現在、掲載募集中です。



Links

About Us

WEBデザイナーの、WEBデザイナーによる、WEBデザイナーの為のサイト。「みんなで書こう!」と仲間を募ってみたが、結局書くのは自分だけとなってしまいました。日々のメモを綴っていきます。

Entry Profile

Graphical FrontEnd Engineer
- Daisuke Takayama

MAD CITY 北九州市で生まれ育つ。20代はバンド活動に明け暮れ、ふと「webデザイナーになりたい。」と思い、デジタルハリウッド福岡校入学。卒業後、数々の賞を受賞、web業界をざわつかせる。
現在、主に、ゲーム制作中心に港区六本木界隈で活動中。

FOLLOW US