HTML 要素にクラスを付与したり削除したりするのに便利なユーティリティメソッド (JavaScript)

HTML5 における HTMLElement#classList

HTML5 では、HTML DOM の HTMLElement インターフェイスclassList というプロパティ が追加される予定です。 このプロパティは W3C DOM 4 の DOMTokenList インターフェイス を実装したオブジェクトを参照しており、このリストに値を追加したり、このリストから値を削除すると、要素のクラス属性に反映されます。

つまり、HTMLElement#classList を使うことで、要素にクラスを追加したり削除したりすることが簡単にできます。 詳細は MDN あたりが分かりやすいと思います。

しかしながら、現在のところ HTMLElement#classList が使える環境は多くはありません。 Firefox 7 や Opera 11.51 では使えることを確認しました (Google Chrome でも使えるようです) が、Safari 5 や IE 10 では実装されていません。

続きを読む

書評: 『ハイパフォーマンス JavaScript』 Nicholas C. Zakas 著, 水野 貴明 訳

ハイパフォーマンスJavaScript

ハイパフォーマンスJavaScript

『ハイパフォーマンス JavaScript』 を読み終わりました。 簡単に内容についてと感想を書いておきます。

続きを読む

JavaScript の正規表現マッチング

JavaScript正規表現 (RegExp オブジェクト) を使ってマッチングする方法。 しょっちゅう忘れてしまううえに ECMAScript 仕様に適合した方法があまり web 上に書かれていないようなので軽くメモしておきます。

RegExp.prototype.exec( string ) メソッド

正規表現のマッチングを試して、マッチした文字列全体とキャプチャリングした文字列を取得するためのメソッドが RegExp.prototype.exec( string ) メソッドです。

このメソッドは、マッチングが成功した場合は Array オブジェクトを返します。 その Array オブジェクトの第 0 要素はマッチした部分文字列全体です。 正規表現にキャプチャリングが含まれていた場合は、Array オブジェクトの第 1 要素以降に、順番にキャプチャリングされた文字列が格納されます。 さらに、この Array オブジェクトには index プロパティと input プロパティが定義され、それぞれの値は、正規表現が文字列にマッチした開始位置とマッチングの対象になった文字列全体です。

また、RegExp オブジェクトの global フラグが true であるならば、マッチした部分の最後の文字の次の文字の位置が、RegExp オブジェクトの lastIndex プロパティに保持されます。 同じ RegExp オブジェクトを使って再び exec メソッドを実行すると、そのときのマッチングは lastIndex プロパティの値から再開されます。 よって、while ループでマッチしなくなるまで繰り返す、ということが可能です。

なお、マッチしなかった場合には exec メソッドは null を返します。

// global フラグを true にした RegExp オブジェクト
var re = /<(\d+),(\d+)>/g;
var m;
// マッチする部分がある限り繰り返す
while( m = ( re.exec( "ab<32,43>cddd<faa>da<342,2>bcaaeabc" ) ) ) {
    // マッチした部分の開始位置
    print( m.index );
    // マッチした部分文字列全体 (Ruby などでは $& で参照できる)
    print( m.shift() ); // (第 0 要素だけを取り出している)
    // キャプチャされた部分 (Ruby などでは $1, $2, ... で参照できる)
    print( m.join( ", " ) ); // (元々の第 0 要素は既に取り出したので, 残りはキャプチャされた部分のみ)
}

JavaScript でも、バージョンによっては RegExp['$&'] としてマッチした部分文字列全体を参照したり RegExp.$1, RegExp.$2, ... としてキャプチャされた部分を参照できたりしましたが、非推奨となっており、使わないようにした方がいいでしょう。 (ちなみに RegExp.$& は基本的に構文エラーになってしまうので、RegExp['$&'] と書く必要がある。)

参考文献

ECMAScript 5 の strict mode でグローバルオブジェクトを得るひとつの方法

ECMAScript 5th edition (JavaScript 系統のコア部分をまとめた言語みたいなもの) には strict mode というものがあり、strict mode では関数コード (Function Code) 内の this キーワードの参照先オブジェクトが、strict mode でない場合と異なる可能性があります。 例えば、関数式を即実行する Immediate Function Pattern (((function () { /* ... */ })(); という形式のもの。 特に正式名称があるわけではないと思いますが、『JavaScriptパターン ―優れたアプリケーションのための作法』 でこの名前が使われていたのでこの名前で呼んでいます)) と呼ばれる手法を使う際、strict mode でなければ関数コード内で this キーワードがグローバルオブジェクトを参照しますが、strict mode では未定義値 (undefined) になります。

