メインコンテンツへ移動

『JavaScript 言語精粹』の関数化

無料2015-05-07#JS#JavaScript语言精粹

学習ノートのこの小さな部分だけが欠けていましたが、ついに完成しました。初めてこの部分を読んだ時は理解できず、2 回目も理解できず、3 回目で少し感覚がつかめ、4 回目で感覚が間違っていることに気づきました。。今はほぼ理解できましたので、整理してみましょう

はじめに

多くの書評や読書ノートで『JavaScript 言語精粹』は字字珠玑だと書かれており、名に偽りなし。。もちろん、理解できてこそですが

実は個人的に関数化の部分はあまり良くないと思っています。挙げられている例があまり適切ではなく、之前理解できなかったのは成功して誤導されたからです。『Head First』デザインパターン第 1 章『戦略パターン』 と同じように、著者が一部の章のテーマから逸脱し、読者が誤導されやすくなっています

声明:とりあえず関数化の部分で提供されているオブジェクトを作成するための関数を*「創造関数」と*呼びましょう。「コンストラクタ関数」と区別するためです。。あまり良い名前ではありませんが、我慢して使いましょう

一。ソースコードで注意すべき点

簡単にソースコードを入手でき、中文版の本のコードと同じです。注意深く見直して非常に巧妙な点を 1 つ発見しましたが、もちろん、あまり理解しやすくありません

P.S. ソースコードに小さな問題があります:「創造関数」cat の波括弧が一致していません。中文版 54 ページ、return that; の前に};が不足しています

###Object のプロトタイプ関数 superior の内部に非常に巧妙な点があります

Object.method('superior', function (name) {
    var that = this,
        method = that[name];
    return function (  ) {
        return method.apply(that, arguments);
    };
});

ハイライトは最後の文の arguments で、一見無心ですが、実は意図的なもので、superior で親クラスメソッドを呼び出す時にもパラメータを渡せることを示しています。もちろん、パラメータを渡す場合は呼び出し方法を変更する必要があります。テストコードは以下の通りです:

Function.prototype.method = function (name, func) {
    this.prototype[name] = func;
    return this;
};

var mammal = function (spec) {
    var that = {};

    that.get_name = function (  ) {
        return spec.name;
    };

    that.says = function (  ) {
        return spec.saying || '';
    };

    return that;
};

var cat = function (spec) {
    spec.saying = spec.saying || 'meow';
    var that = mammal(spec);
    that.purr = function (n) {
        var i, s = '';
        for (i = 0; i < n; i += 1) {
            if (s) {
                s += '-';
            }
            s += 'r';
        }
        return s;
    };
    that.get_name = function (  ) {
        alert("cat.get_name :" + arguments.length);///
        return that.says(  ) + ' ' + spec.name +
                ' ' + that.says(  ) + "[" + arguments.length + "]";
    };
    
    return that;
};

Object.method('superior', function (name) {
    var that = this,
        method = that[name];
    return function (  ) {
        alert("superior :" + arguments.length);///
        return method.apply(that, arguments);
    };
});

var coolcat = function (spec) {
    var that = cat(spec),
        super_get_name = that.superior('get_name');
    that.get_name = function () {
        alert("coolcat.get_name :" + arguments.length);///
        return 'like ' + super_get_name.apply(this, arguments) + ' baby';
    };
    return that;
};

var myCoolCat = coolcat({name: 'Bix'});
var name = myCoolCat.get_name(1, 2, 3);
//        'like meow Bix meow baby'


alert(name);    // 'like meow Bix meow[3] baby'

P.S. 最初は superior 関数の最後の arguments は著者の誤りだと思い、外の arguments オブジェクトを中にではなく method に渡すべきだと思いました。大きく迂回して自分が間違っていたことに気づきました。修行が足りず、ダグラス様の意味を瞬時に理解できませんでした。。

二。関数化の初衷

関数化の部分は冒頭で初衷:プライベート属性を実現するために、最後に言及されている「偽造防止オブジェクト」を作成すると説明しています

