【问题标题】:How to have an array of classes with static methods when using the closure compiler?使用闭包编译器时如何拥有一组具有静态方法的类?
【发布时间】:2012-10-05 14:48:58
【问题描述】:

我正在开发的 API 有问题。我想要一个类数组,每个类都实现相同的接口并具有静态方法。我将有另一个类,它将循环遍历类数组并根据其中一个静态方法的返回值选择一个,但是编译器正在删除静态方法,并且 javascript 在尝试调用静态方法时遇到 TypeError .我试过搜索几个论坛,但我找不到任何有用的东西。这个错误只出现在高级编译模式下,不简单,甚至在debug模式下高级编译也会出现。

我认为这是因为编译器没有意识到我正在使用该函数,因为我调用它的方式很复杂。

我整理了一个稍微简化的示例,说明我正在尝试执行的操作会导致错误。

代码见下:

Racer 接口是由两个类实现的接口:RunnerJogger。该接口有一个名为 getModesOfTransportation 的静态方法,它返回一组赛车可以移动的方式,以及一个名为 getTransportationModes 的实例方法,它执行相同的操作。该文件还定义了一个自定义类型racing.RacerClass,它(我认为)创建一个类型,该类型是一个函数,当使用new 关键字调用时会返回一个赛车对象。我根据https://developers.google.com/closure/compiler/docs/js-for-compiler#types 形成了这个。我认为我应该定义该类型也有一个静态方法,但我不知道如何。这种自定义类型会不会是错误的罪魁祸首?

racer.js:

    goog.provide('racing.Racer');

    /** @typedef function(new:racing.Racer)*/
    racing.RacerClass={};// My custom typedef

    /**
     * Interface for racers.
     * @interface
     */
    racing.Racer=function(){};

    /**
     * Gets the ways this racer can move.
     * @return {Array.<string>} The ways this racer can move.
     */
    racing.Racer.getModesOfTransportation=function(){}


    /**
     * Gets the ways this racer can move.
     * @return {Array.<string>} The ways this racer can move.
     */
    racing.Racer.prototype.getTransportationModes=function(){}

Helper 类存储 Racer 构造函数列表。 registerRacer 函数接受 Racer 的构造函数并将其添加到列表中。传递的构造函数也会有一个静态的getModesOfTransportation 函数。 getRacers 函数返回已注册的赛车手列表。

helper.js:

    goog.provide('racing.Helper');

    /**
     * A collection of functions to help with finding racers.
     */
    racing.Helper={};


    /**
     * An array of racers.
     * @type {Array.<racing.RacerClass>}
     * @private
     */
    racing.Helper.racers_=[]


    /**
     * Adds the given racer to the list.
     * @param {racing.RacerClass} racer A racer to add to the list of racers.
     */
    racing.Helper.registerRacer=
    function(racer){
            racing.Helper.racers_.push(racer);
    }


    /**
     * Gets an array of registered racers.
     * @return Array.<racing.RacerClass> A list of all registered racers.
     */
    racing.Helper.getRacers=
    function(){
            return racing.Helper.racers_;
    }

Jogger类实现了Racer接口,这两个函数的返回值为['jog']文件末尾,向helper注册自己。

jogger.js:

    goog.provide('racing.Jogger');
    goog.require('racing.Racer');
    goog.require('racing.Helper');

    /**
     * This racer can jog.
     * @constructor
     */
    racing.Jogger=function(){
            console.log('Jogger is going');
    };

    /**
     * Gets the ways this racer can move.
     * @static
     * @return {Array.<string>} The ways this racer can move.
     */
    racing.Jogger.getModesOfTransportation=
    function(){
            return ['jog'];//I can jog
    }


    /**
     * Gets the ways this racer can move.
     * @return {Array.<string>} The ways this racer can move.
     */
    racing.Jogger.prototype.getTransportationModes=
    function(){
            return ['jog'];//I can jog
    }

    //Register this racer
    racing.Helper.registerRacer(racing.Jogger);

