はじめに
先日、クロージャ(javascript)の良い解説を読んだ。いままでクロージャの概念はよくわからずもやもやしていたのだが、ようやく理解できた。
ただ、それをアウトプットとして出そうとしても、解説サイトの丸写しになるため、ここではまとめない。『JavaScriptのクロージャは内部でどう機能するのか | プログラミング | POSTD』を是非ご覧あれ。
だが、読み終わってから一つ疑問に浮かんだことがあった。あれ、クロージャとオブジェクトの作成って何が違うのだろうか?
それが本記事の主題である。
オブジェクトの定義
javascriptのオブジェクトの定義、利用は以下のような形をとる。
function Employee(name, salary) { this.name = name; this.salary = salary; this.getBonus = function() { return this.salary * 1.5; } } var employee = new Employee("newWell", 25); console.log(employee.name); // newWellを出力 console.log(employee.salary); // 25を出力 console.log(employee.getBonus()); // 37.5を出力 employee.salary = 30; // salaryを変更 console.log(employee.salary); // 30を出力 console.log(employee.getBonus()); // 45を出力
ちなみにjavascriptにおけるthisに関してもモヤっていたのだが、上記のサイトの記述が参考になった。
関数がnewというプレフィックスで呼ばれる場合、JavaScriptは関数のプロパティprototypeから継承された新しいオブジェクトを割り当てます。そして、新たに割り当てられたオブジェクトはthisとして、関数に渡されます。
javascriptのオブジェクトにはprivate・publicなどというアクセス制限はなく、すべてプロパティとして参照可能であり、変更可能である。なのでjavaでいうgetter,setterは必要ないし、カプセル化したくでも出来ない。
ところで、上記のオブジェクトは以下のように書けたりもする。
function createEmployee(iniName, iniSalary) { var name = iniName; //引数をname、salaryにすれば定義不要だが var salary = iniSalary; //わかりやすさのために一応定義 function getBonus() { return salary * 1.5; } return { name: name, salary:salary, getBonus:getBonus }; } var employee2 = createEmployee("newWell", 25); console.log(employee2.name); //newWellを出力 console.log(employee2.salary); //25を出力 console.log(employee2.getBonus()); //37.5を出力 employee2.salary = 30; //salaryを変更 console.log(employee2.salary); //30を出力 console.log(employee2.getBonus()); //37.5を出力……あれ?
違いといえば、newがないことぐらいだ……と思ったら、よく見たらgetBonus()の結果が異なっている。同じように値を変更したのに何故だろう?
それは、employee2.salaryと、employee2.getBonus()内で参照しているsalaryが別のものであるからだ。
employee2.salaryは、createEmployee()が返したオブジェクトの要素であるが、employee2.getBonus()内で参照しているsalaryはgetBonus()が参照するスコープオブジェクト内に存在する要素なのだ。
そのため、employee2.salaryを変更しても、salaryはgetBonus()が参照するスコープオブジェクト内のsalaryは変更されないため、employee2.salaryを変更してもemployee2.getBonus()の結果は変わらない。
一方、employee.salaryは、new Employee()で作成したオブジェクトの要素であり、employee.getBonus()内で参照しているsalaryも同様のものであるため、employee.salaryを変更するとemployee.getBonus()の結果も変わるのである。
と、いうわけでオブジェクトとクロージャには明確な違いがあるのである(もちろん、クロージャを使ったオブジェクトでは継承が出来ないという一番大きな違いがあるが、それはここでは置いておく)。
クロージャによるImmutableオブジェクトの作成
さて、クロージャの場合、スコープオブジェクト内のオブジェクトを参照し、それは外部からは変更不可だとある。
と、いうことは、いわゆるImmutableオブジェクト、getterのみが存在するオブジェクトが作成出来るのではなかろうか。と、いうわけで定義してみた。
function createImmutableEmployee(name, salary) { function getName() { return name ; } function getSalary() { return salary; } function getBonus() { return salary * 1.5; } return { getName: getName, getSalary:getSalary, getBonus:getBonus }; } var imuEmployee = createImmutableEmployee("newWell", 25); console.log(imuEmployee.getName()); //newWellを出力 console.log(imuEmployee.getSalary()); //25を出力 console.log(imuEmployee.getBonus()); //37.5を出力
name,salaryはスコープオブジェクト内に存在する要素であるため外部からは変更不可である。
また、すでに存在するオブジェクトも以下の形でImmutable化可能である。
function toImmutableEmployee(employee) { var orgEmployee = employee; function getName() { return orgEmployee.name ; } function getSalary() { return orgEmployee.salary; } function getBonus() { return orgEmployee.getBonus(); } return { getName: getName, getSalary:getSalary, getBonus:getBonus }; } var employee = new Employee("newWell", 25); var imuEmployee = toImmutableEmployee(employee); console.log(imuEmployee.getName()); //newWellを出力 console.log(imuEmployee.getSalary()); //25を出力 console.log(imuEmployee.getBonus()); //37.5を出力
もっとも、こちらは、employee.salaryを変更すると、imuEmployee.getSalary()もimuEmployee.getBonus()も結果が変わってしまうので不完全ではある。Immutable化というよりは委譲によるアクセス制限という感じだろうか。
と、ここまで書いておいて何だが、javascriptではそもそもImmutable化は無理である。
imuemployee.getSalary = 30; imuemployee.getSalary = function() { return 10; }
こんな感じに置き換え可能だからだ。もちろんこうやったところでimuEmployee.getBonus()の結果自体は変わらないが、imuEmployeeのオブジェクトとしてはImmutableではない。
まあ、うっかり属性を置き換えたりするのを防止するとう効果はあるかもしれないが、どのみちjavascriptはインタプリタであるため、実行するまでエラーには気づけないから効果は薄そうだ。
と、いうわけでクロージャの活用方法としてImmutable化を考えてみたが、ちょっとイマイチだったというエントリでした。
まあ、クロージャの効能としては、関数オブジェクト内に、一時変数よりもスコープが広く、グローバルよりはスコープが狭い変数が持てる(関数の実行時だけでなく、再実行時まで値を保持可能であるが、外部からは参照・変更できない)という点にあると思うので関数をあれこれ作成していれば、おのずと使う機会は出てくるのだろう。