詳しくは下記参照。

その対策として、id:think49 さんのところでは以下のように new Function() を利用する方法が紹介されています。

関数コードでは new Function() を利用します。

"use strict";

(function () {
  console.log(new Function('return this')()); // [object Window]
  console.log(Function('return this')());     // [object Window]
})();

グローバルコード (Global Code) からグローバルオブジェクトを渡す方法

別の方法としては、Immediate Function の引数としてグローバルオブジェクトを渡してやる方法もあります。 グローバルコードでは this キーワードはグローバルオブジェクトを参照するため、グローバルコードにおける Immediate Function に対しては、引数としてグローバルオブジェクトを渡してやることができます。

"use strict";

(function ( globalObj ) {
    // 引数 globalObj としてグローバルオブジェクトへの参照を受け取る
    print( globalObj ); // [object Window]
    // strict mode: Immediate Function 内の this キーワードは undefined
    print( this );      // undefined
})( this ); // グローバルコードにおける this はグローバルオブジェクト

どこででも使えるという意味では new Function() を使う手法の方が便利ではあるのかなーと思いますが。

new を不当に貶める陰謀と JavaScript におけるクラスの継承構造の話

私は陰謀論者じゃないですし JavaScriptnew 演算子が大好きなわけでも大嫌いなわけでもないです。 念のため。 本記事は Hiraku さんが書かれた下記記事への言及です。

new 演算子は使うな!?

newを封印するべき4つの理由」 でも new がいかに糞であるかが書かれていますし、その記事からも言及があるように Crockford さんが書かれた書籍 『JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス』 でも new 演算子は Bad Parts に分類されています。

new 演算子が忌避される理由はいろいろあるみたいですが、Hiraku さんの記事では

  • new を書き忘れてもコンストラクタ関数が実行されて意図しない動作になる
  • 継承を考慮する必要がある
  • 何をやっているのかわかりにくい
  • 読みづらい

という点が指摘されています。

常に new 演算子を使うことが最良の選択だとは思っていませんが、ことさら new 演算子を貶める言説も好きじゃないのでちょっと反論しておこうかな、というのがこの記事の主題です。

各継承方法のオブジェクトの関係図

はじめに、議論の土台として、純粋なプロトタイプ継承と、クラスの継承構造を持ち込んだ場合の継承方法について、オブジェクトの関係図を示します。

純粋なプロトタイプ継承

まずは JavaScript の純粋なプロトタイプ継承の形を見てみましょう。

ECMA-262 (5th edition) を満たしている JavaScript 処理系なら、Object.create メソッドで簡単にプロトタイプ継承が可能です。 Hiraku さんの記事で定義されている object 関数は、この Object.create メソッドと同じようなものです。

var baseObj = {
  name: "ベースオブジェクト", 
  printName: function(){ print( this.name ) }
};
// Object.create メソッドでプロトタイプ継承する
var subObj = Object.create( baseObj, { name: { value: "サブオブジェクト" } } );
// 使ってみる
subObj.printName() // サブオブジェクト

このような純粋なプロトタイプ継承による継承構造を図で表すと次のようになります。 非常に単純ですね。


new 演算子とクラスの継承構造

次に、プロトタイプ継承と new 演算子によるオブジェクト生成の機構を利用して、クラスの継承構造を JavaScript で実現してみましょう。 詳しい実現方法は 「JavaScript におけるクラスの作成と継承」 の記事で述べましたので、ここでは詳細には立ち入りません。 基本的な思想としては、インスタンス (に相当するオブジェクト) で全てのインスタンス変数を保持し、プロトタイプ継承の先祖のオブジェクトでインスタンスメソッドを定義する、という形式です。 コンストラクタ関数オブジェクトがクラスそのものを表しています。

少しややこしいですが、インスタンス生成には new 演算子、クラスの継承構造の生成には関数定義、という風に明確に分離しています。 また、クラスを表現するオブジェクト (コンストラクタ) とインスタンスメソッドを定義するオブジェクトが分かれているため、コンストラクタのプロパティによってクラスメソッドやクラス変数を実現できます。

