【JavaScript】 ゲッター・セッターとは?必要性はあるのか?
更新日:2021/02/03
JavaScriptにはゲッターとセッターという機能があります。
正直知らなくてもプログラムを作成できますが、他者のコードを見るとき困るので調べてみました。
ゲッター(Getter)とセッター(Setter)とは
ゲッターとセッターとはアクセサープロパティに関連付けられた関数です。
アクセサープロパティは、プロパティ値のセットや取得アルゴリズムをカスタイマイズすることを目的としています。
プロパティの種類
まずは簡単に、プロパティの種類についてお伝えします。
プロパティには、データプロパティとアクセサープロパティの2種類があります。
データプロパティ
通常のプロパティです。
代入された値をそのまま保存し、保存された値をそのまま返します。
データプロパティの定義
obj.prop = 100;
obj.func = function(){};
JavaScriptでプロパティ値として有効なのは、プリミティブまたはオブジェクトです。
関数の実体は関数オブジェクトなので、メソッドもデータプロパティです。
アクセサープロパティ
アクセサープロパティは、代入された値を任意の方法および形式で保存するセッター関数と、保存された値または任意の値を任意の形式で返すゲッター関数が定義されたプロパティです。
次のコードは、単純なアクセサープロパティの定義例です。
アクセサープロパティの定義例
let value = 0;
const obj = {
get prop(){ // ゲッター関数:値を得る
return value * 2;
},
set prop( param ){ // セッター関数:値をセット
value = param / 3;
},
};
上のコードを実行すると、obj内にpropという名前のアクセサープロパティが作成されます。
その後、ゲッター関数とセッター関数がpropに結びつけられます。
ゲッター関数とセッター関数は、値をそのままセットしたり返したりする必要はありません。
ゲッター関数とセッター関数
ゲッター関数とセッター関数は、一般的には外部変数への入出力をおこなう処理をおこないます。
ゲッター関数
ゲッター関数は、オブジェクトリテラル内で次の形式で記述します。
get プロパティ名(){ ・・・関数コード return 戻り値; }
ゲッター関数は引数がありません。
関数内のコードは、外部に確保してある変数をそのまま、あるいは一つまたは複数の変数を加工して戻り値として返します。
変数を使用せず、固定値を返すこともできます。
セッター関数
セッター関数も同様に、オブジェクトリテラル内で次の形式で記述します。
set プロパティ名( value ){ ・・・関数コード }
セッター関数は引数を一つだけ受け取ります。
関数コードは、受け取った引数を外部変数に代入する処理を記述するのが一般的です。
引数の型チェックをおこなうケースもあります。
アクセサープロパティからデータを取得する
アクセサ―プロパティは、代入の右辺などで値が取得されるとき、ゲッター関数が呼び出されます。
アクセサ―プロパティからデータを取得する
const data = obj.prop;
内部的に関数が呼び出されますが、後ろに()を記述しません。
記述するとエラーになります。
const data = obj.prop(); // Uncaught TypeError: obj.prop is not a function
次のように関数を返した場合、
get prop(){ return function(){}; }
()を付けると返された関数が実行されます。
const data = obj.prop();
アクセサープロパティでデータをセットする
アクセサ―プロパティは、代入先として指定されるとき、代入値を引数としたセッター関数が呼び出されます。
次のように100を代入すると、
obj.prop = 100;
引数の値を100として、セッター関数が呼び出されます。
set prop( value ){ // value = 100 ・・・関数コード }
関数が呼び出されますが、データの取得と同様に、()を付けるとエラーになります。
obj.prop() = 100; // Uncaught TypeError: obj.prop is not a function
格納先変数の取り扱い
現実的な使用方法としては、セッター/ゲッターで値の格納先として扱われる変数は、クロージャの特徴などを利用して隠蔽するのが望ましいです。
ゲッター/セッターの現実的な使用例
function getObj(){
let _name = ""; // 値保存用変数
return {
get name(){ // ゲッター
return _name;
},
set name( param ){ // セッター
if( typeof (param) === "string" || param instanceof String){ // 文字列かどうかの検査
_name = param;
}
},
}
};
const obj = getObj();
obj.name = "Taro"; // セッターが呼ばれる
console.log( obj.name ); // ゲッターが呼ばれる
重要な変数を関連するコード以外からアクセスできないようにすることを、カプセル化と呼びます。
カプセル化については、次のページを読んでみてください。
■【JavaScript】 JSにおけるカプセル化手法
アクセサープロパティの利点
わざわざアクセサープロパティを用意しなくても、setName()やgetName()などのメソッドでもいいのでは、と思う人も多いのではないでしょうか。
アクセサープロパティは引数の数が決まっているので、柔軟性に欠ける面があります。
引数で取得する値の条件指定をしたいときなどは、アクセサープロパティでは実現できません。
アクセサープロパティの利点は、上書きができないことです。
データプロパティは代入操作により、値が変更されます。
メソッドだったが、いつのまにか数値プロパティに変わっていたという想定外の結果になる可能性もあります。
obj.prop = function(){}; // メソッド
obj.prop = 100; // 数値プロパティ
しかしアクセサープロパティは、代入値を引数とした関数が呼ばれるので、プロパティそのものを別のものに変更されることがありません。
このような特徴を考慮しながら、アクセサープロパティにするかどうかを決定していく必要があります。
Object.freezeメソッドやObject.definepropertyメソッドを使用することで、データプロパティを上書き不可にすることも可能です。
ゲッター/セッターはセットでなくてもよい
ゲッター/セッターをセットで用意する必要はありません。
片方だけでもOKです。
function getObj( name ){
let _name = name; // 値保存用変数
return {
get name(){ // ゲッター
return _name;
},
set newName( param ){ // セッター
_name = param;
},
}
};
const obj = getObj( "hanako" );
obj.name = "Taro"; // セッターがないのでエラー
// TypeError: setting getter-only property "name"
上の例はセッターが定義されていないので、nameプロパティは取得専用となります。
その逆で、newNameプロパティはセッターのみのため、設定専用プロパティとなります。
コンストラクターとプロタイプでゲッター/セッターを定義する
ゲッター/セッターはプロトタイプでも定義できます。
コンストラクターでthisのプロパティを定義して、プロタイプでthisを操作するゲッター/セッターを記述すればOKです。
const obj = function( name ){
this._name = name;
};
obj.prototype={
get name(){ // ゲッター
return this._name;
},
set name( param ){ // セッター
if( typeof (param) === "string" || param instanceof String){
this._name = param;
}
},
};
const a = new obj( "Taro" );
a.name = "Hanako";
console.log( a.name );
コンストラクターとプロタイプについては、次のページを参考にしてみてください。
参考:【JavaScript】 コンストラクターとは?関数とは違うのか?
参考:【JavaScript】 プロトタイプとは?prototypeプロパティはプロトタイプではない件について
また、プロタイプメソッドからオブジェクト内にカプセル化された変数にアクセスしたいケースがあります。
こちらについては、次のページを読んでみてください。
参考:【JavaScript】 JSにおけるprivate変数と定義のひな型パターン
その他の定義の仕方
アクセサープロパティは、データプロパティのように . (ドット)表記で定義できません。
obj.prop1 = function ( ) { }; // データプロパティの追加
obj.set prop2 = function ( v ) { }; // アクセサープロパティの追加:エラーになる
しかし、これまで紹介してきたオブジェクトリテラルでの定義をおこなうと、現在の定義を上書きしてしまいます。
obj.prop1 = function ( ) { }; // データプロパティの追加
// objの内容を新しいオブジェクトで置き換え
// →prop1は消える
obj = {
get name(){ },
set name( param ){},
};
JavaScriptがブラウザ上で動作している場合、__defineSetter__または__defineGetter__で定義できます。
// セッターの追加
obj.prototype.__defineSetter__(
"prop2", // プロパティ名
function( v ){ this._v = v; } // 呼び出す関数
);
// ゲッターの追加
obj.prototype.__defineGetter__(
"prop2", // プロパティ名
function( ){ return this._v; } // 呼び出す関数
);
ただし現在は非推奨となっているので、次のObject.definePropertyを使用します。
Object.defineProperty(
obj.prototype , // 追加先のオブジェクト
"prop2" , // プロパティ名
{
set : function( v ){ value = v; },
get : function( ){ return value; },
configurable:true,
}
);
推奨されている方法は、次のページを読んでみてください。
■【JavaScript】 ゲッター/セッター関数を後から変更する方法
更新日:2021/02/03
関連記事
スポンサーリンク
記事の内容について
こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。