15

我已经看到以下三个代码块作为 JavaScript 模块模式的示例。有什么区别,为什么我会选择一种模式而不是另一种模式?

模式 1

function Person(firstName, lastName) {
    var firstName = firstName;
    var lastName = lastName;

    this.fullName = function () {
        return firstName + ' ' + lastName;
    };

    this.changeFirstName = function (name) {
        firstName = name;
    };
};

var jordan = new Person('Jordan', 'Parmer');

模式 2

function person (firstName, lastName) { 
    return {
        fullName: function () {
            return firstName + ' ' + lastName;
        },

        changeFirstName: function (name) {
            firstName = name;
        }
    };
};

var jordan = person('Jordan', 'Parmer');

模式 3

var person_factory = (function () {
    var firstName = '';
    var lastName = '';

    var module = function() {
        return {
            set_firstName: function (name) {
                               firstName = name;
                           },
            set_lastName: function (name) {
                              lastName = name;
                          },
            fullName: function () {
                          return firstName + ' ' + lastName;
                      }

        };
    };

    return module;
})();

var jordan = person_factory();

据我所知,JavaScript 社区似乎普遍支持模式 3 是最好的。和前两个有什么区别?在我看来,这三种模式都可以用来封装变量和函数。

注意:这篇文章实际上并没有回答这个问题,我不认为它是重复的。

4

3 回答 3

13

I don't consider them module patterns but more object instantiation patterns. Personally I wouldn't recommend any of your examples. Mainly because I think reassigning function arguments for anything else but method overloading is not good. Lets circle back and look at the two ways you can create Objects in JavaScript:

Protoypes and the new operator

This is the most common way to create Objects in JavaScript. It closely relates to Pattern 1 but attaches the function to the object prototype instead of creating a new one every time:

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
};

Person.prototype.fullName = function () {
    return this.firstName + ' ' + this.lastName;
};

Person.prototype.changeFirstName = function (name) {
    this.firstName = name;
};

var jordan = new Person('Jordan', 'Parmer');

jordan.changeFirstName('John');

Object.create and factory function

ECMAScript 5 introduced Object.create which allows a different way of instantiating Objects. Instead of using the new operator you use Object.create(obj) to set the Prototype.

var Person =  {
    fullName : function () {
        return this.firstName + ' ' + this.lastName;
    },

    changeFirstName : function (name) {
        this.firstName = name;
    }
}

var jordan = Object.create(Person);
jordan.firstName = 'Jordan';
jordan.lastName = 'Parmer';

jordan.changeFirstName('John');

As you can see, you will have to assign your properties manually. This is why it makes sense to create a factory function that does the initial property assignment for you:

function createPerson(firstName, lastName) {
    var instance = Object.create(Person);
    instance.firstName = firstName;
    instance.lastName = lastName;
    return instance;
}

var jordan = createPerson('Jordan', 'Parmer');

As always with things like this I have to refer to Understanding JavaScript OOP which is one of the best articles on JavaScript object oriented programming.

I also want to point out my own little library called UberProto that I created after researching inheritance mechanisms in JavaScript. It provides the Object.create semantics as a more convenient wrapper:

var Person = Proto.extend({
    init : function(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    },

    fullName : function () {
        return this.firstName + ' ' + this.lastName;
    },

    changeFirstName : function (name) {
        this.firstName = name;
    }
});

var jordan = Person.create('Jordan', 'Parmer');

In the end it is not really about what "the community" seems to favour but more about understanding what the language provides to achieve a certain task (in your case creating new obejcts). From there you can decide a lot better which way you prefer.

Module patterns

It seems as if there is some confusion with module patterns and object creation. Even if it looks similar, it has different responsibilities. Since JavaScript only has function scope modules are used to encapsulate functionality (and not accidentally create global variables or name clashes etc.). The most common way is to wrap your functionality in a self-executing function:

(function(window, undefined) {
})(this);

Since it is just a function you might as well return something (your API) in the end

var Person = (function(window, undefined) {
    var MyPerson = function(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    };

    MyPerson.prototype.fullName = function () {
        return this.firstName + ' ' + this.lastName;
    };

    MyPerson.prototype.changeFirstName = function (name) {
        this.firstName = name;
    };

    return MyPerson;
})(this);

That's pretty much modules in JS are. They introduce a wrapping function (which is equivalent to a new scope in JavaScript) and (optionally) return an object which is the modules API.

于 2012-12-10T19:19:59.163 回答
4

首先,正如@Daff 已经提到的,这些并不是所有的模块模式。让我们看看它们的区别:

