[PR]
×[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
なにもやってないわけじゃない
ImageDancer、実際に自分でスクリプトを組んでみると思いのほか問題が多く、難航。気付いたことをいくつか書いておく。
メソッド(インスタンス着きの関数)の受け渡しはメソッド内のthisを拘束しなければならない。これについて他のサイトを見るといろいろ書いてあるようだが(thisがwindowを参照する、みたいな)、要するに受け渡しされる情報は関数の定義部分のみ、ということか。
メソッドの受け渡しを必要とされる処理はその処理のオブジェクト化に問題があると言える、かもしれない。メソッドの所属するオブジェクトごと伝達する仕組みに書き換えられないか検討してみる。場合によってはオブジェクトの基本設計から考え直すことも。
ただ元からJavaScriptに組み込まれているメソッドに対しては関数を渡さざるを得ないわけで。その場合はライブラリに大抵あるbindメソッドよろしく関数をapplyしなければならないようだ。
applyの機能を、コンストラクタ関数から継承関数を生成する、みたいな説明しているサイトがあるが、どうやら関数オブジェクト一般の定義部分を引っ張ってくるのがその基本的な仕様のようだ。
硬派なJavaScript使いを標榜する私(謎)としてはクラス指向的な機能はできるだけ遠くに置いておきたいところではあるのだけど。newとかprototypeとかも、本当に必要かどうかよく検討するべきかと。値は生のオブジェクトで、メソッドは単純に関数オブジェクトへの参照で、としたほうがすっきりする場合も少なくないように思う。
とか考えつつ。しかし支離滅裂。
あんまし進んでいない開発状況は、こちら。
PR
JavaScriptで三角形を描く
スタイルシートのボーダー要素を利用して三角形を描く方法、というのを試してみた。ネタ元はReal-Time 3D in Javascript。
なんせ元が英文なので詳しい仕組みはよくわからんのだが、要するに太さの異なるsolidなborder要素の境界にできる斜線を利用する、ということらしい。
で、right_angledTriangleという関数を作ってみた。矩形のdiv要素を斜めに割って直角三角形に見せる、というもの。
引数は一つのオブジェクトで指定。targetに直角三角形を表示するエレメントを、leftとtopに直角部分の位置をピクセル単位の数値で、baseとheightに底辺と高さをピクセル単位で(マイナス指定可)、colorに位置指定した側の直角三角形の色を、backgroundにその反対側の色を、それぞれ指定する。
ただしIE6以下では色指定にtransparentを指定できない。ああ、これは致命的。またIEか。
ネタ元にさせていただいたサイトでは何やら上手いことやっているみたいだが、そのカラクリがよくわからん。どうやら色の置き換えらしきことをやっているらしいのだけど、この件はとりあえず保留。
ついでにワイプアウト処理の関数をやっつけででっちあげてみる。リアルタイム処理のテストと併せてサンプルを作ってみた。
こんな感じ。
まあワイプアウトとはいっても色指定にtransparentを指定できないのであまり利用価値はない。
ソースは、うーん、何度見てもヒドいもんだ。汎用性などカケラもナシ。
計画性もなくただ思いつくままに行き当たりばったりでコードを重ねていくとこういうことになる、という悪い見本市。文学的プログラミングの真骨頂。冗長とはまさにこのこと。
とりあえずこのサンプルは、記念碑的意味合いを込めて、保存しておくことにする。
ImageDancer
ノーガキばかりというは個人的に少々いただけないので、ちょっくらスクリプトを作ってみます。HTML要素を時間軸に沿って動かすJavaScript関数群、題して『ImageDancer』。画像に特化した処理ではないので「看板に偽りアリ」ですが、ゴロがいいので。 現在のJavaScriptの仕様での画像の変形はちと苦しいので、動きは平面上での移動のみに限定します。 とりあえず座標計算の基となる部分のみ半完成。サンプルを上げておきます。まだこの段階では何がやりたいのかよくわからないでしょうが、今後おいおい機能を追加していく予定であります。
基本的な方向性は、劣化某FFD((c)株式会社モノクローマ)。ただし上にも書いた通り、画像の傾けや変形(拡大、縮小含む)は今回はパス。
肝となるのは、複数の要素のそれぞれ異なる動きを一つの時間軸に沿って表示する、という機能。字面ではニュアンスを伝えにくいのですが、近いうちにサンプルを上げます。
テストはWin版IE6、Firefox2、Opera9。クロスブラウザに大掛かりな仕掛けが必要な機能は使わない方針。独自路線を爆走するブラウザがいるのでかなり手を狭められるのですが、これは致し方なし。
重くなるのは勘弁なので、ライブラリは使いません。(機能を覚えるのがメンド臭い、というのも実は大きい)
ま、例によってボチボチと。
値と演算子・2
オレのためのJavaScript入門、その6
演算子の続き。論理演算子から。
&&演算子と||演算子。条件式の中で「かつ」と「または」を表現する、というのが一般的な使われ方。その仕組み単純である。簡単に説明しておく。
&&演算子。左辺が真と評価される値だった場合は右辺を評価する。作り出される値は後に評価された値。
||演算子。左辺が偽と評価される値だった場合は右辺を評価する。作り出される値は後に評価された値。
偽と評価される値は、object種ではnull、string種では''(空文字列)、number種では0とNaN、boolean種についてはいわずもがな。それ以外の値は真と評価される。undefined種は偽、function種は全て真と判断されるようだ。(抜けがあるかも。要するにBoolean関数でboolean種の値に変換したときにfalseとされる値が偽だ)
注意点は2つ。論理演算子から作り出される値はboolean種ではなく評価された値そのものであり、そして左辺の値によっては右辺が評価されない場合があること。
後者の特徴から、条件文にあたる処理を一つの式の中に収めることができる。
a < 1 && (a = 1) || a > 9 && (a = 9)
こんな感じ。さらに,(カンマ)演算子を組み合わせることで複雑な処理を一つの式に収めることもできるだろう。
論理演算子がこのような使われれ方をされることは、あまりない。パッと見何をやっているのかサッパリわからないからだろう。しかし別に禁止されているわけではない。使いたければ使えばいいと思う。
条件演算子を使うともう少しわかりやすく書けるが、こちらも使われることは少ないようだ。
なお論理演算子のこの特徴はJavaScriptに限った特殊な仕様ではない。他の言語でも大体似たようなものだ。
次は.(ドット)演算子。これについては以前に少し触れた。この演算子を囲む2つの値は、左辺のobject種の値が参照するオブジェクトの持つ右辺のプロパティ名のプロパティ、という一つの値として解釈される。
.(ドット)演算子が左辺に要求する値はobject種、つまりオブジェクトへの参照である。function種でも問題ない。
ちょっとここでfunction種についてこれまでの記述を改めさせてもらう。ここでいきなりこんな話になるのは以前のエントリーを書き直すのがメンド臭いからだ。まあ大筋は変わらない。
以前のエントリーでfunction種は「定義への参照」と書いたが、function種は「オブジェクトへの参照」である。ただfunction種が参照するオブジェクトにはobject種が参照するオブジェクトとは異なる特徴がある。その特徴とは「定義」というプロパティを持っている、ということである。この定義というプロパティには直接アクセスすることはできない。function種を値に持つプロパティを括弧付きでアクセスしたときにその定義を実行することができる。ちなみに定義とは式の集まりである。
この「定義」を持つオブジェクトを今後「Functionオブジェクト」と呼ぶ('f'unctionではなく'F'unction)。
と、大体こんな感じ。Functionオブジェクトについてはまたいずれ詳しく扱う。
そんなわけで、function種でも問題ないのだ。
.(ドット)演算子についての話を続ける。
この演算子の左辺にはstring種、number種、boolean種の値を置くこともできる。以前のエントリーではobject種以外は置けない、みたいなことを書いたが、訂正はしない。メンド臭いからだ。そしてこの場合はもはやおなじみの「種の変換」が行われる。ただし、ここで行われる種の変換はちょっと複雑だ。
まず初めに左辺の値の種に応じた新しいオブジェクトが作られる。これらを、string種はStringオブジェクト、number種はNumberオブジェクト、boolean種はBooleanオブジェクトと呼ぶ。これらのオブジェクトは上に書いたFunctionオブジェクトと似ている。これらのオブジェクトは「定義」の代わりに「値」という直接アクセスできないプロパティを持っている。
そしてそのオブジェクトの値プロパティに値が代入される。
その上でこの新しく作られたオブジェクトの持つ右辺のプロパティ名のプロパティ、という一つの値として解釈される。
かなり説明不足だが、とりあえずここではこれ以上の深入りはせずにおく。ここで説明するためにはまずオブジェクトというものについての説明から始めなければならない。これまでのオブジェクトについての説明はあくまで概念的なものだ。オブジェクトについては、上に挙げたそれぞれのオブジェクトについての説明と併せて機会を改めて考えてみることにする。
最後にnew演算子。これも以前に少し触れた。オブジェクトを生成する魔法の呪文、というやつだ。
右辺に括弧付きのfunction種の値を要求する。この演算子から作り出される値は新しく生成されたオブジェクトを参照するobject種の値である。
new演算子の右辺に置かれる関数はこのとき生成されるオブジェクトの親とも呼べるもので、通常の関数とは呼び出され方も機能も異なる。この関数のことをコンストラクタ関数と呼んだりする。他の関数と区別する意味も込めて頭を大文字で始めるのが一般的だ。
このあたりの説明をするにはオブジェクトについてと関数についてのさらに詳しい説明が必要となる。とりあえずここでは、new演算子はオブジェクトを新しく生成し、そのオブジェクトを参照するobject種の値を作り出す、という説明に留めておく。
.(ドット)演算子とnew演算子はJavaScriptでオブジェクトを扱う要となる重要な演算子である。実質的にはJavaScriptにおいてオブジェクトに関わりのある演算子はこの2つしかない。これらを使いこなすためにはまずオブジェクトというものを正しく知っておかなければならない。
次回からはJavaScriptにおけるオブジェクトというものをより具体的に見ていくことにする。
値と演算子
オレのためのJavaScript入門、その5
JavaScriptでは値は式の中で演算子を使って扱う。オブジェクトを直接扱うことはできない。式の中でプロパティが変更されることによって、結果的にそのプロパティを乗せたテーブル(=オブジェクト)の特徴が変わる。と、これが前回と前々回の内容。今回は値と演算子についてもう少し細かく調べてみる。
とはいっても網羅的にやっていてはキリがないので、混乱しかねない部分だけを簡単に触れてみる。これは以前に「臨機応変」と揶揄した部分でもある。
演算子は1つ以上の値を要求する。そしてその要求に対して提供された値から1つの値を新しく作り出す。これが基本。
まずはじめに、値にはそれぞれの種類が決まっておりそこにあいまいさの入り込む余地はない、ということを確認しておきたい。JavaScriptは「型に対して柔軟」とか言われたりするが、実際のところは演算子が複数の種の値に対応する、という臨機応変さを持っているだけである。このことが「型に対して柔軟」という言葉であいまいにされているがために、しばしば混乱の原因になっているように思われる。
この「臨機応変」な対応は、正直なところ(現時点では)「柔軟」とは言いがたい。演算子が全てのプログラマに対して自然な振る舞いをすることができればそれは「柔軟」と言えるかもしれないが、これは非常に困難である。それを行うためには式に求められる文脈を読み取らなければならない。しかし現実は演算子は自らが要求した値の種類だけしか把握していない。式で扱われる値の種類はプログラマが正しく管理する必要がある。
混乱の原因に加えるなら、見た目上同じプロパティという器に対してどの種類の値でも代入することができる、という点も挙げられる。プロパティに代入されている値の種類も、やはりプログラマが正しく管理しなければならないだろう。これについてはプロパティの命名規則である程度の対応が可能かもしれない。(例えばobject種を保存するプロパティには~objとする、とか? しかしこうすることはやっとの思いで獲得した「自由」に対する冒涜かもしれない) これについては現実にすでに半ば規則化している部分もある。(コンストラクタとして使われるfunction種のプロパティにはUpperCamelCase、とか)
まずはじめに数値を扱う演算子を考えてみる。これらは一般的には算術演算子と呼ばれる。ビット演算子もこの範疇に入るだろう。
この演算子が要求する値は、当然のことながら基本的にnumber種。提供された値がそれ以外の種類だったときは演算子によって「種の変換」が行われる。値の種類に応じて演算子が「臨機応変」に立ち回る、というわけだ。
string種とboolean種は数値として扱われる。
数値化できないstring種はNaNという特殊な値として扱われる。NaNとは数値でないことを表すnumber種の値である。NaNはNaNを含むいかなる値とも一致せず、また、大きくも小さくもない、という特徴を持つ。(「そんなの数値じゃないじゃん」と突っ込まれそうだが、要するにそういうことなのだ) NaNを含む演算の結果はNaNになる。
boolean種においてはtrueは1、falseは0として扱われる。
この演算子の生み出す値は全てnumber種である。(この中にはNaNも含まれる。なお、値がNaNかどうか調べるにはisNaN関数を使う)
+演算子は数値を扱う加算演算子であるが、同時に文字列結合演算子でもある。提供された値にstring種が含まれていた場合は文字列結合演算子として機能する。このときに生み出される値は、string種である。+演算子のこの対応はとても「柔軟」とは言い難いが、まあ混乱することも少ないだろう。(ちなみに加算演算子と文字列結合演算子が別の言語もあり、個人的にはこの仕様の方が好きだ) 文字列結合演算子が要求する値がboolean種だった場合は'true'または'false'というstring種に変換される。
さて、これらの演算子に提供された値がobject種やfunction種だったらどうなるか。これらは参照値であり、JavaScriptにおいて上に挙げた演算子はこれらの値を演算することはできない。しかしエラーにはならない。
この場合はこれらの参照先のオブジェクト(以前にfunction種の参照先は「定義」と書いたが、これも実はオブジェクト。近いうちに詳しく解説する)の持つプロパティtoString関数の戻り値に変換される。この値はその名が示す通りstring種である。例外はNumberオブジェクトで、このオブジェクトへの参照を持つobject種の値に限りnumber種に変換される。(ただしこの場合でもtoString関数の戻り値はstring種)
そして演算によって作り出される値はstring種、またはnumber種の値である。
以下、蛇足。
式の中でのオブジェクトの操作をそのオブジェクトが持つプロパティへの代入のみに限定する、というのはアリだと思う。しかし上の扱いが「柔軟」な対応に見える人はあまりいないのではないだろうか。むしろかなり無理矢理な対応に見える。オブジェクトや関数を足したり引いたりしようとする人はあまりいないのかね。そんなこともないと思うけど。ここは素直に「そんなことはできません」としてしまった方が個人的にはすっきりする。
方向性としてはfunction種の値が返す文字列のようにevalできる文字列にする、というテもあるのだろうけど、これは機能して別のものかもしれない。
演算子をオーバーロードする機能がない、ということもあるのだろうけど。
話を戻す。
次は代入演算子。これは右辺の値を左辺のプロパティに代入する。この際、「種の変換」は行われない。object種やfunction種を扱ってもこれらの値の参照先のオブジェクトには何の変化も起きない、というのはこれまで散々書いてきた通り。
この演算によっても新しい値が作り出される。代入で扱われた値がそれである。よってa = (b = 1) + (c = 2);
のような記述ができる。ただしこの記法は一般的にあまり好まれない。
続いて比較演算子。左右の値を比較してその結果に応じたboolean種の値を生成する。ただし、ここでも「臨機応変」な種の変換が行われる場合がある。
===と!==については種の変換は行われない。値の種類が違えばその時点でfalseになる。また==と!=においても同じ種類の値を比較する場合も種の変換は行われない。気を付けなければならないのは、object種やfunction種同士の比較では参照先の内容が比較の対象ではない、ということ。(a = new Object()) == a
はtrueだが(new Object()) == (new Object())
はfalseだ。object種とfunction種を==で比較する場合には種の変換は行われないらしく、必ずfalseになる。(もちろん!=ではtrue)
種類の異なる値を==と!=で比較する場合と、不等号を含む比較演算子に提供される値に対しては例によって種の変換が「臨機応変」に行われる。まずobject種とfunction種はtoStringされた値(ただしNumberオブジェクトはnumber種)に変換される。さらに種類が異なる場合はnumber種に変換される。string種同士の比較は辞書順でされるようだ。
以下にサンプルを挙げる。どの種類の何の値に変換されているか考えてみてほしい。(NaNに変換される場合に注意) ヒマな人はシェルでいろいろ試してみるのもおもしろいかもしれない。
'123' < '9' //true
'123' < 9 //false
'0' == 0 //true
'' == 0 //true
true < '100' //true
true < '1' //false
false == '0' //true
false == '' //true
true < 'true' //false
true > 'true' //false
true == 'true' //false
(function(){}) <= (function(){}) //true
(function(){}) >= (function(){}) //true
(function(){}) == (function(){}) //false
obj = new Object();
obj.toString = function(){return 1};
func = function(){};
func.toString = function(){return 1};
obj == true //true
func == true //true
obj == func //false
strobj = new String('JavaScript');
tmpobj = new String('JavaScript');
str = 'JavaScript';
tmp = strobj;
strobj == tmp //true
strobj == str //true
tmpobj == str //true
strobj == tmpobj //false
ただ上のサンプルに挙げた種の変換が実際のプログラミングにおいて問題を起こすことはまずないだろう。なぜならこれらは比較すること自体にほとんど意味がないからだ。
思ったより長くなってしまったので、残りの演算子については次回。
値、プロパティ、そしてオブジェクト
オレのためのJavaScript入門、その4
JavaScriptは式で構成され、値は式から生まれる。そしてプロパティという殻を与えられた値のみが生き残ることができる。これが前回の内容。しかしこの説明の中にはオブジェクトという言葉は出てこなかった。式で扱うものは値である。当然プロパティに納めることができるものは値のみ、ということになる。ではJavaScriptにおいてオブジェクトとは一体どのようなものなのだろうか。
実のところ式の中でオブジェクトを扱う手段は一つだけである。これはつまり、JavaScriptではオブジェクトを扱う方法が一つしかない、ということでもある。
JavaScriptにおいてオブジェクトに対して行える唯一の操作、それは「オブジェクトの生成」である。
最も基本的なオブジェクトの生成方法を記す。
新しいオブジェクト = new Object();
この悪い冗談のようなコードの新しいオブジェクトとはプロパティ名である。それなら素直にプロパティ名 = new Object();
とした方が混乱も少なくて済みそうなものだが、ここでは話をややこしくするためにわざとそうしてみた。料理にはスパイスが必要である。
このコードではまず新しいオブジェクトを生成し、新しいオブジェクトという名のプロパティ(←この辺がややこしい)にそのオブジェクトを参照するobject種の値を代入している。new演算子の機能やらその後に続く関数呼び出しとの関係やらについてはとりあえず今は保留する。要するにnew演算子とはオブジェクトを生み出す魔法の呪文みたいなものなのだが、とりあえずここでは新しいオブジェクトという名のプロパティに新しいオブジェクトを参照するobject種の値が代入された、ということだけわかればよい。
このnew Object()
で生成されるオブジェクトはJavaScriptにおけるオブジェクトというものの原型とも言えるもので、これは何の特徴も持たないオブジェクトである。(あらかじめ特徴を持たせる方法もあるのだが、ここでは触れない)
「何の特徴も持たない」からといって、しょぼくれた30過ぎの冴えないサラリーマンみたいなものを想像されては困る。このサラリーマンは単に「平凡」なだけであり、その平凡さこそが彼の持つ特徴なのである。勝手に失礼な想像をしてはいけない。無論彼と私とは何の関係もない。ここでいう「何の特徴も持たない」とはサラリーマンであるかどうかはもとより、そもそも人間であるかどうかすらもわからない、文字通り何の特徴も持たない状態のことである。
さて、これでは何もしたことにはならない。何の特徴も持たない「ナンダカヨクワカラナイモノ」ができただけだけである。
そこでこの「ナンダカヨクワカラナイモノ」に特徴をつけてやることにする。
新しいオブジェクト.種別 = '人間';
新しいオブジェクト.職業 = 'サラリーマン';
新しいオブジェクト.年齢 = '30過ぎ';
...(ry
まあ結局しょぼくれた30過ぎの冴えないサラリーマンなんだが、この際そんなことはどうでもいい。
これは何をしているのかというと、プロパティに値を代入しているのである。見たまんまである。
だがこれではオブジェクトがどういうものなのかよくわからないだろう。
テーブルの上にいくつかのお皿が乗っていて、それぞれのお皿には料理の写真が入っている。少々シュールな光景だが、そんなイメージを想像していただきたい。
ここで「料理の写真」とは実体化していない値を表す。「お皿」はプロパティだ。お皿には名前が書いてあり、これがプロパティ名にあたる。プロパティに収められた値が式の中で評価されるときはこの写真を基に料理が実体化され演算子に食われる。料理を食った演算子はその味を基に新しい料理を作り出し、必要があればその写真を撮ってお皿に移す、というわけだ。
よく見てみるとテーブルにも名前が付いている。中華料理屋なんかで誰も来ないのに店主が見栄を張って「予約席」とか書かれた札を置く、みたいな感じだ。さらに周りを見渡すと、同じようなテーブルがいくつかあり、それぞれに別の名前が付けられている。
再びテーブルの上に目を移すと、料理の写真ではないものが入っているお皿がある。どうやら何かの名前が書いてある紙ようだがよく読めない。そしてその名前はプロパティ名ではないようだ。
この辺で大体察しが付いただろう。「テーブル」とはオブジェクトのことであり、「何かの名前が書いてある紙」とはobject種の値である。
function種も似たようなものだと思ってよい。ただ「何かの名前が書いてある紙」に書いてある名前の示すものがテーブルではなく「事務机」みたいなものであり、お皿にはその紙以外に何か呼び鈴のようなものが入っている。詳しくはまた別のエントリーで説明する(予定)。
このイメージで上のコードを説明をしてみる。
まずnew Object()
で新しいテーブルが生成される。このテーブルの上には何も乗っていない。次に新しいオブジェクトというお皿にこの何も乗っていないテーブルに付けられた名前の書かれた紙が入れられる。
次の行には.(ドット)演算子が出てくる。ここで.(ドット)演算子というものを簡単に説明しておく。
.(ドット)演算子の左辺に置くことができるのはobject種の値のみであり、右辺に置くことができるのはプロパティ名のみ(カッコ付きプロパティ名も含む)である。そしてこのとき.(ドット)演算子を囲む2つの値は、左辺のobject種の値が参照するオブジェクトの持つ右辺のプロパティ名のプロパティ、という一つの値として扱われる。
つまり新しいオブジェクト.種別 = '人間';
とは、'人間'という料理(=string種の値)の写真を撮って、それを新しいオブジェクトと書かれたお皿(=プロパティ)に入った紙(=object種の値)に示されたテーブルの上にある種別と書かれたお皿(=プロパティ)に入れる、ということである。
以下の行も同様だ。
「オブジェクト」とは簡単に言ってしまうと「0(ゼロ)個以上のプロパティを持つもの」である。上のイメージで言い換えると、「オブジェクト」とは「プロパティ」というお皿を乗せるテーブル、なのである。
初めに、オブジェクトを扱う手段は生成しかない、と書いた。新しいテーブルを作ることはできるが、そのテーブルを揺らしたりひっくり返したりすることはできない、ということだ。もちろんテーブルを消すこともできない。テーブルはテーブルとしてそこに存在するだけである。(これが「残り湯は、犬も食わねえ」のココロだ) ではテーブルをそのテーブルたらしめているものは何か。それはテーブルに乗ったお皿である。
つまりテーブルに乗ったお皿とはそのテーブルを特徴付けるものである、と言うことができる。お皿やそのお皿の中身は式によって自由に扱うことができる。そしてテーブルの上のお皿やそのお皿の中身を操作することによってそのテーブルに意味を持たせてやるのである。
この意味を持ったテーブル、これこそが「オブジェクト」なのである。
以下は蛇足だ。
「値」とは具体的なものであり、わかりやすい。反対に「オブジェクト」はものすごく漠然としたものである。それ故に様々な扱い方が可能である。いや、「様々」という表現では生ぬるい。それこそ「無数」の扱い方ができる。
JavaScriptではオブジェクトを使うことによって同じデータ構造に対して様々な表現が可能になる。それは「自由」で「個性的」な表現と言えるかもしれない。そしてもしかしたらこれこそがJavaScriptを使うことの最大の魅力かもしれない。現実はオブジェクトの扱いを型にはめようとして皆必死なようだがね。
さて、前回と今回の説明では端折ってしまった部分があまりにも多い。次回からはその辺の穴埋めをしていこうと思う。
JavaScriptにおける値
オレのためのJavaScript入門、その3
JavaScriptには「値」と「オブジェクト」という二つの概念(というと大げさだが、他にうまい言葉を思いつかない)がある。この二つの概念はお互いに密接な関係を持つことで様々な情報を表現することができる。
そして、ものすごくざっくばらんにいえば、「値」は物質を表し、「オブジェクト」はその構造を表す。
今回はJavaScriptにおける物質、つまり「値」というものを考えてみる。
JavaScriptにおける値にはどんな種類があるか。まずは列記してみる。
object種、string種、number種、boolean種、undefined種、function種、以上の6種類である。
これはJavaScriptにおける正式な分類とは違うかもしれない。いやたぶん違う。おそらく「オレ的」な勝手な解釈だろう(し、だからこそ一般的な「~型」という分類にしなかったのだ)。だがこう括ってしまった方が「オレ的」にわかりやすい。ちなみにこれらはtypeof演算子が返す文字列である、というかtypeof演算子が返す文字列を値の種類としてみたら具合が良かっただけだったりする。
それぞれについて簡単に説明してみる。
object種とは、これまで述べてきた「オブジェクトへの参照」のことである。ここに分類される値には、null以外にはリテラルがなく、値そのものを見ることもできない、という他にはない独特な特徴がある。また、.(ドット)演算子の左辺に置ける唯一の種である。
string種、number種とは、それぞれ一般に文字列、数値と呼ばれるものである。これらについて多くの説明は不要だろう。
boolean種は一般に真偽値と呼ばれる。値はtrueとfalseの2種類。主に比較演算によって生まれる。
undefined種は説明するのが難しい。未定義を表す値、みたいに言われることが多いようだが、この説明ではnullとの区別があいまいである。「オレ的」に詳しく説明してみると、オブジェクトという構造にはすでに組み込まれているがundefinedを除く値の代入はされていないプロパティが持つ値、ということになる。(これを理解できる人はひょっとしたら天才かもしれないが、天才ではなかった場合は私と似た思考回路の持ち主ということになる。さあヘコめ!) この種類に属するものの値は全てundefinedである。(ちなみにnullとはobject種に属する値である。と、ここまではいいのだが、実のところ私はこれ以上のことはわかっていない。オブジェクトという構造に組み込まれていないことを表す値で、ブラックホールを模倣したもの、と思っていたのだが、どうもこの解釈では納得できない挙動を示す。とりあえず現時点では保留しておく。あるいはnullとは係わり合いを持たない、という手もある。もしかしたらこれが現実的に最も賢い判断かもしれない)
最後はfunction種。これについてはまた別にエントリーを作って詳しく考えてみる予定であるが、ここでは「値」としておく。ここからは例によって「オレ的」勝手解釈だ。function種の値の具体的な中身は「定義への参照」である。いわゆる「リテラル」はないが独自の「リテラル風」のものがあり、これを代入演算子の右辺に使うことができる。このときJavaScript内部に「定義」が作られ、左辺値には「定義への参照」が代入される。また、function種の値を持つプロパティを代入することによって一つの定義に複数の参照を持たせることができる。この点はオブジェクトと似ている。function種のみの特徴として、この値を持つプロパティの後ろに()
を付けることによって参照先の定義を評価することが可能で、このときはfunction種を含むいずれかの種類の値に変わる。この値のことを「関数の戻り値」と呼ぶ。関数の戻り値がどの種類のどんな値になるかは、定義による。さらに()
付きのfunction種のみの特徴としてnew演算子を使った演算ができる。これは重要な特徴なのだが、説明は後に取っておく。
だいぶ端折った説明になってしまったが、以上だ。
さて、ここからが本題。
JavaScriptのプログラムは「式」の集まりと考えることができる。JavaScriptにおけるプログラムの実行とは、式を評価していくことである。ここで式の評価について少し細かく考えてみる。
式とは「値」と「演算子」の集まりである。プログラムの中の値とは、値を直に記したもの(これを「リテラル」とか言ったりする)とプロパティ名である。そしてこれらの値は演算子の優先順位に従って順次演算されていく。
さらに細かく見てみる。
プログラム中に記された値は式の中で必要とされる順に順次新しく実体化される。実体化した値はそれを要求した演算子に食われ(前回のオチはここにつながるワケだ)新たな値に生まれ変わる。
これが「式を評価する」ということである。つまり値は式の中で、あたかも泡のように生まれたり消え(=食われ)たりしているのである。
このように値の一生は短い。ほとんどの値は一つの式の中で生まれ、その式の中で消えていく。唯一生き残ることが許されるのは代入演算子によりプロパティに代入された値である。このとき値はプロパティという殻に収められる。そして殻の中で再び実体化される日を待つことになる。
つまり、JavaScriptにおいて「値」は式の中から生まれる。大げさに言うと、JavaScript世界における物質である「値」の錬金術の源は式である、ということになる。そしてこの錬金術から生み出された6種類の値たちがJavaScriptの世界を形作っていくのである。
プロパティと値との関係について少し触れておく。
プロパティと値とは常に一対一の関係が保たれる。一つのプロパティに複数の値を代入することはできない。プロパティに値が代入されると、それまでプロパティが保持していた値は消えてしまう。ただし式の中で値が実体化されることによってプロパティの保持する値が変化することはない。とまあ、このあたりは感覚的に問題ないと思う。
このことは当然object種やfunction種の値であっても同じである。では一つのオブジェクトに複数の参照が付く、というのはどういう状態なのか。
これは複数のプロパティがobject種(あるいはfunction種)の同じ値を持つ、ということである。つまり、値そのものはそれぞれ別のものだが偶然(ではないのだが)その中身が同じだった、とそれだけのことだ。決して値そのものが共有されているわけではない。
object種とfunction種では値そのものを見ることができない。そのため感覚的には少々理解しにくいが、まあ要するにそういうことだ。(ちなみにシェルにおいてobject種の値は[object Object]
と表示される。不勉強で申し訳ないが、私にはこれが何を意味しているのかよくわからない。function種の場合は上でチラと書いた「リテラル風」のものが表示されるが、これは値そのものの内容ではなく参照先の定義をtoStringしたものであり、値としてはobject種と同じく見ることのできない「定義への参照」である、たぶん)
さて次回は、JavaScript世界におけるもう一つの重要な概念、「オブジェクト」について考えてみる。
代入式の中で何が起こっているか
オレのためのJavaScript入門、その2
まずはサンプルコードを挙げる。
・サンプル1
片桐姫子.所属 = 'C組';
橘玲.所属 = 片桐姫子.所属;
・サンプル2
片桐姫子.所属 = new Array('C組');
橘玲.所属 = 片桐姫子.所属;
とりあえずなんとなくサンプル1のようなコードを書いてみたが、後のことを考え所属プロパティを配列にしたサンプル2に書き直した。こうすると後で片桐姫子.所属.push('漫研');
みたいなことができてなかなか具合が良いではないか。
今回はこの2つのコードの違いについて考えてみる。
さてこの2つのコードにほとんど違いはない。2行目については全く同じである。にもかわらず内部では異なるデータ構造が出来上がる。その違いはそれぞれのコードで姫子をD組にトレードしてみるとわかる。
・サンプル1の場合
片桐姫子.所属 = 'D組';
・サンプル2の場合
片桐姫子.所属[0] = 'D組';
サンプル1では姫子だけがD組に放出されるのに対し、サンプル2では玲までがその巻き添えをくらっている。
これはこのコードに問題があるからであって、ではどこにその問題があるかというと、玲の所属プロパティに姫子の所属プロパティを代入している部分である。これは前回に説明した通り。そうなると次に問題になるのはサンプル2ではなく、サンプル1がプログラマの意図通りに動いてしまうことになる。これはいったいどうしてだろう。
この原因は代入式の扱う値が文字列ときと配列のときの処理に違いにある。
代入式の右辺値の値が文字列のとき左辺値のプロパティが受け取る値は、演算子=によって書き写された右辺値の文字列そのものである。つまり文字列はオブジェクトではなく値として扱われる。左辺値のプロパティには右辺値の文字列が文字通り代入される。文字列をオブジェクト風に考えると、このとき左辺値の指すオブジェクトは右辺値の指すオブジェクトと同じ内容で参照先の異なるオブジェクトということになるが、先に書いたように文字列はオブジェクトではないのでこの考え方はやはり正確とはいえない。
では代入式の右辺値が配列のときはどうだろう。配列はオブジェクトである。つまり橘玲.所属 = 片桐姫子.所属;
で扱われる値は配列への参照ということになり、玲は姫子と運命を共にしなければならなくなる。
原因はわかった。要はプログラマが扱う値の種類を正しく把握し、管理すれば問題は起こらないということだ。これで問題は解決、メデタシメデタシ。
と言いたいところだが、この問題の本質は内部で何が起こっているかではなく、見た目上ほぼ同一のコードから異なる結果が導き出されることにある。本題(JavaScript)から少し離れるが、このことを少し突っ込んで考えてみる。
プログラマが出す同じ指示に対してコンピュータが勝手に違う処理をされてはかなわない。これは「仕事をしなさい」という指示を受けた部下が急に飯を食いだしたり居眠りを始めたりするようなものだ。後で「寝るのも仕事のうちです」とか開き直られても、困る。
細かく見ていけばサンプル2の1行目にはnew演算子があるので、この時点で代入式の扱う値はオブジェクトへの参照であることはわかる。だがこれだけでは決してわかりやすいとはいえないだろうし、実際にはnew演算子による代入式が近くにあるとは限らない。
あるいは見た目上全く同じであるプロパティという器に対して「値」と「オブジェクトへの参照」という全く異質なものを乗せることができてしまうことの方が問題なのかもしれない。レストランのテーブルでお皿に乗ったメニューが出てきたらそれは混乱するだろう。事実、参照値を扱う際の入れ物に対して通常の変数とは異なる記法をとる言語だってあるし、それは特に珍しいことでもない。
そもそもどうしてオブジェクトを値として直接扱わず、参照されるものとして間接的に扱うのだろう。
これはオブジェクトが構造を伴った情報であること、そして場合によっては情報量がとてつもなく大きくなる可能性があることが原因ではないかと思う。複雑で巨大なものを直接扱うのは効率が悪い、という現実世界の常識をコンピュータ言語の世界に取り入れたものといえるかもしれない。
落語に「風呂屋と豆腐屋」という噺がある。毎日豆腐を売り歩かなければならない豆腐屋が番台に座っているだけの風呂屋に延々と愚痴を言う、という他愛もない噺なのだが、これなどは値的な扱い(=豆腐屋)とオブジェクト的な扱い(=風呂屋)のたとえ話として聞くとなかなかおもしろい。あるいは夫婦という関係はお互いを値的に扱った関係で、キャバクラやホストクラブに行くのはオブジェクト的に……。いやいや、要するにどちらの扱いにも欠点はあるがそれを補って余りある利点がある、ということを言いたいワケだ。
となると今度は参照と値とに同じ表現を用いることに対する問題が残る。同じものの複製を作ることと、ひとつのものに複数の関連付けを行うこととは本質的にまったく異なる。しかし振る舞いにおいては変わりがないことも多い。実際、内容に変化を加えない限りにおいてその振る舞いは完全に同じである。映画館まで出かけようが(=オブジェクト的扱い)DVDを買ってきて家で見ようが(=値的扱い)映画の内容に変わりはない。
それならいっそ同じ表記にした方がわかりやすいのではないか、ということかもしれない。あるいは単に新しい表記を考えるのがメンド臭かっただけかもしれない。
このことは肯定的な言葉で「臨機応変」と言われる。つまり一概に「悪い」とは決め付けられない。ではあるが、このいわば重複した仕様のせいでJavaScriptを学ぶ人がほぼ必ず一度はつまずかざるを得ないのもまた事実である。
ちなみに「風呂屋と豆腐屋」では風呂屋と豆腐屋は幼馴染で二人とも新婚という設定になっていて、豆腐屋の愚痴が実は単なるノロケ話だったことに対して最後に風呂屋が「残り湯は、犬も食わねえ」とオチるのだが、実はこの豆腐の「食える」と残り湯の「食えない」という特徴は値とオブジェクトの特徴と似ている、という話はまた次回に。
ついでに言っておくとこの「風呂屋と豆腐屋」という噺は完全に私のでっちあげなのでオブジェクト的には取り扱わないように。あくまで値的に扱ってください。
はい、おあとがよろしいようで。(よろしくない)
jsShel
前回のエントリーはあまりにあんまりなので、修正版を上げておきます。
まずはjsShel.html。
続いてjsPlus.js。
主な変更点は以下の通り。
・jsShel.htmlで使われる関数をjsShellのハッシュにまとめました(これはオブジェクト指向云々とは全然関係なくて、単に名前の重複を避けるための措置)
・処理の途中の値を参照するため、引数の値を順次表示する関数Pを定義しました(Pが大文字なのは、これも名前の重複を避けるため)
・jsPlus.js内でのオブジェクトの汚染を極力減らしました(一部残っていますが、これは記述しやすさを優先した意図的なものです。またこれらのメソッドがヘンテコリンな文字使いになっていますが、これも例によって名前の重複を避けるための措置です)
・リロードした際にクッキーにセットした外部JavaScriptファイルを読み込ませることが可能になりました(詳しくはjsPlus.js内CookiePlus.setIncludeFiles関数のコメントを参照してください)
オフライン版jsShel。
うーん、まだなんだかいろいろと意図しない挙動をすることがあるが、現在の私の知識では対処できませぬー。
つか、もっとステキなのがあるじゃん。