Hiraku さんの記事におけるクラスの継承構造

最後に、Hiraku さんの記事に書かれている方法でクラスの継承構造を実現した場合のオブジェクトの関係を図に示します。

JavaScript における純粋なプロトタイプ継承の継承構造に、無理やりクラスとインスタンスという概念を持ち込んでいるように私は思います。 以下のような問題点があるように思います。

  • クラスとインスタンスの区別が明確でない : クラスの継承もインスタンスの生成も完全に同じ機構で実現している。 同じプロトタイプ継承によって実現しているとしても、クラスとインスタンスという概念を持ち込むのであれば、区別するようにしたほうが良い
  • クラスメソッドとインスタンスメソッドのを区別が明確でない : クラスを表すオブジェクトとインスタンスメソッドを定義するオブジェクトが同一なので、クラスメソッドとインスタンスメソッドを区別できない
  • 明示的なコンストラクタ呼び出しが必要 : new 演算子によるオブジェクト生成の場合は暗黙的にコンストラクタ関数が呼び出されましたが、この方法では明示的にコンストラクタの呼び出しが必要です

4 つの new 批判に対する反論

それでは 「newを封印するべき4つの理由」 に対して反論してみます。

new を書き忘れてもコンストラクタが実行されてしまう問題
var Person = function Person( name ) {
    this.name = name;
};
Person.prototypte.sayHello = function sayHello() {
    print( "Hello, I'm " + this.name + "." );
};

// new を忘れて実行
var tarou = Person( tarou );

確かに、new を書き忘れてもコンストラクタ関数が実行されてしまいます。 それは認めましょう。 ですが、これの何が問題なんでしょうか? new を書き忘れることってありますか? 書き忘れたとして、それが発見困難なバグに繋がりますか?

私の感覚では、new の書き忘れは引数の個数不足での関数呼び出しと同じレベルの問題であると思います。 引数の個数や型チェックが必要なら関数内でチェックをするように、チェックする必要があるならコンストラクタ関数内で this が何を指しているのかチェックすればいいだけです。

new の書き忘れで時間を浪費するようなプログラマが居るならば、その人はプログラマに向いていないので転職を勧めてあげましょう。

継承を考慮する必要があるという問題

『この書き方は、object関数をインライン展開したのと同義です…。それならいっそのこと、常にobject関数を使うようにした方が楽だと思いませんか?』 とありますが、クラスの継承を行う関数を定義すればいいだけのことです。 例えば 「JavaScript におけるクラスの作成と継承」 の記事では、クラスの継承のための extend という関数を定義しました。

何をやっているのかわかりにくいという問題

JavaScript自体はプロトタイプベースで作られているくせに、newを使うとクラスベースみたいな書き方をする必要があります。』 って、new メソッドはプロトタイプ継承を応用してクラスの継承構造を JavaScript に持ち込むためのものなので当然でしょう。 クラスの継承構造を持ち込むのであれば、インスタンス化に new 演算子を使うことは他の言語との類似性から言ってもわかりやすい と思います。 もちろんクラスの継承構造を使わないのに new 演算子を使うことは混乱の元になると思いますが。

読みづらいという問題

個人的にはオブジェクトリテラルの中に関数式を書くのは好きじゃないのでどっちかというと

var Animal = {
  name: "動物"
, breathe: function(){alert("すーはー")}
, sayName: function(){alert(this.name)}
};

よりも

var Animal = function Animal( name ) {
    this.name = name;
};
Animal.prototype.breath = function breath() {
    print( "すーはー" );
};
Animal.prototypte.sayName = function sayName() {
    print( this.name );
};

の書き方が好きなんですが。 まあでも多くの人は前者の書き方の方が好みなんだろうなー、とは思います。

ていうかこの name ってなんなんでしょう。。 クラス変数?

そもそも new 演算子のどの部分を批難しているのか?

というわけで 4 つの問題点に対する反論 (になってるかどうかは微妙ですが) を書いてきましたが、そもそも new を封印する目的がいまいちよくわかりません。

  • 本当に、ただ純粋に new 演算子を使わないようにすべき、という主張?
  • JavaScript にクラスの継承構造を持ち込むな、という主張?