模式 1 与模式 2

你可以省略无用的行

var firstName = firstName;
var lastName = lastName;

从模式 1 开始。函数参数已经是局部范围的变量,正如您在模式 2 代码中看到的那样。

显然,功能非常相似。两者都在这两个局部变量上创建了一个闭包,只有(暴露fullName的)和changeFirstName函数可以访问。不同之处在于实例化时发生的情况。

  • 在模式 2 中,您只需返回一个对象(字面量),它继承自Object.prototype.
  • 在模式 1 中,您将new关键字与称为“构造函数”(并且也正确大写)的函数一起使用。这将创建一个继承自 的对象,Person.prototype您可以在其中放置所有实例将共享的其他方法或默认属性。

构造函数模式还有其他变体。他们可能喜欢对象的 [public] 属性,并将所有方法放在原型上——你可以混合使用。有关模拟基于类的继承的工作原理,请参阅此答案

什么时候用什么?原型模式通常是首选,特别是如果您可能想要扩展所有Person实例的功能 - 甚至可能不是来自同一个模块。当然也有模式 1 的用例,尤其是对于不需要继承的单例。

模式 3

…现在实际上是模块模式,使用闭包创建静态私有变量。您从闭包中导出的内容实际上是无关紧要的,它可以是任何结构/构造函数/对象文字 - 仍然是“模块模式”。

当然,你的模式 2 中的闭包可以被认为是使用“模块模式”,但它的目的是创建一个实例,所以我不会使用这个术语。更好的例子是 Revealing Prototype Pattern 或任何扩展现有对象的东西,使用模块模式的闭包 - 专注于代码模块化。

在您的情况下,模块导出一个构造函数,该函数返回一个对象以访问静态变量。玩它,你可以做到

var jordan = person_factory();
jordan.set_firstname("Jordan");
var pete = person_factory();
pete.set_firstname("Pete");
var guyFawkes = person_factory();
guyFawkes.set_lastname("Fawkes");

console.log(jordan.fullname()); // "Pete Fawkes"

不确定这是否是预期的。如果是这样,获取访问器函数的额外构造函数对我来说似乎有点没用。

于 2012-12-10T23:58:27.827 回答
2

“哪个最好?” 这里不是一个真正有效的问题。
他们都做不同的事情,有不同的权衡,并提供不同的好处。
使用一个或另一个或全部三个(或不使用)归结为您选择如何设计程序。

模式 #1 是 JS 对“类”的传统看法。

它允许,在类 C 语言中prototyping真的应该与它混淆。 原型设计更像是其他语言中的属性/方法。原型方法也无法访问实例变量(即:未附加到的变量)。inheritance
public staticthis

var MyClass = function (args) { this.thing = args; };
MyClass.prototype.static_public_property = "that";
MyClass.prototype.static_public_method   = function () { console.log(this.thing); };

var myInstance = new MyClass("bob");
myInstance.static_public_method();

模式 #2 创建单个对象的单个实例,没有隐式继承。

var MyConstructor = function (args) {
    var private_property = 123,
        private_method = function () { return private_property; },

        public_interface = {
            public_method : function () { return private_method(); },
            public_property : 456
        };

    return public_interface;
};


var myInstance = MyConstructor(789);

没有继承,每个实例都会获得每个函数/变量的副本。
如果您正在处理每页不会有数十万个实例的对象,那么这是非常可行的。

模式 #3 与模式 #2 类似,不同之处在于您正在构建一个构造函数并且可以包含等效的private static方法(您必须在 100% 的时间里传入参数,并且如果该函数旨在返回一个值,而不是直接修改对象或数组,因为这些道具/方法无法访问实例级数据/功能,尽管实例构造函数可以访问所有“静态”功能)。
这里的实际好处是内存占用更少,因为每个实例都有对这些函数的引用,而不是它们自己的副本

var PrivateStaticConstructor = function (private_static_args) {
    var private_static_props = private_static_args,
        private_static_method = function (args) { return doStuff(args); },

        constructor_function = function (private_args) {
            var private_props = private_args,
                private_method = function (args) { return private_static_method(args); },
                public_prop = 123,
                public_method = function (args) { return private_method(args); },

                public_interface = {
                    public_prop   : public_prop,
                    public_method : public_method
                };

            return public_interface;
        };

    return constructor_function;
};


var InstanceConstructor = PrivateStaticConstructor(123),
    myInstance = InstanceConstructor(456);

这些都在做非常不同的事情。

于 2012-12-10T19:31:55.903 回答