書評: 『JavaScript: The Good Parts ― 「良いパーツ」 によるベストプラクティス』 Douglas Crockford 著, 水野 貴明 訳

JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス

JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス

Amazon.co.jp でのレビューで高評価だったのでちょっと読んでみようかなぁ、と思って購入しました。 読んでみると思ったほど良い内容ではなかったので、ちょっと気になった部分を書いておきます。

初心者向けにしてはやや難あり?

本書の 「はじめに」 には次のように書かれています。

本書は JavaScript というプログラム言語についての本であり、偶然か、はたまた好奇心のためか、初めて JavaScript の世界へ飛び込もうとしているプログラマに向けて書かれている。 また本書は、すでに JavaScript を使っていて、この言語により精通したい初級プログラマに向けて書かれた本でもある。

つまり、本書は JavaScript の初心者向けの本ということです。 私も本書を読んで、内容的には初心者向けの書籍だと感じました。

しかし、「こうすべきではない」 という筆者の主張が強く、初心者向けにしてはやや難があるように感じました。 絶対に守るべきことを強く主張するだけならまだ良いのですが、本書には必ずしも良いとは言い切れない主張も多かったので、そういう主張を読者が鵜呑みにしてしまう可能性があるのはちょっと問題だと思いました。 まあ主張が強すぎるだけで内容的には悪くはないので、「こういう主張もあるんだなー」 という程度に読むのであれば初心者にもいいかと思います。

細かい部分について、Kanasansoft Web Lab. さんが下記記事にて色々突っ込んでいるのでご覧ください。

特に問題があるのは継承について

私が、本書で特に問題があると感じたのは 「継承」 について書かれた章です。

本書では、下記のような継承方法を 「疑似クラス型」 の継承 *1 と呼んでいます。

// ベースとなる疑似クラス
var Base = function Base( value ) {
    this.fBase = value;
};
Base.prototype.doBase = function doBase() { /* 何らかの処理 */ };

// Base を継承した疑似的なサブクラス
var Sub = function Sub( value ) {
    this.fSub = value;
};
Sub.prototype = new Base();
Sub.prototype.doSub = function doSub() { /* 何らかの処理 */ };

// インスタンス化
var b = new Base( "ベース" );
var s = new Sub( "サブ" );

疑似クラス型の継承だと、必要以上に深く複雑な階層構造を構築するコードを書く必要があって、さらにプライベート領域 (オブジェクトの外部から参照できない変数やメソッド) を持てないため良くない、というのが本書の主張です。

その解決策として、下記のようにオブジェクトを生成する関数を使って継承を実現する方法 (本書では 「関数型」 と呼ばれていました) が記されていました。

// このサンプルコードは上のサンプルコードと等価なわけではない

// Base 相当のオブジェクトを生成する関数
var createBase = function createBase( spec ) {
    // spec がプライベートな領域
    // that が新しく生成したオブジェクト
    var that = {};
    that.doBase = function doBase() {
        // プライベートな領域の値を返す
        return spec.name;
    };
    return that;
};

// Sub 相当のオブジェクトを生成する関数
var createSub = function createSub( spec ) {
    spec.subInfo = "sub!";
    // Base 相当のオブジェクトを生成
    var that = createBase( spec );
    that.doSub = function doSub() {
        // プライベートな領域の値を返す
        return spec.subInfo;
    };
    // ...
    // メソッドの追加などをする
    // ...
    return that;
};

// オブジェクトの取得
var b = createBase( { name: "ベース" } );
var s = createSub( { name: "サブ" } );

要は 「クロージャでプライベート領域が実現できるし、こう書くことで疑似クラス型の手法よりもすっきりとする」 というのが筆者の主張なわけです。

しかしながら、プライベート領域を実現しようと思ったら疑似クラス型の方法でも可能ですし、個人的には関数型の手法よりも疑似クラス型の手法の方がすっきりしているように思います。 また、(プライベート領域を実現しない場合の) 疑似クラス型であれば、メソッドはそれぞれのオブジェクトで共有されるため、メモリ領域は少なくて済みます。 (関数型の場合は各オブジェクトにメソッドが追加されるので、メモリ領域が多く必要。)

私が本書で特に気になったのはこの部分と、あとは new 演算子を使うべきではない、という主張です。 継承の方法も new 演算子を使うべきかどうかも、必ずしも筆者の考えが正解だとは言えないと思うのですが、筆者の主張が強くやや独善的であるように感じました。

おまけ: よりよい疑似クラス型の継承方法

上では、コンストラクタ関数の prototype メソッドに、継承したいオブジェクトをインスタンス化したものを設定していました。 しかし、単にそうするだけではサブクラスの constructor プロパティがベースクラスを指す、というような問題があります。 また、プロトタイプオブジェクトのプロパティだけでなく、インスタンス化されたオブジェクトにのみ存在するプロパティも継承してしまいます。

// 上の疑似クラスのサンプルの続き
b.constructor // => Base
s.constructor // => Base // 本当は Sub になってほしい

そこで、次のように継承用のメソッドを定義します。

Function.prototype.extend = function extend( aBaseConstructor ) {
    var T = function() {};
    T.prototype = aBaseConstructor.prototype;
    this.prototype = new T();
    this.prototype.constructor = this;
    this.baseConstructor = aBaseConstructor;
    return this;
};

こうしておくと、extend メソッドを使うだけで継承ができます。 さらに、ベースクラスのコンストラクタの呼び出しも簡単にできるようになります。

var Base = function Base( base ) {
    this.baseValue = base;
};
Base.prototype.doBase = function doBase() { return this.baseValue; };

var Sub = function Sub( base, sub ) {
    // ベースクラスのコンストラクタ呼び出し
    Sub.baseConstructor.apply( this, [base] );
    // サブクラスにおけるプロパティの設定
    this.subValue = sub;
}.extend( Base ); // 継承
Sub.prototype.doSub = function doSub() { return this.subValue; };

// インスタンス化
var b = new Base( "aaa" );
var s = new Sub( "aaa", "bbb" );

詳しい説明については JavaScript におけるクラスの作成と継承 の記事をご覧ください。

*1:ちなみに、ここで 「疑似クラス型の継承」 として紹介している継承の方法は 「疑似クラス型の継承」 の方法として最善だとは言えないと思います。 後でさらに良い方法を示します。