なめこ備忘録

プログラミングに関する備忘録や経験したこと,考えたことなど好き勝手書きます.

[tex: ]

JavaScriptでプロパティを実装してみる

こんにちは,なめこです.

2,3年ほど前にC#のプロパティを乱用し,それをJavaScriptでも同様に実装しようとしていた時期がありました.(発想が既におかしいですけど先人がいました)
今回はそんなJavaScriptオブジェクト指向をする時に必要になるであろうプロパティの実装までを備忘録として書き留めておきます.

オブジェクト指向とは

そもそものお話から書いていこうと思います.
簡単に言うとオブジェクト単位で機能を実装して,それらの相互作用でシステムを作るような考え方です.
はい,まだちょっとわかりにくいですね.

例としてデスクトップパソコンを考えます.
キーボード,マウス,ディスプレイ,パソコン本体(もうちょっと分けられそう)の4つくらいのモノに分けて考えます.
これらのモノがオブジェクトです.マウスとかディスプレイっていうオブジェクトです.
どのオブジェクトもお互いが一体どういう原理でどう動いているのかなんて知りません.
でもデータ通信の規格が決まっていて,どんな機能を持っているのかは分かっているので,何の問題もなく動作するわけです.
それぞれが固有の機能(キーボードならキーを押すとか)を持っていて,その結果のデータのやり取りは何かしらの通信によって行われていますよね.
どんな機能でどんなデータが飛んできてってのが分かっていれば中身を知らなくていいわけです.
自分の使っているものの全原理を完璧に理解した上で使っている人なんていません.
ここを押したらこうなるっていう機能とその結果だけ分かっていればいいんです.

オブジェクト指向では各オブジェクトの機能(とデータのやり取りの形式)を定めて中身はブラックボックスとして考えます.

小さくて簡単なプログラムを作る時には特に必要ないと思いますが,これが大規模で複雑なものになるとすごくありがたいですよね.
君はキーボード作って,君はマウスねって委託できるんですから.
ついでに何かエラーが生じてもオブジェクトごとにチェックできるんですから原因の発見もすんなりいきそうです.

カプセル化

オブジェクト指向って実装の隠蔽(カプセル化)って考えがあります.
これも簡単に言うと,外から使える機能だけ公開して,他は見えないようにしましょうってことです.
キーボードのキーを押すのはいいけど,解体して中の回路とかをいじられると困るわけです.なのでそれをできないようにしましょうねってことです.

そのために,オブジェクト内の各変数やメソッド(関数)にアクセスレベル(アクセスできる範囲の制限)を設定します.
よくみかけるのはprivateとpublicですね.
privateはそのオブジェクト自身からしか操作(参照も)できません.逆にpublicはどこからでも操作できます.
キーボードの中身(主に変数)は外枠で隠して(private),外側のキーは使える機能(メソッド)として公開(public)する感じです.

プロパティとは

ようやく今回のメイン,プロパティです.
プロパティはJavaC++にはない機能で外側からは変数,内側からはメソッドのように振舞います.

順を追って説明しましょう.
オブジェクト指向では基本的に変数は全部隠して,外側から操作するときは公開してあるメソッドから行います.
こんな感じです.

private int data; // 外からは操作できない
public int GetInt(){return data;} // dataを取得
public void SetInt(int d){this.data = d;} // dataに値をセット

どうせゲットもセットもできるようにするなら変数を公開すればいいのにって思うかもしれませんが,メソッドからの操作が基本ですから一部だけ変数だと使いづらくなるんです.
この実装方法は,例えば何かの処理中の内部データを保存している変数があったとして,途中経過を確認したいからデータを観れるようにしたいけど,操作できてしまうのは困るってときに重宝します.

でもこれだと変数の数だけGet〜やSet〜のメソッドが出てきかねないんです.
コードが無駄に長くなりますし,読みづらいです.
また,メソッドからの操作が基本と言いましたが,使用者視点だと,Get,Setなんかより普通に変数に代入したいわけです.

そこで,外からは変数,内からはメソッドのように使えるプロパティが登場します.
このプロパティ,SetとGetでアクセスレベルを変えられるというのがとても便利です.

C#での実装

C#での実装法をざっくり書いておきます.

private int d;
public int data{
    get{ return d;}
    private set{ d = value;}
}

こんな記述で,外からは見ることしかできないdata変数のようなものができます.
詳細は省きますが,書き方はほぼ関数なので途中に他の処理を挟んでも問題なく機能します.

JavaScriptオブジェクト指向をしてみる

それでは,まずはオブジェクト指向プログラミングをJavaScriptでやってみましょう.
もともとJavaScriptオブジェクト指向スクリプト言語らしいので,普通にオブジェクト指向の書き方ができます.
ただしC#Javaとはちょっと違った記法になります.

オブジェクトの生成

まずはオブジェクトの生成方法です.
この時点で複数あります...というか,実はJavaScript,ほとんどがオブジェクトです.
配列だと思っていたもの,連想配列だと思っていたもの,関数だと思っていたもの,純粋な変数以外のもの全てがオブジェクトです.
ですので,配列として宣言したものに唐突に新しい変数名とデータを入れてあげても当たり前のように動きます.