本当に、ただ純粋に new 演算子を使わないようにすべき、という主張は JavaScript に限らず Java などでもあります。 new 演算子によるインスタンス化は柔軟性に欠ける、というものです。 インスタンス化のための API としてユーザーにはファクトリーメソッドを提供し、new 演算子は表から見えないようにするべきであるという主張であり、『Effective Java 第2版 (The Java Series)』 などに書かれています。

しかし、今回の場合は単純に new 演算子を表から隠すだけでなく継承構造も変えていますし、単純に new 演算子を表から隠せ、という主張ではないように思います。

それでは、JavaScript にクラスの継承構造を持ち込むな、という主張なのかというとそれも違います。 Hiraku さんの記事では 「クラス(に相当するオブジェクト)を作る」 という表現が出てきていますし、クラスの継承構造を実現したいように思えます。

クラスの継承構造が不要な場面ではわざわざクラスの継承構造なんか作らずに、JavaScript の基本的な継承であるプロトタイプ継承を使いましょう!」 という主張なら納得するのですが、Hiraku さんの記事では結局のところ何が目的なのかよくわからなくなっており、「俺俺オブジェクト指向な気がする」 と言われても仕方ないかなー、と思います。

new 演算子を使うとわかりやすい? わかりにくい? (追記)

ちょっと重要なことなので追記。

本記事の上のほうでも 「クラスの継承構造を持ち込むのであれば、インスタンス化に new 演算子を使うことは他の言語との類似性から言ってもわかりやすい」 と述べましたし、id:teramako さんも 「僕はnew演算子は好きです。書き忘れが発生してorzすることはあるけど。何故好きかというと、コードを読んでいてインスタンスを作成していることが明確に分かるから」 と仰っています。 一方で、Hiraku さんは 「JavaScript自体はプロトタイプベースで作られているくせに、newを使うとクラスベースみたいな書き方をする必要があります。中でやっていることと、外側から見えるインターフェースが違いすぎる」 という立場です。

もちろん、どちらが間違っている、ということはありません。 とはいえどちらかの立場をとらなければいけないのであれば、「誰にとってわかりやすくすべきか」 という視点を取り入れるべきでしょう。

クラスという概念を取り入れる以上、クラスの設計者とクラスのユーザーという 2 人の視点で考える必要があります。 これはしばしば同一人物でありますが、同一人物ではない可能性もあります。 そして、クラスというものは クラスのユーザーにとってわかりやすい ものであるべきだと思います。 クラスの設計・実装者は当然 JavaScript に詳しいはずですので、プロトタイプ継承がどうとか、そういう点は問題なく認識しているはずです。 一方、クラスの使用者はクラスを提供されているだけなので、「クラスに見えるけどプロトタイプ継承で実装されている」 という点は認識していないかもしれません。 また、そのように認識させる必要もありません。 なぜなら 「クラス」 を提供しているのですからクラスのように扱えればいいのです。

例えば、Person というクラスを提供するとしましょう。 このクラスは人物を表すもので、人物の名前を引数にとってインスタンス化するものとしましょう。 こう聞くと、クラスのユーザーはほぼ間違いなく以下のようなコードでインスタンス化できるものと期待するはずです。

var tarou = new Person( "田中 太郎" );

「クラスをプロトタイプ継承したオブジェクトを作って、自分で初期化関数を呼んでください」 などと言われるとどうでしょう?

var tarou = Object.create( Person );
tarou.init( "田中 太郎" );

という感じでしょうか。 お世辞にも良いインターフェイスだとは言えません。 ではオブジェクトの生成と初期化を行う関数を Person オブジェクトに追加してみますか?

// ファクトリーメソッド
Person.newInstance = function newInstance( name ) {
    Object.create( this );
    this.init( name );
};
// インスタンス化
var tarou = Person.newInstance( "田中 太郎" );

すっきりしました。 これなら良いでしょう。 しかしこうなるともはや従来の手法でいい気がします。 クラスの実装方法に違いはありますが、上でも言ったようにクラスの設計・実装者には十分な知識があるはずなのでどちらの実装方法でもあまり問題にはなりません。 それよりも、ユーザーが標準的な使用方法でクラスを使用できるかどうか、が重要なのです。

標準的な使用方法で使えるのかどうかという点において、インスタンス化についてよりももっと大きな問題があります。 instanceof 演算子を実直に使えないという問題です。 (そもそも instanceof 演算子は使うべきではない、という主張もありますが、それはさておき。) ユーザーは、Person クラスをインスタンス化した tarou は、以下の式で true になると期待するでしょう。

