【问题标题】:Javascript - Is it a bad idea to use function constructors within closures?Javascript - 在闭包中使用函数构造函数是一个坏主意吗?
【发布时间】:2013-09-16 16:52:18
【问题描述】:

我想知道在闭包中使用函数构造函数时是否存在任何内存或性能问题?

这是一个粗略的例子,我知道有很多不同的更好的方法来写这个,我只是想提供一个例子,每个构造函数现在都可以访问闭包中的变量(农民、lastToSpeak 和动物) .

// usage: myFarm = new Farm(["Cow","Goat"],"Old MacDonald");

function Farm(animalList, farmer){
    var animals = [],
        lastToSpeak = "";

    function Cow(){
        this.speak = function(){
            alert("I'm a Cow and I belong to "+farmer);
        }
        lastToSpeak = "A Cow";
    }
    function Sheep(){
        this.speak = function(){
            alert("I'm a Sheep and I belong to "+farmer);
        }
        lastToSpeak = "A Sheep";
    }
    function Goat(){
        this.speak = function(){
            alert("I'm a Goat and I belong to "+farmer);
        }
        lastToSpeak = "A Goat";
    }

    for(var i = 0; i < animalList.length; i++){
        switch(animalList[i]){
            case "Cow":
                animals.push(new Cow());
                break;
            case "Sheep":
                animals.push(new Sheep());
                break;
            case "Goat":
                animals.push(new Goat());
                break;
        }
    }

    this.allSpeak = function(){
        for(var i = 0; i < animals.length; i++){
            animals[i].speak();
        }
    }
}
myFarm = new Farm(["Cow","Goat"],"Old MacDonald");
myFarm.allSpeak();

我猜在这个例子中它几乎没有什么区别,但是如果牛、绵羊和山羊更大更复杂,并且我们正在创建许多农场,这种方法会有什么缺点吗?

每次创建农场时都会将一组新的构造函数存储到内存中吗?

更新

所以我对 Kemal Dağ 所说的以及 Bergi 的评论感到满意。

如果我按照 Bergi 的建议更改代码以使用原型并在农场中传递,这似乎是更好的方法吗?

function Farm(animalList, farmer){
    var animals = [],
        lastToSpeak = "";

    this.farmer = farmer;

    for(var i = 0; i < animalList.length; i++){
        switch(animalList[i]){
            case "Cow":
                animals.push(new this.Cow(this));
                break;
            case "Sheep":
                animals.push(new this.Sheep(this));
                break;
            case "Goat":
                animals.push(new this.Goat(this));
                break;
        }
    }

    this.allSpeak = function(){
        for(var i = 0; i < animals.length; i++){
            animals[i].speak();
        }
    }
}
Farm.prototype.Goat = function(farm){
    this.speak = function(){
        alert("I'm a Goat and I belong to "+farm.farmer);
    }
    farm.lastToSpeak = "A Goat";
}
Farm.prototype.Cow = function(farm){
    this.speak = function(){
        alert("I'm a Cow and I belong to "+farm.farmer);
    }
    farm.lastToSpeak = "A Cow";
}
Farm.prototype.Sheep = function(farm){
    this.speak = function(){
        alert("I'm a Sheep and I belong to "+farm.farmer);
    }
    farm.lastToSpeak = "A Sheep";
}

myFarm = new Farm(["Cow","Goat"],"Old MacDonald");
myFarm.allSpeak();

更新

我整理了一个小提琴而不是添加另一个版本到问题here。我已经完全分离了我的动物构造函数并将 speakAll() 移到原型中。我想我真的在寻找一种解决方案,它允许我在我的实例之间共享变量而不向全局范围添加任何内容。我最终决定将一个对象传递给每个实例而不是构造函数,这意味着我不必在构造函数上公开它们。谢谢各位。

【问题讨论】:

  • 如果这些构造函数中的每一个都确实需要访问闭包变量(如您的情况下的farmer),那很好。不过,将它们放在外面并将农场实例作为参数传递给它们可能会更方便。
  • 您的最新更新可以正常工作而没有任何性能问题,但您也可以将 Goat、Cow 和 Sheep 函数定义为独立对象,并将它们注入到 Farm 对象中。除非您希望它们是私有的,否则它们完全不需要与 Farm 对象相关地定义。

