JavaScript - 在闭包内使用函数构造函数是一个不好的主意吗?

3

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

这里有一个简单的例子,我知道有很多不同且更好的编写方式,但我只是想提供一个例子,每个构造器现在都可以访问闭包中的变量(farmer、lastToSpeak和animals)。

// 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();

更新

我创建了一个fiddle,而不是在这里的问题添加另一个版本。我完全分开了我的动物构造函数,并将speakAll()移到了原型中。我认为我真正寻找的解决方案允许我在各个实例之间共享变量,而不添加任何东西到全局范围内。最终,我决定向每个实例传递一个对象,而不是构造函数,这意味着我不必在构造函数上公开它们。谢谢大家。


如果每个构造函数确实需要访问闭包变量(例如您的情况中的“farmer”),那么这很好。不过,将它们放在外面并将农场实例作为参数传递可能更方便。 - Bergi
您的最新更新应该没有任何性能问题,但您也可以将 Goat、Cow 和 Sheep 函数定义为独立对象,并将它们注入到 Farm 对象中。除非您想让它们为私有,否则它们完全不需要与 Farm 对象相关定义。 - Kemal Dağ
2个回答

3
在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中使用原型方法有些棘手,因此我不得不修改您的代码的某些方面,您可以在这里看到一个工作示例。

在该示例中,你会发现this.lastToSpeak赋值基本上是错误的。由于你正在使用在Farm对象原型上创建的函数作为对象构造函数本身。然后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而不是new this.Sheep吗?我尝试了一下,它可以工作,只是想确认我是否理解正确。 - Ben
这个答案比我的好。谢谢。 - Kemal Dağ
@Bergi 抱歉,我在看到这个之前发布了我的最新更新。对我来说问题是需要大约10分钟才能理解这里的一切 :), 但之后我肯定可以看出它更好地优化并且相当优雅。非常感激,谢谢。 - Ben

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接