Closure Compiler'ın getirdiği kısıtlamaları anlama

Closure Compiler, JavaScript girişinin belirli kısıtlamalara uymasını bekler. Derleyiciden istediğiniz optimizasyon düzeyi ne kadar yüksek olursa derleyici, giriş JavaScript'i üzerinde o kadar fazla kısıtlama uygular.

Bu belgede, her optimizasyon seviyesindeki temel kısıtlamalar açıklanmaktadır. Derleyici tarafından yapılan ek varsayımlar için bu wiki sayfasına da bakın.

Tüm optimizasyon düzeylerindeki kısıtlamalar

Derleyici, işlediği tüm JavaScript'lere tüm optimizasyon seviyeleri için aşağıdaki iki kısıtlamayı uygular:

  • Derleyici yalnızca ECMAScript'i tanır.

    ECMAScript 5, neredeyse her yerde desteklenen JavaScript sürümüdür. Ancak derleyici, ECMAScript 6'daki birçok özelliği de destekler. Derleyici yalnızca resmi dil özelliklerini destekler.

    Uygun ECMAScript dil spesifikasyonuna uygun tarayıcıya özgü özellikler derleyiciyle sorunsuz çalışır. Örneğin, ActiveX nesneleri yasal JavaScript söz dizimiyle oluşturulur. Bu nedenle, ActiveX nesneleri oluşturan kod derleyiciyle çalışır.

    Derleyici bakımcıları, yeni dil sürümlerini ve özelliklerini desteklemek için aktif olarak çalışır. Projeler, --language_in işaretini kullanarak hangi ECMAScript dil sürümünü kullanmak istediklerini belirtebilir.

  • Derleyici, yorumları korumaz.

    Tüm Compiler optimizasyon seviyelerinde yorumlar kaldırılır. Bu nedenle, özel olarak biçimlendirilmiş yorumlara dayanan kodlar Compiler ile çalışmaz.

    Örneğin, derleyici yorumları korumadığından JScript'in "koşullu yorumlarını" doğrudan kullanamazsınız. Ancak koşullu yorumları eval() ifadeleriyle sarmalayarak bu kısıtlamayı aşabilirsiniz. Derleyici, aşağıdaki kodu hata oluşturmadan işleyebilir:

     x = eval("/*@cc_on 2+@*/ 0");

    Not: @preserve ek açıklamasını kullanarak derleyici çıkışının en üstüne açık kaynak lisansları ve diğer önemli metinleri ekleyebilirsiniz.

SIMPLE_OPTIMIZATIONS ile ilgili kısıtlamalar

Basit optimizasyon düzeyi, kod boyutunu küçültmek için işlev parametrelerini, yerel değişkenleri ve yerel olarak tanımlanmış işlevleri yeniden adlandırır. Ancak bazı JavaScript yapıları bu yeniden adlandırma sürecini bozabilir.

SIMPLE_OPTIMIZATIONS kullanırken aşağıdaki yapılardan ve uygulamalardan kaçının:

  • with:

    with kullandığınızda derleyici, aynı ada sahip yerel bir değişken ile nesne özelliği arasında ayrım yapamaz ve bu nedenle adın tüm örneklerini yeniden adlandırır.

    Ayrıca, with ifadesi kodunuzun insanlar tarafından okunmasını zorlaştırır. with ifadesi, ad çözümlemeyle ilgili normal kuralları değiştirir ve kodu yazan programcının bile bir adın neyi ifade ettiğini belirlemesini zorlaştırabilir.

  • eval():

    Derleyici, eval() işlevinin dize bağımsız değişkenini ayrıştırmadığından bu bağımsız değişkendeki sembolleri yeniden adlandırmaz.

  • İşlev veya parametre adlarının dize gösterimleri:

    Derleyici, işlevleri ve işlev parametrelerini yeniden adlandırır ancak kodunuzdaki işlevlere veya parametrelere adlarıyla atıfta bulunan dizeleri değiştirmez. Bu nedenle, işlev veya parametre adlarını kodunuzda dize olarak göstermemelisiniz. Örneğin, Prototype kitaplığı işlevi argumentNames(), bir işlevin parametrelerinin adlarını almak için Function.toString() kullanır. Ancak argumentNames(), kodunuzda bağımsız değişkenlerin adlarını kullanmanıza neden olabilir. Simple mod derlemesi bu tür referansları bozar.

ADVANCED_OPTIMIZATIONS ile ilgili kısıtlamalar