Runner类也实现了Racer接口,但是这两个函数的返回值是['run']文件末尾,它自己注册了helper。

runner.js:

    goog.provide('racing.Runner');
    goog.require('racing.Racer');
    goog.require('racing.Helper');

    /**
     * This racer can run.
     * @constructor
     */
    racing.Runner=
    function(){
            console.log('Runner is going');
    };

    /**
     * Gets the ways this racer can move.
     * @static
     * @return {Array.<string>} The ways this racer can move.
     */
    racing.Runner.getModesOfTransportation=
    function(){
            return ['run'];//I can run
    }


    /**
     * Gets the ways this racer can move.
     * @return {Array.<string>} The ways this racer can move.
     */
    racing.Runner.prototype.getTransportationModes=
    function(){
            return ['run'];//I can run
    }

    //Register this racer
    racing.Helper.registerRacer(racing.Runner);

Organizer 类有一个名为startRace 的公共函数,它获取并存储Racer 的一个实例,该实例可以通过调用受保护的getRunner 函数来运行。 getRunner 函数在竞速者列表中循环并试图找到一个可以运行的,但是,一旦代码被编译,racerClass.getModesOfTransportation() 就会失败,指出 getModesOfTransportation 未定义。

organizer.js:

    goog.provide('racing.Organizer');
    goog.require('racing.Helper');
    goog.require('goog.array');

    /** 
     * Class that helps with starting a race.
     * @constructor
     */
    racing.Organizer=function(){}


    /**
     * A racer that can run.
     * @protected
     * @returns {racing.Racer}
     */
    racing.Organizer.prototype.runner=null;


    /**
     * Get a racer that can run.
     * @protected
     * @returns {racing.Racer}
     */
    racing.Organizer.prototype.getRunner=
    function(){
            //Cycle through the racers until we find one that can run.
            goog.array.findIndex(racing.Helper.getRacers(),
                    function(racerClass){
                            if(goog.array.contains(racerClass.getModesOfTransportation(),'run')){
                                    this.runner=new racerClass();
                                    return true;
                            }
                            else return false;
                    },this
            );
    }


    /**
     * Starts a race.
     */
    racing.Organizer.prototype.startRace=
    function(){
            this.runner=this.getRunner();
    }

最终文件只包含编译器的所有类。

api.js:

    //Include the racers
    goog.require('racing.Runner');
    goog.require('racing.Jogger');

    //Include the organizer and export its properties
    goog.require('racing.Organizer')
    goog.exportSymbol('racing.Organizer', racing.Organizer);
    goog.exportProperty(racing.Organizer.prototype, 'startRace', racing.Organizer.prototype.startRace);

在调试模式下对编译代码运行new racing.Organizer().startRace();,会产生以下错误,当我查看编译代码时,getModesOfTransportation 函数不再存在:

    Uncaught TypeError: Object function () {
            console.log("Runner is going")
    } has no method '$getModesOfTransportation$'
    $$racing$Organizer$$$$$startRace$$
    (anonymous function)

我希望能够使其工作,而不必将类拆分为仅具有静态函数的类和仅具有构造函数的类,因为这会使代码混乱。我试图弄清楚这一点,但我做不到。

提前感谢您的任何想法/建议。