var a = [1, 2, 3];
a.data = 'data'; // aというオブジェクトに新しくdataという変数を作った上で'data'を代入

console.log(a.data); // data
console.log(a['data']); // data
console.log(a); // [ 1, 2, 3, data: 'data' ] 実際の中身はこんな感じ

a.push(10); // 普通の配列としての操作もできる
console.log(a[3]); // 10

というわけで,だいたい何でもオブジェクト扱いですが,よく使われるオブジェクトの宣言方法を示します.
thisだとかコンストラクタだとかの細かい説明は省きます.

空のオブジェクトの宣言方法

var obj = new Object();
obj.data = 10;
obj.func = function(){return this.data;};
console.log(obj.data); // 10
console.log(obj.func()); // 10

連想配列のような宣言方法

var obj = {
    data : 'd',
    i : 5,
    func : function(n){
        return this.i*n;
    }
};
console.log(obj.data); // d
console.log(obj.i); // 5
console.log(obj.func(4)); // 20

関数のような宣言方法

コンストラクタ関数を定義することでオブジェクトを生成することができます.

function Obj(data, i, func){
    this.data = data;
    this.i = i;
    this.func = func;
}
var obj = new Obj('data', 10, function(){return this.data;});
console.log(obj.func()); // data

プロパティの実装

さてようやくJavaScriptでのプロパティの実装です.setやgetを設定できるのは連想配列の時のみのようです.
以下のようにするとsetやgetを設定できますが,JavaScriptではアクセスレベルの指定はできません.

var obj = {
    _data : 'd',
    _i : 5,
    get data(){return _data;},
    get i(){return _i;},
    set i(x){_i = i;}
};
console.log(obj.data); // d
obj.i = 20;
console.log(obj.i); // 20

getやsetの後に関数のように定義することでプロパティが実装できます.
しかし,この実装方法だと

obj._i = 5;
obj._data = 'null';

とすることでアクセスできてしまいます.
これではアクセサの意味があまりありません.

そこで,JavaScriptの特殊な仕様を利用して次のようにしてオブジェクトを生成します.

var obj = (function(){
    var data = 'obj_data';
    var i = 0;
    return {
        get data(){return data;},
        get i(){return i;},
        set i(x){i = x;}
    };
})();

はい,一気にわけのわからなさが増しましたね.
でもこうすることで,以下のようにsetを設定していないdataにはデータのセットができないようになります.

obj.data = 'null'; // 代入されないがエラーも出ない
console.log(obj.data); // obj_data(内部で入れられたデータが入っている)
obj.i = 10;
console.log(obj.i); // 10

JavaScriptではfunctionのオブジェクトを変数に入れたとき,関数内のローカル変数は最初に宣言されたあと保持されるようです.
なのでobjに代入する際の無名関数の内部で宣言した data や i が消えずに残ります.

また,関数内で新たに宣言したローカル変数に関しては外側からはアクセスできないので実質的にprivateで宣言したものと同じになります.
正確にはobjに実際に入れられているのは,即時関数の返り値である連想配列なので,ローカル変数へのアクセスの手段がそもそもないですね.
なぜこれでローカル変数の値が保持され続けるのか不思議ですが,細かい話をするとわけがわからなくなるのでやめておきましょう.

まとめ

というわけで,JavaScriptにちゃんと機能として実装されているという感じではないですが,プロパティの実装ができました. JavaScriptオブジェクト指向プログラミングをする際にプロパティをどうしても使いたい!となったときは,

  • 隠蔽したいものはローカル変数
  • 公開するものは返り値の連想配列の中

とすれば実装できます.
ただし可読性はどんどん下がっていくので気をつけましょう.

JavaScriptとNode.jsに関して

はじめましての後の最初の記事はプログラミングに関するものです.

タイトルの通りNode.jsです.ええ,JavaScriptですとも.
プログラミングしようと思った時に,自分の中で真っ先に出てくる言語ってありますよね.
私はそれが JavaScript なわけです.

ということで,今後の記事の内容がJavaScriptに偏りそうなので,先に前提となる部分を書き留めておこうかと思います.
JavaScriptの沼が深いのと,私の知識がそんなにないので,違ってたりするかもしれませんが悪しからず.

JavaScriptとは

ご存知の通り,プログラミング言語です.Javaとは全く違います
間違えると怒る人もいるので気をつけましょう.
実行環境は主にWebブラウザとなっており,Webページの動的処理の部分をよく担当するような,そんな言語です.

略称・拡張子は ' js ' です.
理由は言いませんが,略称をむやみに言い過ぎるのはやめましょう.

JavaScriptの特徴

すごくざっくりと言ってしまうと,結構何でもありな言語です.
以下に特徴をいくつか並べていきますが,後半になる程特殊なものが増えると思います.

変数宣言時の型

宣言時の型の明示がありません.全部 var です.
最初に入れたもので勝手に判断されます.