ADVANCED_OPTIMIZATIONS derleme düzeyi, SIMPLE_OPTIMIZATIONS ile aynı dönüşümleri gerçekleştirir ve özelliklerin, değişkenlerin ve işlevlerin genel olarak yeniden adlandırılması, kullanılmayan kodların kaldırılması ve özelliklerin düzleştirilmesi işlemlerini de ekler. Bu yeni geçişler, giriş JavaScript'i üzerinde ek kısıtlamalar uygular. Genel olarak, JavaScript'in dinamik özelliklerinin kullanılması kodunuzda doğru statik analiz yapılmasını engeller.

Global değişken, işlev ve özellik yeniden adlandırmanın etkileri:

ADVANCED_OPTIMIZATIONS'ın küresel olarak yeniden adlandırılması, aşağıdaki uygulamaları tehlikeli hale getirir:

  • Bildirilmemiş harici referanslar:

    Derleyicinin, global değişkenleri, işlevleri ve özellikleri doğru şekilde yeniden adlandırabilmesi için bu global'lere yapılan tüm referansları bilmesi gerekir. Derleyiciye, derlenen kodun dışında tanımlanan semboller hakkında bilgi vermeniz gerekir. Gelişmiş Derleme ve Harici Semboller, harici sembollerin nasıl bildirileceğini açıklar.

  • Dışa aktarılmamış dahili adları harici kodda kullanma:

    Derlenmiş kod, derlenmemiş kodun referans verdiği tüm sembolleri dışa aktarmalıdır. Advanced Compilation and Externs (Gelişmiş Derleme ve Harici Dosyalar) başlıklı makalede, sembollerin nasıl dışa aktarılacağı açıklanmaktadır.

  • Nesne özelliklerine referans vermek için dize adlarını kullanma:

    Derleyici, Gelişmiş modda özellikleri yeniden adlandırır ancak dizeleri asla yeniden adlandırmaz.

      var x = { renamed_property: 1 };
      var y = x.renamed_property; // This is OK.
    
      // 'renamed_property' below doesn't exist on x after renaming, so the
      //  following evaluates to false.
      if ( 'renamed_property' in x ) {}; // BAD
    
      // The following also fails:
      x['renamed_property']; // BAD

    Tırnak içine alınmış bir dize içeren bir özelliğe başvurmanız gerekiyorsa her zaman tırnak içine alınmış bir dize kullanın:

      var x = { 'unrenamed_property': 1 };
      x['unrenamed_property'];  // This is OK.
      if ( 'unrenamed_property' in x ) {};   // This is OK
  • Değişkenleri genel nesnenin özellikleri olarak adlandırma:

    Derleyici, özellikleri ve değişkenleri bağımsız olarak yeniden adlandırır. Örneğin, derleyici aşağıdaki iki foo referansını eşdeğer olmalarına rağmen farklı şekilde ele alır:

      var foo = {};
      window.foo; // BAD

    Bu kod şu şekilde derlenebilir:

      var a = {};
      window.b;

    Bir değişkene genel nesnenin özelliği olarak başvurmanız gerekiyorsa her zaman bu şekilde başvurun:

    window.foo = {}
    window.foo;
    

Implications of dead code elimination

The ADVANCED_OPTIMIZATIONS compilation level removes code that is never executed. This elimination of dead code makes the following practices dangerous:

  • Calling functions from outside of compiled code:

    When you compile functions without compiling the code that calls those functions, the Compiler assumes that the functions are never called and removes them. To avoid unwanted code removal, either:

    • compile all the JavaScript for your application together, or
    • export compiled functions.

    Advanced Compilation and Externs describes both of these approaches in greater detail.

  • Retrieving functions through iteration over constructor or prototype properties:

    To determine whether a function is dead code, the Compiler has to find all the calls to that function. By iterating over the properties of a constructor or its prototype you can find and call methods, but the Compiler can't identify the specific functions called in this manner.

    For example, the following code causes unintended code removal:

    function Coordinate() {
    }
    Coordinate.prototype.initX = function() {
      this.x = 0;
    }
    Coordinate.prototype.initY = function() {
      this.y = 0;
    }
    var coord = new Coordinate();
    for (method in Coordinate.prototype) {
      Coordinate.prototype[method].call(coord); // BAD
    }
        

    Derleyici, initX() ve initY() yöntemlerinin for döngüsünde çağrıldığını anlamadığından bu yöntemlerin ikisini de kaldırır.

    Bir işlevi parametre olarak iletirseniz derleyicinin bu parametreye yapılan çağrıları bulabileceğini unutmayın. Örneğin, Derleyici, aşağıdaki kodu Gelişmiş modda derlerken getHello() işlevini kaldırmaz.

    function alertF(f) {
      alert(f());
    }
    function getHello() {
      return 'hello';
    }
    // The Compiler figures out that this call to alertF also calls getHello().
    alertF(getHello); // This is OK.
        

