高品質コードの作成:JavaScriptプログラムの改善提案--対象向けプログラミング


JavaScriptは、オブジェクトに基づく弱タイプの言語で、オブジェクトをベースにした関数をモデルとして、プロトタイプを継承機構とした開発モードです.
提案1:Object構造体系を参照してprototypeメカニズムを分析する.
オブジェクト(Object)はプロトタイプがなく、プロトタイプ属性でプロトタイプにアクセスできる構造関数のみを持っています.プロトタイプ表示類の原型は、構造類が持つオリジナルメンバーです.コンストラクタのprototype属性は、プロトタイプオブジェクトを指す参照オブジェクトポインタを格納しています.
すべての関数は、定義時にプロトタイプの属性を自動的に作成し初期化しました.この初期化されたプロトタイプの属性は、一つのconstructor属性だけを含むオブジェクトを指し、このconstructor属性はこのfunction自身を指します.
var obj = new Object()
console.log(obj.constructor); // function Object() { [native code] }
コンストラクタが実用化されると,すべてのインスタンスオブジェクトがコンストラクタのプロトタイプメンバーにアクセスできる.プロトタイプオブジェクトにメンバを宣言すると、すべてのインスタンスオブジェクトが共有されます.
ObjectとFunctの関係は非常に微妙で、それらは高度に抽象的なタイプで、互いに相手の実例です.
console.log(Object instanceof Function); // true
console.log(Function instanceof Object); // true
プロトタイプ関係は、新しい属性をプロトタイプに追加すると、そのプロトタイプに基づいて作成されたすべてのオブジェクトにすぐに継承されます.
function P(x){this.x = x;}
var p1 = new P("p1");
var p2 = new P("p2");
P.prototype.y = 1;
console.log(p1.x, p1.y); // p1 1
console.log(p2.x, p1.y); // p2 1
提案2:対象属性値を直接検索しない
var modelValue = obj.obj_1.model; // TypeError
var modelValue = obj.obj_1 && obj.obj_1.model; // undefined
確実にするために、上記のエラーは&&演算子で回避できます.
提案3:原型反射防止
for...in文を使用すると、オブジェクト内のすべての属性(プロトタイプを含む)を遍歴できます.Object.keys()は、エニュメレート・属性のみを巡回している.
  • は、原型属性hasOwnPropertyを用いてフィルタリングする.
  • は、typeof演算子排除方法関数
  • を使用する.
    for(var name in obj) {
        if(obj.hasOwnProperty(name) && typeof obj[name] !== 'function') {
            console.log(name, obj[name]);
        }
    }
    提案4:慎重に対象のScopeを処理する
    JavaScriptでは、functionは動作範囲ではなく、動作範囲に作用します.
    functionを対象とする方法で動作する場合、thisはオブジェクトの参照です.このfunctionが対象となる方法がない場合、thisはグローバルオブジェクトを表します.
    var a = 1;
    function f1() {
        var a  = 2;
        function f2() {
            console.log(this.a); // this  window
        } 
        f2();
    }
    f1(); // 1
    上記の問題を解決するための通常のやり方は、外部functionのthis値をある変数に保存し、内部関数でより使います.
    var obj = {
        myVal: 'hello',
        fn: function() {
            var self = this;
            function inner() {
                console.log(self.myVal);
            }
            inner();
        }
    }
    obj.fn();  // hello
    JSON文字列に括弧を加えると、eval方法はJavaScriptコードを評価する際に強制的に式として実行させ、文としてではなくJSONオブジェクトを得ることができる.
    var JSONString = '{"name":"ligang","age":27}';
    var JSONObject = eval("(" + JSONString + ")");
    console.log(JSONObject.name);
    注意:eval及びwindow.eval
    function testEvalScope() {
        eval('var a = 1');
        console.log(a);
    }
    testEvalScope();
    console.log(typeof a); // undefined
    function testEvalScope() {
        window.eval('var a = 1');
        console.log(a);
    }
    testEvalScope();
    console.log(typeof a); // number
    提案5:thisは動的指針であり、静的参照ではない.
    thisの対象はthisのある実行領域(実行環境)によって決定され、thisのある定義領域によって決定されるものではない.これは常に現在のオブジェクトを指します.JavaScriptにおける類似の指針特性の表示は、次の3つがあります.
  • calee:関数のパラメータセットに含まれる静的ポインタは、常にパラメータセットが属する関数を指す.
  • prototype:関数に含まれる半静的なポインタは、デフォルトの状態では常に関数に付随するプロトタイプオブジェクトを指していますが、このポインタを他のオブジェクトに向けて変更することができます.
  • constructor:オブジェクトに含まれるポインタは、常にオブジェクトを作成する構造関数を指す.
  • (1)関数の参照と呼び出し
    参照関数は、関数の実行スコープを変更することができますが、関数を呼び出しても、関数の実行スコープを変更しません.
    var obj = {
        name: '  obj',
        f: function() {
            return this;
        }
    };
    obj.o1 = {
        name: '  o1',
        me: obj.f   //     obj   f
    };
    obj.o2 = {
        name: '  o2',
        me: obj.f() //     obj   f
    };
    console.log(obj.o1.me().name); //   o1
    console.log(obj.o2.me.name);   //   obj
    (2)callとappy
    call、appyは、実行関数のスコープを直接変更することができます.
    function f() {
        if(this.constructor === arguments.callee) console.log('    ');
        else if(this === window) console.log('windwo  ');
        else console.log(`     constructor = ${this.constructor}`);
    }
    
    f();        // windwo  
    new f();    // new f()
    f.call(1);  //      constructor = function Number() { [native code] }
    (3)非同期呼び出し
    関数の呼び出し時間とタイミングは、イベント機構またはタイマーによって遅延または調整されます.関数の実行のためのスコープは、元の定義されたスコープではなく、すべての関数の中でthisは、イベントが発生するオブジェクトまたはwindowオブジェクトを常に指す.
    var obj = {
        value: 123,
        f: function() {
            console.log(this.value, arguments);
        }
    };
    
    var btn = document.getElementById('btn');
    btn.addEventListener('click', obj.f); //  
    btn.addEventListener('click', obj.f.bind(obj, event)); // 123
    var obj = {
        f: function() {
            if(this == obj) console.log("this = obj");
            if(this == window) console.log("this = window");
        }
    }
    obj.f(); // this = obj
    setTimeout(obj.f, 1000); // this = window
    setTimeout(function(){
      obj.f.call(obj)
    }, 1000); // this = obj
    setTimeout(obj.f.bind(obj), 1000); // // this = obj
    提案6:享元類の使用
    享元類はクラスのタイプで、つまりタイプを作るクラスです.享元類は、クラスをパラメータとして受け入れることができます.つまり、クラス、クラスに戻ります.
    function O(x) {
        return function() {
            this.x = x;
            this.get = function() {
                console.log(this.x);
            }
        }
    }
    var o = new O(1);
    var f = new o();
    f.get(); // 1
    コンストラクタが戻り値を持ち、戻り値が参照タイプである場合、new演算子によって計算された後、戻ってくるのはコンストラクタ自身の対応するインスタンスオブジェクトではなく、コンストラクタに含まれる戻り値(すなわち参照タイプ値)です.
    function F() {
        this.x = 1;
        return function() {
            return this.x;
        }
    }
    F.prototype.y = function() {
        console.log("call y");
    }
    
    var f = new F();
    console.log(f.x);   // undefined,         
    console.log(F()()); // 1,this  window
    console.log(f.y()); // TypeError,