var a = 1; // たぶんint型
var str = 'string'; // たぶんstring(文字列)型

グローバル変数

コード書いてる途中にグローバルな変数が欲しいなと思った時,最初の方の行に戻って宣言...の必要もないです.
varをつけずにその場で新しい変数名書いて代入でも何でもしましょう.勝手にグローバルになります.

{ // 何かの関数とかの中
    var a = 1; // ローカル
    b = 1; // グローバル
}
console.log(a); // undefined
console.log(b); // 1

関数の基本的な宣言

関数宣言の基本はfunction

function test(n){
    return n*5;
}
console.log(test(3)); // 15

配列操作

これはそこまで珍しいものではないです.
ただ普通にC言語とかを使うよりは簡単に色々できます.
最近よく使うのは要素の頭と末尾に対する操作です.

var array = []; // 空の配列の宣言
var array1 = [1, 2, 3, 4]; // 4つの要素を持った配列宣言
array1.pop(); // 配列の末尾を削除します -> [1, 2, 3]
array1.push(5); // 配列の末尾に追加します -> [1, 2, 3, 5]
array1.shift(); // 配列の先頭を削除します -> [2, 3, 5]
array1.unshift(6); // 配列の先頭に追加します -> [6, 2, 3, 5]

連想配列も使えたりしますが割愛します.

関数のちょっと不思議な操作

JavaScriptさんの関数はいろいろ特殊な操作ができます.
よく使うものを3つだけ抜粋して載せておきます.
この記述方法面白い!とかあったら別記事に載せると思います.

変数として扱う

関数を変数のように代入できます.
そうした場合,変数は関数の入れ物となります.

function test(n){return n*5;}
var a = test;
console.log(a(3)); // 15

var b = function(n){return 5;} // なんならこれでもいい

関数をその場で使い倒す

即時関数と呼ばれるものです.
作った関数をその場で一度だけ実行します.

(function(){
    console.log('test');
})();
// test

コールバック関数をその場で記述

コールバック関数を宣言するまでもなく,その場で引数として入れることができます.

function func(n, callback){
    var a = callback(n);
}
console.log(func(5, function(n){
    return n*n;
}));
// 25

文字列をJavaScriptのコードとして実行

私はほとんど使いませんが一番なんでもできそうって感じのものです.
eval関数を使います.文字列がその場(そのスコープ内かつ呼び出し元の権限)で実行されるため細心の注意を払って使いましょう.

var x = 5;
var y = 10;
var z = 3;
eval('z = x + y;');
console.log(z); // 15

セミコロンの有無

実はJavaScriptさん,セミコロンがあってもなくても動きます.
私は最初つけるものだと思ってたので当たり前のようにつけてますが,うっかり忘れてしまってもエラーも警告も出ずに普通に実行してくれます.
JavaScriptでのセミコロンの存在意義ってなんなんですかね...

Node.jsとは

ようやく本題のNode.jsです.
Node.jsはサーバーサイドJavaScript環境です.
もともとWebブラウザ上,つまりクライアントサイドで動いてたJavaScriptをサーバーサイドで動くようにするんです.
私もソケット通信とかしようとした時に存在を知ったんですが,Node.jsを使うとほとんどJavaScriptだけで書けちゃったりします.

また,このNode.js,コマンドライン上で動かせます.
Python感覚でほいほいJavaScriptのコードが実行できます.
そのおかげか調べてみると,Webだとか通信だとかと一切関係ない使い方してる人がいっぱいいます.私もそのうちの一人です.

さらにモジュールも豊富です.
オープンソースなのでいろんなモジュールがあるんですが,このモジュールの幅広さがすごいです. この言語のこのモジュールいいよなーって思ったら,[node (モジュール名)]で検索かけてみましょう.結構それっぽいモジュールがヒットします.
また,この言語のこの機能(構文)いいよなーってなったときはだいたいJavaScriptの記述法で代用できます.

まとめ

もともと結構何でもありだったJavaScriptが,Node.jsを使うことでさらに何でもありになります.
フロントエンド,バックエンド共にJavaScriptにすればデータの受け渡しもスムーズにいきそうですね.
ただ,何でもありすぎて人のコードを読む難易度は高い気がします.知らない記法とかいっぱい出てきますし.

私はそんなJavaScriptの特徴的過ぎる記法が好きなので,よくJavaScriptで何でもしようとします.
共感してもらえたことはないです.

ぶろぐ開設しました.

はじめまして.なめこです.
Twitterとか見てると知り合いとかがブログやってるのをちょいちょい見るので,流行に乗って(遅い)ブログを始めてみます.

基本的に研究だとか趣味的な意味でのプログラミングだとかの備忘録の予定です.
興に乗ってきたらもうちょっと記事の内容の範囲を広げるかもしれません.

いろいろと初めてなもので読みづらいことも多いかと思いますが,暇つぶし程度に付き合ってくれると幸いです.

私情全開でブログですが,誰かのためになることがあると尚更いいですね.