Nesne özelliği düzleştirmenin etkileri

Gelişmiş modda derleyici, ad kısaltmaya hazırlanmak için nesne özelliklerini daraltır. Örneğin, derleyici şunu dönüştürür:

   var foo = {};
   foo.bar = function (a) { alert(a) };
   foo.bar("hello");

şuna dönüştürülür:

   var foo$bar = function (a) { alert(a) };
   foo$bar("hello");

Bu özellik düzleştirme, daha sonraki yeniden adlandırma adımında daha verimli bir şekilde yeniden adlandırma yapılmasını sağlar. Derleyici, foo$bar ifadesini tek bir karakterle değiştirebilir.

Ancak mülk düzleştirme, aşağıdaki uygulamaları da tehlikeli hale getirir:

  • this'ı oluşturucular ve prototip yöntemleri dışında kullanma:

    Özellik düzleştirme, bir işlevdeki this anahtar kelimesinin anlamını değiştirebilir. Örneğin:

       var foo = {};
       foo.bar = function (a) { this.bad = a; }; // BAD
       foo.bar("hello");

    şu olur:

       var foo$bar = function (a) { this.bad = a; };
       foo$bar("hello");

    Dönüşümden önce this içindeki foo.bar, foo anlamına gelir. Dönüşümden sonra this, genel this'yı ifade eder. Bu gibi durumlarda derleyici şu uyarıyı verir:

    "WARNING - dangerous use of this in static method foo.bar"

    Mülk düzleştirmenin this referanslarınızı bozmasını önlemek için this yalnızca oluşturucular ve prototip yöntemleri içinde kullanın. this, new anahtar kelimesiyle bir oluşturucu çağırdığınızda veya prototype öğesinin özelliği olan bir işlevde net bir anlama sahiptir.

  • Hangi sınıfta çağrıldığını bilmeden statik yöntemler kullanma:

    Örneğin aşağıdaki özelliklere sahipseniz:

    class A { static create() { return new A(); }};
    class B { static create() { return new B(); }};
    let cls = someCondition ? A : B;
    cls.create();
    Derleyici, her iki create yöntemini (ES6'dan ES5'e dönüştürüldükten sonra) daraltır. Bu nedenle cls.create() çağrısı başarısız olur. Bu durumu @nocollapse notuyla önleyebilirsiniz:
    class A {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    class B {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
  • Üst sınıfı bilmeden statik bir yöntemde super kullanma:

    Derleyici, super.sayHi() öğesinin Parent.sayHi() öğesini ifade ettiğini bildiği için aşağıdaki kod güvenlidir:

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends Parent {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();

    Ancak, myMixin(Parent).sayHi derlenmemiş Parent.sayHi değerine eşit olsa bile özellik düzleştirme işlemi aşağıdaki kodu bozacaktır:

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends myMixin(Parent) {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();

    /** @nocollapse */ notuyla bu bozulmayı önleyin.

  • Object.defineProperties veya ES6 getter/setters kullanma:

    Derleyici bu yapıları iyi anlamıyor. ES6 getter ve setter'ları, dönüştürme işlemiyle Object.defineProperties(...) olarak dönüştürülür. Derleyici şu anda bu yapıyı statik olarak analiz edemiyor ve özelliklere erişimin ve özelliklerin ayarlanmasının yan etkisiz olduğunu varsayıyor. Bu durum tehlikeli sonuçlara yol açabilir. Örneğin:

    class C {
      static get someProperty() {
        console.log("hello getters!");
      }
    }
    var unused = C.someProperty;

    Şununla derlenir:

    C = function() {};
    Object.defineProperties(C, {a: // Note someProperty is also renamed to 'a'.
      {configurable:!0, enumerable:!0, get:function() {
        console.log("hello world");
        return 1;
    }}});

    C.someProperty'nin yan etkisi olmadığı belirlendiği için kaldırıldı.