tarou instanceof Person; //=> true

しかし、Hiraku さんとこの方法では

tarou instanceof type( Person ); //=> true

などとしなければいけません。 大規模なフレームワークとしてクラス機構を提供するのであれば新しい書き方を導入するのもひとつの手ではありますが、単一、または少数個のクラスを提供する際にこのような書き方をクラスのユーザーに強要するのは無理というものです。 様々なクラスの提供者がそれぞれ独自に type 関数のような関数を提供したらどうでしょう? 使いにくいですよね。 これが 「俺俺オブジェクト指向」 と呼ばれてしまったひとつの理由でしょう。

プロトタイプ継承という機構を隠してしまったことの弊害

しかしながら、JavaScript が 「クラスの継承構造のような機構」 を提供し、プロトタイプ継承という機構を隠してしまったがための弊害もあります。 Hiraku さんが仰るように 「実際の挙動 (プロトタイプ継承) と見た目 (クラスの継承やインスタンス化) が異なっている」 というものです。

JavaScriptnew Date() のようにインスタンス化を行ったことがある人はたくさんいると思いますが、プロトタイプ継承という継承方法を知っている人は思っている以上に少ないのではないか、と思います。 プロトタイプ継承という機構が裏に隠れてしまっているために、(誰かが作ったクラスを使うだけなら) プロトタイプ継承について勉強する必要が無いためです。 特に JavaScriptプログラマだけでなく、web デザイナーなど本職がプログラマじゃない人にも使われていますので、ちゃんと勉強してない人も多いはずです。

そのような 「クラスを使ったことはあるけどプロトタイプ継承はよく知らない」 というような人がクラスを設計・実装しようとするとよくわからなくなって混乱してしまいます *1。 そして、「JavaScript のクラスってよくわからない」 ということになってしまうのではないかと思います。

この点はどうしようもないかなーという気がするのですが、とりあえず言えることは 「他の言語と同じようにクラスを使えるからといってろくに勉強せずクラスの設計・実装をしようとすると痛い目を見るので、まず JavaScript の勉強をしましょう」 ということですね。

私の主張

最後に JavaScript におけるオブジェクトに関して私の主張をちょこっとしておきます。

new 演算子がしばしば攻撃の対象になるのは、他の言語との類推から JavaScript においてもなんでもかんでも new 演算子を使ってオブジェクト生成するような人がいるからだと思います。 他の言語、例えば Java などではオブジェクトを生成するために基本的 *2new を使います。 一方、JavaScript では new 演算子が必要となる場面はそれほど多くありません。

JavaScript は弱い動的型付け言語であり、使い捨てのオブジェクトをオブジェクトリテラルで生成することができます。 なんらかのオブジェクトを継承したい場合、クラスの継承などしなくてもプロトタイプ継承によりオブジェクトそのものを継承することができます。 特に web サイト上で動く JavaScript は使い捨てのオブジェクトを使うことが多く、クラスの継承構造が必要になることはあまり多くないでしょう。

一方で、クラスの継承構造を利用すると便利な場面もあります。 同じメソッドを持ち、異なるデータを保持する多くのオブジェクトを使いたい場面では、クラスの継承構造が役に立ちます。 そういうところでは、クラスの継承構造をつくり、new 演算子 ((new じゃなくてファクトリーメソッドなどを使うべき、という議論もありますが、それはまた別のお話ということで。)) を使ってインスタンス化する、ということをすれば良いでしょう。

使いどころを考えて、使うべきところで使うべき方法をとるというのが重要です。

*1:Hiraku さんが仰っている 「よくわからない」 という指摘はこの点なんじゃないかと思います。

*2:ファクトリーメソッドなどもありますが。。

ECMAScript のレキシカル環境 〜catch 節のスコープのお話〜

Twitter で ECMAScript (JavaScript) の catch 節のスコープについての話をみかけた ので、ちょっと調べてみた。

catch( err )err のスコープは?

例外処理の機構で使用される catch( err ) ですが、この err はどの範囲で有効なのか? 普通に考えると catch 節内だけで有効な気がするけど、ECMAScript では一般に関数ごとにスコープを持つと言うし、関数全体? まさかグローバル?

