マニアックな最小化(バイトの追跡)

ハローワールド



このトピックでは、コードを事前にリファクタリングして最小化を改善する方法について説明します。 リリース前にライブラリを最近最小化しました Helios Kernel昨日書いた )。 ライブラリのソースコードの重量は28112バイトで、コメントが豊富なので、 YUIコンプレッサーによってハーフキックで7083バイトに圧縮されます。 7キロバイトが大胆すぎるとは思えませんでした。 しかし、最小化されたコードを自分の目で見るだけで、さらに節約できる場所がたくさん見つかりました。







7083バイトを4009 3937に変換するコードで何ができるかを見てみましょう。



ただし、開始する前に、2つの注意事項があります。



ミニマイザーの選択



一般に、この記事はミニマイザーの比較に関するものではありませんが、その過程でYUIコンプレッサーには機能のバグがあることに気付きました。1行で構成されるコードブロックから中括弧を削除しません。 さらに、元の文字 (最初の図でWTFタグでマークされている)にない場合でも、中括弧が追加されます。 私はそれを無礼だと思い、ためらうことなく、オンラインミニマイザーhttp://jscompress.com/の使用に切り替えました。 ただし、残りの引数は、任意の最小化子に適用されます。



大きな匿名関数



まず、すぐに呼び出される大規模な匿名関数ですべてのコードをラップしましょう(これが最初に行われない場合)。 次に、この関数のローカルスコープを使用できます。 これがどのようにバイトを節約するかを以下に示します。 匿名関数でコードをラップする最もコンパクトな方法は次のとおりです。

