-
Notifications
You must be signed in to change notification settings - Fork 2
模块模式学习
原文链接:http://toddmotto.com/mastering-the-module-pattern/ 翻译:jnotnull
我是一个JS模块模式的超级fan,因此我很乐于和大家分享一些使用场景、这些场景中有哪些不同,以及为什么它们如此重要。模块模式就是我们常说的一种设计模式,非常有用。它非常吸引我,因为它使得作用域变得简单,同时也不会使得涉及过于复杂。
同时它使得代码变得非常简单,便于阅读和使用,巧妙的使用了对象,也不像使用prototype那样膨胀你的代码。我想我会针对模块中最棒的部分分享我的想法,如何掌握它以及它的变量和属性。
为了弄明白模块能给你带来什么,你首先得明白如下函数做了什么:
(function () {
// code
})();
这段代码定义了一个立即执行的函数,这就是著名的IIFE,函数创建了一个新的私有作用域。JS中并没有私有的概念,但是可以创建了一个新的作用域用来包含我们的函数逻辑在里面。所以我们可以只包含我们想要的部分到这个作用域中,其他的放到global中。
创建了新的作用域后,我们需要给我们的代码一个命名空间,这样我们就可以访问里面return出来的方法了。下面让我们来为我们的匿名模块创建一个命名空间。
var Module = (function () {
// code
})();
我们在global中定义了一个模块Module,我们可以在任何地方可以访问它,甚至可以把它传递给其它模块。
你可能已经提说过JS中的私有方法,但是严格意义上说是没有的,不过我们可以创建它。
关于私有方法你会关注它什么呢?作为私有方法我们是不希望用户能够在外部访问到它的。我们可能是用来做服务端访问或者传递敏感数据的,我们不希望把这些方法设为公有,他们可能发送hack,那我们的代码就不完全了。因此我们可以创建闭包来保护我们的代码。这也不仅仅是为了安全,同时也是为了防止命名冲突。我打赌,在你第一次写jQuery、JS代码的时候,你把所有的代码卸载了一个文件中,里面充斥着大量的function。没想到这些都是全局的,你可能会在默写时候会遇到冲突。如果是这样,你就知道为什么去改变它了。
现在让我们使用之前创建的Module,让外界不能够访问。对于模块模式的初学者来说,这个例子能够帮助你理解怎么定义一个私有方法:
var Module = (function () {
var privateMethod = function () {
// do something
};
})();
这个例子声明了一个内部新的作用域内的privateMethod函数,如果我们试图在外部域访问它,会得到一个错误,然后代码终止。我们不希望任何人去访问这个方法,特别是有人可能修改数据或者去访问服务器。
经典的模块会使用return得到一个Ojbect对象,这样通过Module命名空间就可以访问这个对象中绑定的方法了。
一个真实的轻量级的返回带有函数的对象的例子:
var Module = (function () {
return {
publicMethod: function () {
// code
}
};
})();
因为我们返回了一个对象字面量,所以我们能够像访问对象字面量那样访问它们。
Module.publicMethod();
当然也可以不用对象字面的语法访问,一个标注的对象语法例子如下:
var myObjLiteral = {
defaults: { name: 'Todd' },
someMethod: function () {
console.log(this.defaults);
}
};
// console.log: Object { name: 'Todd' }
myObjLiteral.someMethod();
但是对于模式来说,我们不去关心是否用对象字面量,毕竟它能够附有私有方法在上面,我们可以把他当成正常的对象,我们能够访问到这些方法。这就是模块的由来,我们自己来定义哪些是私有的,然后只返回公有的给外部。
让我们再来看一个对象字面量语法,以及一个非常好的模块模式和返回值之前的关系。通常一个模块会返回一个对象,但是如果定义和构建这个对象是摆在我们面前的问题。根据具体场景,我会用一些语法糖。
最简单的方式就是我们上面说的,对象不用声明名称,直接返回一个对象。看下这个代码:
var Module = (function () {
var privateMethod = function () {};
return {
publicMethodOne: function () {
// I can call `privateMethod()` you know...
},
publicMethodtwo: function () {
},
publicMethodThree: function () {
}
};
})();
本域的意思就是一个变量或者方法申明在当前域中。在Conditionizr 项目中,我们使用了一个本地域命名空间,因为文件已经超过了100行,因此很方便看到哪些是公有的哪些是私有而不用去看返回值。着这里,我们可以更加容易的看到哪些是公有的,因为他们有一个本地域命名空间。
var Module = (function () {
// locally scoped Object
var myObject = {};
// declared with `var`, must be "private"
var privateMethod = function () {};
myObject.someMethod = function () {
// take it away Mr. Public Method
};
return myObject;
})();
你会发现在Module模块内部的最后一个,返回了myObject。我们的global模块不会关心内部定义的对象是否有名称,我们只负责把真正的对象发送出去,而不是名字,这对你的编码会非常有帮助。
这个和上面的例子很雷同。但是使用了传统的单个对象语法:
var Module = (function () {
var privateMethod = function () {};
var myObject = {
someMethod: function () {
},
anotherMethod: function () {
}
};
return myObject;
})();
我比较喜欢这种本地域对象语法。因为在这里,我们必须先声明然后才能使用(你必须这么做,使用function myFunction () {}会提升你的函数,如果使用不正确会导致一些问题)。使用var myFunction = function () {};语法让我们不必担心这个,因为在使用前我们都已经定义了,这也是的调试变得非常容易,因为JS会按照我们定义的顺序解析,不会提升函数声明。我也不喜欢上面描述的这种方式,因为多个方法经常多余的巡检,同时也没有一个冥想的域对象命名空间来阻止公有方法被外部访问。
我们已经仔细学习了模块,但是仍有一些东西有待揭秘。现在我们可以创建一个优秀的模块放到代码管理系统中了。从代码中你能够清晰的看到哪些模块被传递给Module了。
var Module = (function () {
var privateMethod = function () {
// private
};
var someMethod = function () {
// public
};
var anotherMethod = function () {
// public
};
return {
someMethod: someMethod,
anotherMethod: anotherMethod
};
})();
我非常喜欢上面的语法,因为非常直观。对于大部分JS模块,这种标准的模块模式模式可以帮助我们决定我们的代码框架。
你可能在读这篇文章过程中想:“如果我定义了私有方法,我如何访问它们呢”。这就就JS强大的地方,它允许我们我们通过公有方法访问私有方法。
var Module = (function () {
var privateMethod = function (message) {
console.log(message);
};
var publicMethod = function (text) {
privateMethod(text);
};
return {
publicMethod: publicMethod
};
})();
// Example of passing data into a private method
// the private method will then `console.log()` 'Hello!'
Module.publicMethod('Hello!');
当然不仅仅是方法,还可以是对象,数组,任何东西:
var Module = (function () {
var privateArray = [];
var publicMethod = function (somethingOfInterest) {
privateArray.push(somethingOfInterest);
};
return {
publicMethod: publicMethod
};
})();
迄今为止我们已经创建了一个非常好的能够返回对象的模块。但是我们想扩展我们的模块我们该怎么做了,如何包含另一个小的模块,如何扩展原始模块。
我们先看下下面代码:
var Module = (function () {
var privateMethod = function () {
// private
};
var someMethod = function () {
// public
};
var anotherMethod = function () {
// public
};
return {
someMethod: someMethod,
anotherMethod: anotherMethod
};
})();
我们假设踏实我们系统的一部分,但是设计的时候我们决定我们不能在core部分加入任何东西,我们需要创建扩展来包含一个独立的模块。
当前我们的模块对象看起来应该这样
但是如果我想增加模块扩展该怎么做了,因此我们在结尾的地方顶一个一个公有的方法,如下代码:
Object {someMethod: function, anotherMethod: function, extension: function}
第三方模块现在可用了,但是我们如何使用呢?让我们创建一个ModuleTwo,传递我们的模块名称,能够访问到我们的扩展对象:
var ModuleTwo = (function (Module) {
// access to `Module`
})(Module);
我们在这个模块里面创建了一个新的方法,对于私有域或者方法访问非常便利。伪代码如下:
var ModuleTwo = (function (Module) {
Module.extension = function () {
// another method!
};
return Module;
})(Module || {});
Module传递到了ModuleTwo,增加了一个扩展方法后就返回了。我们的对象贯彻其中,这就是JS的可扩展性。
我的这个模块现在已经有了一个第三放属性了。
// Object {someMethod: function, anotherMethod: function, extension: function}
console.log(Module);
有个提示,你会发现我传递了Module || {} 到ModuleTwo,这是防止Module是未定义的-我们不想引起错误。这里做的就是初始化一个新的对象,然后绑定了我们的扩展方法,然后返回。
我个人非常喜欢《模块模式揭秘》,到目前为止,我会对许多方法做点睛之笔,虽然看起来很像。我有时会创建一个本地域对象,但是有时候不会。如果我不创建,我如何区分出私有的变量和方法呢?请使用_字符吧。你可能在网上经常看到它,现在你知道为什么了吧:
var Module = (function () {
var _privateMethod = function () {
// private stuff
};
var publicMethod = function () {
_privateMethod();
};
return {
publicMethod: publicMethod
};
})();