(function test() {
    try {
        throw 0;
    } catch( err ) {
        print( err ); // 0
    }
    print( err ); // [ERROR] err is not defined
})();

試したところ、どうやら catch 節内だけのスコープがある模様。

ECMA-262 (5th edition) の 10.2 節 (Lexical Environments) のところには以下のように書かれていて、catch 節にもレキシカル環境 (Lexical Environment) が結び付けられていることがわかります。

Usually a Lexical Environment is associated with some specific syntactic structure of ECMAScript code such as a FunctionDeclaration, a WithStatement, or a Catch clause of a TryStatement and a new Lexical Environment is created each time such code is evaluated.

catch 節内の VariableDeclaration は?

じゃあ catch 節内の VariableDeclaration ((var variable_name; という形式の変数宣言)) は catch 節の外の環境には影響を与えないのか?

(function test2() {
    try {
        throw 1;
    } catch( err ) {
        var n = err;
    }
    print( n ); // 1
})();

試してみると、catch 節内の var で宣言された変数は、catch 節の外でも有効でした。 なぜ?

レキシカル環境と実行コンテキスト

ECMA-262 (5th edition) の 10.3 節 (Execution Context) を見ると、次のように書いてあります。

When control is transferred to ECMAScript executable code, control is entering an execution context. Active execution contexts logically form a stack. The top execution context on this logical stack is the running execution context. A new execution context is created whenever control is transferred from the executable code associated with the currently running execution context to executable code that is not associated with that execution context. The newly created execution context is pushed onto the stack and becomes the running execution context.


An execution context contains whatever state is necessary to track the execution progress of its associated code. In addition, each execution context has the state components listed in Table 19.


The LexicalEnvironment and VariableEnvironment components of an execution context are always Lexical Environments. When an execution context is created its LexicalEnvironment and VariableEnvironment components initially have the same value. The value of the VariableEnvironment component never changes while the value of the LexicalEnvironment component may change during execution of code within an execution context.

Table 19 の内容は次のとおり。

Component Purpose
LexicalEnvironment Identifies the Lexical Environment used to resolve identifier references made by code within this execution context.
VariableEnvironment Identifies the Lexical Environment whose environment record holds bindings created by VariableStatements and FunctionDeclarations within this execution context.
ThisBinding The value associated with the this keyword within ECMAScript code associated with this execution context.

適当にまとめると、『ECMAScript を実行するための実行コンテキストは LexicalEnvironment と VariableEnvironment と ThisBinding から成る。 LexicalEnvironment と VariableEnvironment は常にレキシカル環境である。 実行コンテキストが生成されるとき、最初は LexicalEnvironment と VariableEnvironment は同じ値を持つ。 VariableEnvironment の値は決して変更されないが、LexicalEnvironment の値は変更される可能性がある』 という感じでしょうか。 つまり、実行コンテキストにおけるレキシカル環境として、変更される可能性がある LexicalEnvironment と変更されない VariableEnvironment の 2 種類のレキシカル環境がある、というわけです。

実行コンテキストは関数へ入るときに生成され、catch 節に入るときには LexicalEnvironment が変更される

さて、実行コンテキストには LexicalEnvironment と VariableEnvironment の 2 種類のレキシカル環境があることがわかりましたが、それぞれどのように使われるのでしょうか。

実行コンテキストが生成されるのはいつかというと、ECMA-262 の 10.4 節 (Establishing an Execution Context) に書かれています。 適当に意訳すると 『グローバルコードや eval 関数で使われているコードを評価する際、および関数を呼び出す際に新しい実行コンテキストが作られ、そこに入る。 実行コンテキストに入る際、ThisBinding がセットされ、VariableEnvironment と初期の LexicalEnvironment が定義される。 また、宣言束縛の具現化 (10.5 節) が実行される。』 と書かれています。 宣言束縛の具現化 (Declaration Binding Instantiation) というのが、関数宣言 (FunctionDeclaration) や変数宣言 (VariableDeclaration) を環境に結びつけることを意味しています。

つまり、VariableDeclaration が環境に結び付けられるのは実行コンテキストが生成されるときであり、実行コンテキストが生成されるのは関数を呼び出すときである、ということです。 すなわち、catch 節の中に VariableDeclaration があったとしても、それが環境に結び付けられるのはその catch 節を含む関数 (またはグローバルコードか eval コード) が呼び出されるときであって、catch 節に入るときはない、というわけです。