标签: javascript performance memory


【解决方案1】:

在 javascript 中,每个函数本身都是一个对象,因此您的问题的答案是肯定的。每次创建 Farm 对象时,您也会创建其所有私有方法,它们是:Cow、Sheep、Goat。

为防止这种情况,请在 Farm 对象的原型中创建函数。这可以防止为这些函数重新分配内存,但它们会立即成为公共函数。见:

Farm.prototype.Goat = function(farmer){
        this.speak = function(){
            alert("I'm a Goat and I belong to "+farmer);
        }
        this.lastToSpeak = "A Goat";
}

您必须决定私人功能对您是否至关重要?那么原型方法要好得多。

但是请记住,在 javascript 中使用原型有点棘手,所以我不得不修改你的代码的某些方面,你可以看到一个工作示例 here.

在该示例中,您会发现this.lastToSpeak 分配从根本上是错误的。由于您使用在 Farm 对象原型处创建的函数作为 Object 构造函数本身。然后this 成为对 Goat 对象而不是 Farm 对象的引用,因此如果您想引用父级 Farm 对象,您可能会将对父级的引用传递给 Goat 构造函数。请记住,原型继承与标准类继承完全不同,但您几乎可以模拟任何您想要的行为。

【讨论】:

    【解决方案2】:

    每次创建农场时都会将一组新的构造函数存储到内存中吗?

    是的。但是,如果他们真的需要访问闭包变量(如lastToSpeak),这可能是合理的,就像特权方法一样。拥有私有构造函数可能有点奇怪,但可能是必需的。

    当您处理一组相似(但范围不同)的构造函数时,为它们提供一个通用原型对象可能是一种性能优化。如果原型方法也不需要访问闭包变量,则在 Farm 构造函数之外使用静态对象并将其分配给每个特权构造函数。

    我更改了代码以使用原型,这似乎是更好的方法吗?

    不是这样的。构造函数不应该是实例方法,将它们放在原型对象上很奇怪。最好将它们作为命名空间放在Farm 函数对象上。

    这是一个公开一切,崇拜原型的例子:

    function Farm(animalList, farmer){
        this.farmer = farmer;
        this.lastToSpeak = "";
        this.animals = [];
    
        for(var i = 0; i < animalList.length; i++){
            var name = animalList[i];
            if (Farm.hasOwnProperty(name))
                this.animals.push(new Farm[name](this));
        }
    }
    Farm.prototype.allSpeak = function(){
        for(var i = 0; i < this.animals.length; i++){
            this.animals[i].speak();
        }
    };
    
    function Animal(farm) {
        this.farm = farm;
        farm.lastToSpeak = "A "+this.type;
    }
    Animal.prototype.speak = function(){
        alert("I'm a "+this.type+" and I belong to "+this.farm.farmer);
    };
    Animal.create = function(name) {
        Farm[name] = function() {
            Animal.apply(this, arguments);
        };
        Farm[name].prototype = Object.create(Animal.prototype);
        Farm[name].prototype.type = name;
    };
    Animal.create("Goat");
    Animal.create("Sheep");
    Animal.create("Cow");
    
    myFarm = new Farm(["Cow","Goat"],"Old MacDonald");
    myFarm.allSpeak();
    

    【讨论】:

    • 谢谢,当您说“最好将它们作为名称空间放在 Farm 函数对象上”时,您是指将 Farm.prototype.Sheep 更改为 Farm.Sheep 并调用 new Farm.Sheep 而不是新这个。羊?我试过了,它奏效了,只是想检查一下我是否理解。
    • 这个答案比我的好。谢谢。
    • @Bergi 抱歉,我在查看此内容之前发布了我的最新更新。对我来说问题是我花了大约 10 分钟来理解这里的所有内容:),之后我可以肯定地看到它得到了更好的优化并且非常优雅。非常感谢,谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-31
    • 2012-04-19
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多