JavaScript中这三个模块模式实现有什么区别?

时间:2012-12-10 18:56:20

标签: javascript module-pattern

我已经看到以下三个代码块作为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是最好的。与前两个有什么不同?在我看来,所有三种模式都可以用来封装变量和函数。

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

3 个答案:

答案 0 :(得分:13)

我不认为它们是模块模式,而是更多的对象实例化模式。就个人而言,我不会推荐你的任何例子。主要是因为我认为重新分配函数参数除了方法重载之外的其他任何东西都不好。让我们回过头来看看你可以用JavaScript创建对象的两种方法:

原型和new运营商

这是在JavaScript中创建对象的最常用方法。它与模式1密切相关,但是将函数附加到对象原型,而不是每次都创建一个新函数:

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和工厂功能

ECMAScript 5引入了Object.create,它允许以不同的方式实例化对象。您可以使用new来设置原型,而不是使用Object.create(obj)运算符。

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');

如您所见,您必须手动分配属性。这就是为什么创建一个为您执行初始属性赋值的工厂函数是有意义的:

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

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

像往常一样,我必须参考Understanding JavaScript OOP这是关于JavaScript面向对象编程的最佳文章之一。

我还想指出我在研究JavaScript中的继承机制后创建的名为UberProto的小库。它提供Object.create语义作为更方便的包装器:

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');

最后,它并不是关于“社区”似乎更喜欢什么,而是关于理解语言为实现某项任务所提供的内容(在您的情况下创建新的主题)。从那里你可以更好地决定你喜欢哪种方式。

模块模式

似乎与模块模式和对象创建存在一些混淆。即使看起来很相似,它也有不同的责任。由于JavaScript 只有函数作用域模块用于封装功能(并且不会意外地创建全局变量或命名冲突等)。最常见的方法是将您的功能包装在自执行功能中:

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

因为它只是一个功能,你最后还可以返回一些东西(你的API)

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

这几乎是JS中的模块。它们引入了一个包装函数(相当于JavaScript中的一个新作用域)和(可选)返回一个作为模块API的对象。

答案 1 :(得分:4)

首先,正如@Daff已经提到的,这些并非都是模块模式。让我们来看看差异:

模式1与模式2

您可以省略无用的行

var firstName = firstName;
var lastName = lastName;

来自模式1.函数参数已经是本地范围的变量,正如您在模式2代码中看到的那样。

显然,功能非常相似。两者都在这两个局部变量上创建一个闭包,只有(公开的)fullNamechangeFirstName函数可以访问这些变量。不同之处在于实例化。

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

constructor patterns还有其他变体。他们可能喜欢对象的[公共]属性,并将所有方法放在原型上 - 你可以混合使用。有关如何模拟基于类的继承的工作原理,请参阅this answer

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

模式3

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

当然,模式2中的闭包可以被认为是使用“模块模式”,但它的目的是创建一个实例,所以我不会使用这个术语。更好的例子是Revealing Prototype Pattern或任何扩展已有对象的东西,使用Module 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"

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

答案 2 :(得分:2)

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

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

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

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

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

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

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

这些都是非常不同的事情。