では catch 節に入るときはどのようなことが行われているのでしょうか? これは、12.14 節 (The try Statement) に書かれています。

The production Catch : catch ( Identifier ) Block is evaluated as follows:


1. Let C be the parameter that has been passed to this production.
2. Let oldEnv be the running execution context’s LexicalEnvironment.
3. Let catchEnv be the result of calling NewDeclarativeEnvironment passing oldEnv as the argument.
4. Call the CreateMutableBinding concrete method of catchEnv passing the Identifier String value as the argument.
5. Call the SetMutableBinding concrete method of catchEnv passing the Identifier, C, and false as arguments. Note that the last argument is immaterial in this situation.
6. Set the running execution context’s LexicalEnvironment to catchEnv.
7. Let B be the result of evaluating Block.
8. Set the running execution context’s LexicalEnvironment to oldEnv.
9. Return B.


NOTE No matter how control leaves the Block the LexicalEnvironment is always restored to its former state.

いくつか処理を行っていますが、要は実行コンテキストの LexicalEnvironment を変更しているわけです。 その新しい LexicalEnvironment は、古い LexicalEnvironment を親環境とし、catch 節にパラメータとして渡された値を唯一の変数束縛として持っています。

識別子の名前解決に使われるのは LexicalEnvironment (10.3.1 節 Identifier Resolution 参照) なので、catch 節に渡されたパラメータは catch 節の中だけのスコープを持つわけです。 ちなみに with 文も catch 節と同じで、実行時に LexicalEnvironment を変更します。

まとめ

  • グローバルコードと eval コードの評価時、および関数の呼び出し時に新たな実行コンテキストを生成し、そこに入る
  • 実行コンテキストは ThisBinding と LexicalEnvironment と VariableEnvironment から成る
  • 識別子の名前解決に使われるのは LexicalEnvironment
  • 実行コンテキストの生成時に、関数宣言と変数宣言は環境に結び付けられる (このとき LexicalEnvironment と VariableEnvironment は同じ値)
  • with 文と catch 節は LexicalEnvironment を変更する

JavaScript におけるクラスベースの継承方法色々

JavaScript Patterns: Build Better Applications with Coding and Design Patterns

JavaScript Patterns: Build Better Applications with Coding and Design Patterns

先日、JavaScript におけるクラスベースの継承方法に関して @think49 さんと色々議論してたんですが、クラスベースの継承方法に関して 『JavaScript Patterns』 (和訳版 『JavaScript パターン』) の中にパターン化してまとめられていましたのでここで紹介しておきます。

JavaScript におけるクラスベースの継承って?

JavaScript にはクラスというものがありませんが、コンストラクタ関数を作り、new 演算子を使ってインスタンスを作るということが可能です。

var Constructor = function Constructor() {}; // コンストラクタ
var c = new Constructor(); // インスタンス化

インスタンス化の記法が他のクラスベースの言語 (特に Java など) のものに近いため、コンストラクタ (prototype プロパティなどを含む) を擬似的なクラスだとみなすことがあります。 本記事では、継承を用いてこの擬似的なクラスのコードを再利用する方法をクラスベースの継承という *1 ことにし、それについて紹介します。 簡単な紹介に留めますので、詳細は 『JavaScript Patterns』 の 6 章 (Code Reuse Patterns) をご覧ください。 以降、"Classical Pattern" という言葉が出てきますが、これは書籍中の言葉をそのまま使っています。

なお、書籍では 「再利用」 を目的としていますので必ずしも 「継承」 と言えないパターンもありますが、「再利用」 というのも締りが悪いので本記事では 「継承」 という言葉を使わせてもらいます。

前提

親となるコンストラクParent と、それを再利用する Child があるとします。 特に断りが無い限りは、このコードを前提に話を進めていきます。

var Parent = function Parent( name ) {
    this.name = name || "Adam";
};
Parent.prototype.getGreetingMessage = function getGreetingMessage() {
    return "Hello, I'm " + this.name + "!";
};

var Child = function Child() {};

以降、5 つのパターンを紹介します。

Classical Pattern #1 - The Default Pattern

クラスベースの継承の最も一般的なパターンです (多分)。 単純に子となるコンストラクタの prototype プロパティに、親のインスタンスを代入するというものです。