【问题讨论】:

    标签: javascript static-methods google-closure-compiler google-closure google-closure-library


    【解决方案1】:

    我认为您可以使用@expose 来阻止编译器删除成员。或者我可能误解了你在问什么。

    【讨论】:

    • 据我所知,@expose 允许外部未编译代码访问该成员。我不需要任何外部代码就可以访问该成员。如果函数被重命名是可以的,但在 getRunner 函数中的 Organizer.js 中对 getModesOfTransportation 的引用也需要重命名。我只希望编译后的代码能够访问该成员。我认为编译器没有意识到我正在使用该函数,因为我将类存储在一个数组中并稍后调用它。
    • 好的,刚刚尝试添加@expose 标签。这解决了问题,但它也阻止了函数被重命名。这是迄今为止我得到的最好的解决方案。
    • @expose 是答案,它确实可以防止重命名。其他解决方案不起作用的原因是它是一种静态方法。
    • @expose 现已正式弃用。作者应该改用@export
    【解决方案2】:

    如上所述,“静态”方法被折叠,@expose 通过导出它们来防止这种情况。替代方法是间接添加属性:

    function helperAddMyProp(obj, value) {
      obj.myProp = value;
    }
    
    /** @construcor */
    function Foo() {};
    helperAddMyProp(Foo, 1);
    

    它可能会在稍后被内联,但在属性被折叠之后。这说明了问题(在closure-compiler.appspot.com 上):

    // ==ClosureCompiler==
    // @compilation_level ADVANCED_OPTIMIZATIONS
    // @output_file_name default.js
    // @formatting pretty_print
    // @debug true
    // ==/ClosureCompiler==
    
    function Foo(name) {
      alert('Hello, ' + name);
    }
    Foo.prop = 2;
    Foo("me");
    alert(Foo.prop);
    

    这是解决方案:

    // ==ClosureCompiler==
    // @compilation_level ADVANCED_OPTIMIZATIONS
    // @output_file_name default.js
    // @formatting pretty_print
    // @debug true
    // ==/ClosureCompiler==
    
    function addProp(obj, value) {
      obj.prop = value;
    }
    function Foo(name) {
      alert('Hello, ' + name);
    }
    addProp(Foo,2);
    Foo("me");
    alert(Foo.prop);
    

    【讨论】:

    • 但是如果编译器崩溃或重命名静态属性,它不应该也重命名对它的引用吗?
    • 它重命名属性和对它的引用。冲突与命名空间折叠有关,它将构造函数视为命名空间。它假设不会有任何对构造函数别名的属性的引用。通过间接设置属性,分析不再将其视为折叠的候选对象,现在其他引用仍然有效。
    • 看起来它可以工作,但是使用@expose 比必须为每个静态成员创建一个函数更容易。 +1 以获得详细答案。
    【解决方案3】:

    如果您将RacerClass 更改为@constructor,它将起作用。

    /** @typedef function(new:racing.Racer)*/
    racing.RacerClass={};// My custom typedef
    

    使用@typedef 仅用于对象属性一致性。它的instanceofObject。但是你需要 racing.RacerClass instanceof 是 RacerClass

    编辑:

    改为:

    /** 
    * @constructor 
    * @implements {racing.Racer} 
    */
    racing.RacerClass={};// My custom typedef
    

    【讨论】:

    • 我无法完全理解您的建议。您发布的代码块正是我的代码中的内容。 Racer 必须是一个接口,因为我的代码中有其他限制,我需要有一个实现 Racer 的类构造函数数组,而不仅仅是实现 Racer 的类数组。
    • 对不起。现在添加了修改后的代码。想想instanceof,你就会明白为什么了。
    • 您的代码将使RacerClass 成为自己的类。我需要RacerClass 来引用Racer 构造函数,以便我可以从RacerClass 类型的数组中初始化一个项目,如下所示:/** @type {Array&lt;RacerClass&gt;} */var arr=[/* stuff here */];new arr[0]();
    • 这段代码看起来很奇怪new arr[0](),我认为它不适用于闭包编译器。请改用简单的工厂方法。
    • 也许我过于简单化了。我实际上是在使用goog.array.findIndex 在阵列中循环。 (请参阅 Organizer.js 中的 getRunner。)对于每个项目(类构造函数),我正在调用该类的静态方法并使用结果来确定是否应该使用该类并对其进行初始化。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-19
    • 1970-01-01
    • 2013-08-25
    • 1970-01-01
    • 1970-01-01
    • 2013-02-13
    相关资源
    最近更新 更多