目的は非難の余地なく、プライベート属性を実現することは非常に必要です。しかし、挙げられている例 mammal -> cat -> coolcat はあまりにも不適切です。著者は関数化の方法で継承を実現できることを説明したいのです

もちろん、厳密な意味での継承ではありません。関数化の方法はカスタムタイプを使用していないため、サブクラスインスタンスと親クラスインスタンスの is-a 関係について語る余地もありません

P.S. 1 回目に見た時 cat の例が私を溝に連れて行き、関数化は new を捨てて、完全に関数で継承を実現することだと思いました。。当然溝の中で越走越深くなりました

三。関数化の思想

直接コードを見ましょう。コード自身が語ります:

/*
 * 関数化の思想:
 * 1.オブジェクトを作成
 * 2.プライベート属性を追加
 * 3.公開インターフェース(パブリック属性を追加)
 * 4.該オブジェクトを返す
 */
/*
 * method: getSuper
 * @param spec 仕様説明オブジェクト、オブジェクト作成に必要な基本データを提供
 * @param my「創造関数」間でデータを共有するコンテナ
 */
function getSuper(spec, my){
    var obj;            // 返すオブジェクト
    var my = my || {};  // 渡されなければ作成する
    
    // プライベート属性
    var attr = spec.value;  // 仕様説明オブジェクトからデータを取得
    var fun = function(){
        alert(attr);
    }
    
    // [オプション] 他の「創造関数」と共有する必要があるデータを my に装入
    
    // オブジェクトを作成、任意の方法で使用可能、例えば new、リテラル、他の「創造関数」を呼び出す
    obj = {
        name: "SuperObject"
    };
    
    // 公開インターフェース
    obj.fun1 = fun;
    
    // obj を返す
    return obj;
}

/*
 * method: getSub
 * パラメータは上記と同じ
 */
function getSub(spec, my){
    var obj;
    var my = my || {};
    
    // プライベート属性
    var attr = spec.value + 1;
    var fun = function(){
        alert(attr);
    }
    
    // [オプション] 共有
    
    // オブジェクトを作成
    obj = getSuper(spec, my);   // 直接渡すことも可能、もちろん変更して渡すことも、他の何かを渡すことも
    
    // 公開インターフェース
    obj.fun2 = fun;
    
    // obj を返す
    return obj;
}

// テスト
var spec = {
    value: 1
};
var sub = getSub(spec); // my を渡す必要はない、my は「創造関数」間でのみ使用するべき
sub.fun1(); // 1
sub.fun2(); // 2

P.S. また「オブジェクトを作成 -> 強化 -> 新しいオブジェクトを返す」この套路です。ニコラスが言うダグラスが発明した「モジュールパターン」ではありませんか?

四。偽造防止オブジェクト(持続性のオブジェクト)

関数化部分の核心はこれです。上記の例の公開インターフェースの方法に注意してください:

// プライベート属性
var myFun = function(){/* ... */};
// 公開インターフェース
obj.fun = myFun;

直接以下を使用するのではなく:

// 公開インターフェース
obj.fun = function(){/* ... */};

第 1 の方法*より安全*です。外部から fun を変更しても、内部の他の myFun を呼び出すメソッドは引き続き正常に動作できるため、このような関数オブジェクトがいわゆる*偽造防止オブジェクト*です

完全な定義:

偽造防止オブジェクトの属性は置換または削除可能ですが、該オブジェクトの完全性は損なわれません

持続性のオブジェクトとも呼ばれ、持続性オブジェクトとは単純な機能関数の集合です

後日談

これで『JavaScript 言語精粹』の学習ノートは一段落しました。[関数化] の欠落を補い、学習ノートの他の部分については [黯羽軽揚:『JavaScript 言語精粹』学習ノート](http://ayqy.net/blog/《JavaScript 言語精粹》学习笔记/) をご覧ください

コメント

コメントはまだありません

コメントを書く