// Child のインスタンスが Parent のインスタンスをプロトタイプ継承する
Child.prototype = new Parent();
// 必要に応じてメソッドの追加などを行う
Child.prototype.newMethod = function newMethod() { /* ... */ };

単純なので JavaScript 関係の書籍でもしばしば目にすることがあり、一般的に使われている手法だといえます。 しかし、この方法ではクラスを継承しているのではなく、親クラスのあるインスタンスを継承していることになります。 上の例では、本来インスタンスごとに持っているべきだと思われる name プロパティが、Child インスタンスごとではなく Child インスタンスの単一のプロトタイプオブジェクトにのみ存在しているという点で問題があります。 (もちろんそれが問題でないなら良いのですが。)

Classical Pattern #2 - Rent-a-Constructor

次に紹介するのは、子のコンストラクタ内から親のコンストラクタを呼び出す、というものです。

// 前提となる Child コンストラクタをこれに置き換える
var Child = function Child( name ) {
    // 親のコンストラクタを呼び出す
    Parent.apply( this, arguments );
};

このパターンは、親のコンストラクタを再利用する、というものです。 親と子の間に直接的なプロトタイプ継承の関係はありません。

Classical Pattern #1 の欠点が解消され、親クラスで定義されているプロパティが、ちゃんと子のインスタンスごとに持たされるようになっています。 その一方で、親の prototype プロパティで定義されているメソッドは継承されない、という欠点もあります。

Classical Pattern #3 - Rent and Set Prototype

上の 2 つのパターンをあわせたものがこのパターンです。

// 前提となる Child コンストラクタをこれに置き換える
var Child = function Child( name ) {
    // 親のコンストラクタを呼び出す
    Parent.apply( this, arguments );
};
// prototype プロパティに親のインスタンスの設定する
Child.prototype = new Parent();
// 必要に応じてメソッドの追加などを行う
Child.prototype.newMethod = function newMethod() { /* ... */ };

これまでの 2 つのパターンの短所を解消できています。 ただ、親のコンストラクタ内で定義される name プロパティが、子のインスタンスのプロトタイプオブジェクトにも存在し、また子のインスタンス自身のプロパティとしても存在する、(すなわち、name プロパティが 2 回継承されている) というのが気になるところではあります。

Classical Pattern #4 - Share the Prototype

これは 「継承」 とは少し違いますが、プロトタイプオブジェクトを共有するというものです。

Child.prototype = Parent.prototype

このようなコードの再利用方法が有効な場面もあるかもしれませんが、基本的には使う必要のないパターンでしょう。

Classical Pattern #5 - A Temporary Constructor

最後は、#3 のパターンをさらに改良したものです。

// #3 のパターンにおける
//   Child.prototype = new Parent();
// の代わりに以下の処理を行う
(function () { // ローカルスコープ生成
    var F = function F() {}; // 一時的なコンストラクタ
    F.prototype = Parent.prototype;
    Child.prototype = new F();
})();

#3 のパターンでは Parent.prototype を継承するために Child.prototype = new Parent() としていましたが、この方法だと Parent コンストラクタを実行してしまいます。 Parent コンストラクタを実行せずに Parent.prototype を継承するために、一時的なコンストラクF を作って対処するというのがこのパターンです。

さらに改良

他のパターンではプロトタイプオブジェクトの constructor プロパティについてはあまり考えてきませんでしたが、実際にはそういった点も考えたほうがいいでしょう。 ついでに親となるコンストラクタを子のコンストラクタのプロパティとして持たせておけばいいかもしれません。

(function () { // ローカルスコープ生成
    var F = function F() {}; // 一時的なコンストラクタ
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child; // constructor プロパティの設定
    Child.baseConstructor = Parent;      // 親コンストラクタを表すプロパティの設定
})();

まとめ

色々なパターンを紹介しましたが、クラスベースの継承という点では #5 の方法が最良だと思います。

ただし、JavaScript でクラスベースの継承を使う必要がある場面というのはそうそうないと思います。 他の言語に近いからという理由で安易にクラスベースっぽい書き方をするのではなく、本当に必要な場合にのみ使うようにしましょう。

参考文献

本記事の内容は、次の書籍の内容を紹介するものです。 詳細は書籍をご覧ください。

*1:書籍では "Classical Inheritance" と言っています。