だった になっています
// 
      
      



 !function(){ //  }()
      
      





「プライベート」オブジェクト



確かに、コードにはパブリックAPIに含まれていない多数の補助オブジェクトがあります。 Javascriptにはオブジェクトがプライベートであることを示すネイティブな方法がないため、通常、何らかの種類の規則が使用されます。 ほとんどの場合、このようなオブジェクトにはアンダースコアで始まる名前が付けられます: " _ "。 通常、ミニマイザーはローカル変数の名前を1文字の名前に置き換えますが、「プライベート」オブジェクトの指定方法について大胆な仮定を立てないため、「プライベート」オブジェクトの名前は変更しません。 ただし、最小化されたコードでこれらのオブジェクトがどのように呼び出されるかは重要ではないため、手動で名前を変更できます。

だった になっています
 myObject._somethingPrivate = { // ... }
      
      



 myObject.a = { // ... }
      
      



 MyObj = function() { this.somePublicProperty = ...; this._somePrivateProperty = ...; this._anotherPrivateProperty = ...; }
      
      



 MyObj = function() { this.somePublicProperty = ...; this.a = ...; this.b = ...; }
      
      



 MyObj.prototype._privateMethod = function() { // ... }
      
      



 MyObj.prototype.c = function() { // ... }
      
      





ここで注意する必要があります。 最初に、宣言内だけでなく、それらが使用される場所でも、プライベート関数とプライベート変数の名前を置き換えることを忘れないでください。 次に、コードのロジックを追跡し、名前の重複を避ける必要があります。 たとえば、プロトタイプの一部の型に対して関数aが既に宣言されている場合、このオブジェクトのプライベートプロパティと同じ名前を使用することはできません。 これは明らかなことですが、特に注意を払わないと見逃しがちです。



さらに、多くの場合、プライベートオブジェクトはあらゆる種類のコンストラクター/イニシャライザーで宣言されるだけではありません。 Javascriptを使用すると、その場でオブジェクトを補完できます。 理論的には、コード内のすべてのプライベート識別子は、1文字の識別子に慎重に置き換えることができます。

だった になっています
 MyObj.prototype.getSomething = function() { if ( typeof this._prop == "undefined" ) { this._prop = 0; } return this._prop; }
      
      



 MyObj.prototype.getSomething = function() { if ( typeof this.x == "undefined" ) { this.x = 0; } return this.x; }
      
      





「パブリック」オブジェクト



「パブリック」オブジェクトはAPIの一部であり、元の名前とまったく同じ名前を付ける必要があります。 しかし、「パブリック」オブジェクトがコード内であまりにも頻繁に(たとえば、少なくとも1回)使用され、その名前が長すぎる(たとえば、2バイト以上)場合、エイリアスにすることは理にかなっています。

だった になっています
 myObject = { ... }
      
      



 var a = myObject = { ... }
      
      





この例では、このような変更後、変数aはローカルとして宣言され、変数myObjectはグローバルとして宣言されます(識別子myObjectが初めて使用される場合)。



これで、コードを調べて、宣言されているだけでなく使用されているすべてのオブジェクトを見つけて、エイリアスにすることができます。

だった になっています
 MyObj = function() { this.somePublicProperty = ...; this.a = ...; this.b = ...; }
      
      



 var b = MyObj = function() { this.somePublicProperty = ...; this.a = ...; this.b = ...; }
      
      



 MyObj.prototype.someMethod = function() { // ... }
      
      



 b.prototype.d = b.prototype.someMethod = function() { // ... }
      
      



 someStorage.someMethod = function() { // ... }
      
      



 var c = someStorage.someMethod = function() { // ... }
      
      





繰り返しますが、主なことは、スコープ内で混乱しないようにすることと、同じスコープの変数に同じ名前を付けないことです。 上記の例では、タイプMyObjのオブジェクトにはすでにプライベートプロパティbとプライベートメソッドcがあり、新しいローカル変数bcはBig Anonymous Functionのスコープに入ります。 ?)



さらに、いくつかのパブリックプロパティにエイリアスを作成できますが、複雑なオブジェクトを含むプロパティにのみエイリアスを作成できます。

だった になっています
 AnotherObj = function() { this.someProperty = [ 0, 0, 0 ]; //  this.secondProperty = { a: 1 }; //  this.thirdProperty = 0; //  this.fourthProperty = true; // - this.fifthProperty = "hello"; //  }
      
      



 AnotherObj = function() { this.a = this.someProperty = [ 0, 0, 0 ]; this.b = this.secondProperty = { a: 1 }; this.thirdProperty = 0; this.fourthProperty = true; this.fifthProperty = "hello"; }
      
      





単純なオブジェクトのエイリアスを作成すると、コンテンツがコピーされ、エイリアスは別のオブジェクトを指します。



Varを置く



今度は、 varという単語を使用して、コンマで区切られた変数を宣言できるという事実を活用しましょう。 最も単純な場合、次のようになります。

だった になっています
 someFunction = function() { var a = 0; var b = something(); // ... }
      
      



 someFunction = function() { var a = 0, b = something(); // ... }
      
      



 anotherFunction = function() { var c; // -  var d = something(); //  -  for ( var i = 0; i < ... //   -  }
      
      



 anotherFunction = function() { var c, d = something(), i = 0 // -  //  -  for ( ; i < ... //   -  }
      
      





一般に、関数の先頭ですべての宣言を引き出し、1つのvarを使用してそれらを記述する必要があります。 for()ループの最適化について以下に説明します。 それでも、大規模なハドロン関数内のすべてのローカル宣言を収集し、最初に1つの変数の下に配置する必要があります。 これらは、前のセクションで作成したエイリアスとまったく同じです。 すべてのコードは次のように変換する必要があります。

だった になっています
 !function(){ // -  var b = MyObj = function() { this.somePublicProperty = ...; this.a = ...; this.b = ...; } //  -  var c = b.prototype.someMethod = function() { // ... } //   -  }()
      
      



 !function(){ var b = MyObj = function() { this.somePublicProperty = ...; this.a = ...; this.b = ...; }, c = b.prototype.someMethod = function() { // ... }, //      // -  //  -  //   -  }()
      
      





この例では、変数bcなどはBig Functionに対してローカルに宣言されたままであることに注意してください。 この方法で、関数内にあるのと同じ数の変数を保存します(1つを除いて)。



それでも、コードロジックが変更されていないことを確認する必要があります。 行の順序を変更しているため、理論的には、初期化される前に何らかのオブジェクトが使用される可能性があり、これは許可されません。



プロトタイプ



宣言された型とそのコンストラクターごとに、 protoypeという言葉をかなり節約できます-長すぎます。 これを行うために、このタイプの将来のオブジェクトのプロトタイプ全体を単一のハッシュの形式で記述します。

だった になっています
 MyObj = function() { // ... } MyObj.prototype.someMethod = function() { // ... } MyObj.prototype.anotherMethod = function() { // ... } MyObj.prototype.thirdMethod = function() { // ... }
      
      



 MyObj = function() { // ... } MyObj.prototype = { someMethod : function() { // ... }, anotherMethod : function() { // ... }, thirdMethod : function() { // ... } }
      
      





ご覧のとおり、このためには、「 = 」を「 」に置き換え、メソッド宣言をコンマで区切ることを忘れないでください。 このメソッドは、どこかで宣言された型コンストラクターのプロトタイプを補足する必要がある場合には機能しません(そのようなレコードでプロトタイプを完全に再定義するため)。



サイクルと条件の最適化



ほとんどすべてのサイクルと多くの条件を最適化できます。

だった になっています
 a--; if ( a == 0 ) { // ... }
      
      



 if ( --a == 0 ) { // ... }
      
      



 if ( --a == 0 ) { // ... }
      
      



 if ( !--a ) { // ... }
      
      



 for ( var i = 0; i < a; i++ ) { b( c[ i ] ); }
      
      



 for ( var i = 0; i < a; ) { b( c[ i++ ] ); }
      
      





ただし、ここでも、コードのロジックに違反しないように注意する必要があります。



よく使用される値



複数回使用される値があることがあります。 変数に入れることもできます:

だった になっています
 // ... if ( typeof a == "undefined" ) ... // ... if ( typeof b == "undefined" ) ... // ...
      
      



 var z = "undefined"; // ... if ( typeof a == z ) ... // ... if ( typeof b == z ) ... // ...
      
      



 if ( typeof a != "function" ) { a = function(){} } // ... if ( typeof b != "function" ) { b = function(){} }
      
      



 var f = "function", g = function(){} // ... if ( typeof a != f ) { a = g; } // ... if ( typeof b != f ) { b = g; }
      
      



 el = document.createElement( "script" ); el.type = "text/javascript";
      
      



 var x = "script"; el = document.createElement( x ); el.type = "text/java" + x;
      
      







不要なものをすべて捨てる



コードに「わかりやすくするために」冗長な情報が含まれていることがよくありますが、これは取り除くことができます。 しかし、ここでは、他の場所と同様に、削除するものを注意深く監視する必要があります。

だった になっています
 if ( a.length > 0 ) { b = a.pop() }
      
      



 if ( a.length ) { b = a.pop() }
      
      



 var someEnum = { foo : 0, bar : 1, buz : 2 } // ... var a = []; for ( var i in someEnum ) { a[ someEnum[ i ] ] = 0; } // ... a[ someEnum.bar ] = getSomething(); // ... if ( c.state == someEnum.foo ) { // ... }
      
      



 var someEnum = { foo : 0, bar : 1, buz : 2 } // ... var a = [ 0, 0, 0 ]; // ... a[ 1 ] = getSomething(); // ... if ( !c.state ) { // ... }
      
      





ボーナス: varを削除



これは興味深いトリックで、1つのローカル変数が関数内で宣言されている場合(または変数が初期化なしで宣言されている場合)に役立ちます。 ここでは1つのvarを保存しますが、変数名を複製する必要があります。

だった になっています
 doSomething = function( param1, param2 ) { var i = 0; // .... }
      
      



 doSomething = function( param1, param2, i ) { i = 0; // .... }
      
      



 doSomething = function( param1, param2 ) { var a, b, c; // .... }
      
      



 doSomething = function( param1, param2, a, b, c ) { // .... }
      
      





ここでは、ローカル変数の代わりにパラメーターを使用していますが、それらはまったく同じように動作します。 このトリックは、関数が事前に不明な数のパラメーターを受け取るような場合には適していません。 ほとんどの場合、コード内のほとんどすべてのvarを取り除くことができます。



結果として何が起こったのか



説明した方法でコードを処理した後、スクリプトをjscompress.comサービスにフィードしました。 少し考えた後、彼はこの種のおを4009バイト与えてくれました。 いってらっしゃい!







ところで、私はあなたがこの混乱でトリムできる他のものをコメントで見つけて説明する人にカルマにプラスを与えます:-)



更新する


nano_freelancerはいくつかの良いアイデアを提案しました:

さらに、ほとんどのヌルは0に置き換えることもできます(すべてではありません)。



コードサイズを3937バイトに縮小:-)



オフトピック:私が使用したソースコードと最小化されたコードは、プロジェクトのホームページ( http://home.gna.org/helios/kernel/)からダウンロードできます。



All Articles