js的模块模式被定义为给类提供私有和公共封装的一种方法,也就是我们常说的“模块化”。
怎么实现“模块化”?
通过闭包的原理来实现“模块化” ,具体实现:1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例);2.封闭函数必须返回至少一个内部函数(返回多个函数时,以对象字面量的形式返回)
先看一个实例:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6 </head> 7 <body> 8 <script> 9 function Module (){
//内部变量 10 var something = 'cool'; 11 var another = [1,2,3]; 12 //内部函数 13 function doSomething(){ 14 console.log(something); 15 } 16 17 function doAnother(){ 18 console.log(another); 19 } 20 //返回对象字面量形式,里面包含内部函数的引用, 这样就保持了内部变量是隐藏且私有的状态。 21 return { 22 doSomething: doSomething, 23 doAnother: doAnother 24 }; 25 } 26 //调用外部函数Module创建一个模块实例foo 27 var foo = Module(); 28 foo.doSomething();//cool 29 foo.doAnother();//[1,2,3] 30 </script> 31 </body> 32 </html>
简单分析一下代码:
首先,Module只是一个函数,必须通过他才能创建一个模块实例,如果不执行他,内部作用域和闭包都无法被创建。 其次,Module函数返回一个字面量对象,这个返回的对象含有对内部函数而不是内部变量的引用。这样就保持了内部变量是隐藏且私有的状态。可以将这个对象类型的返回值看作模块的公共API 这个API最终会被赋值给外部的变量foo,通过他就可以访问API中的属性方法,比如:foo.doSomething()。
上面的实例中,Module函数可以调用任意多次,每次调用都会创建一个新的模块实例。当只需要一个实例时,可以对这个模块进行简单的改进来实现单例模式:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script> var foo = (function Module (){ var something = 'cool'; var another = [1,2,3]; function doSomething(){ console.log(something); } function doAnother(){ console.log(another); } return { doSomething: doSomething, doAnother: doAnother }; })(); foo.doSomething();//cool foo.doAnother();//[1,2,3] </script> </body> </html>
通过将模块函数转换为IIFE(立即执行函数),立即调用这个函数并将返回值直接赋值给电力的模块实例foo
模块也是普通的函数,因此可以接收参数:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script> // var foo = (function Module (){ // var something = 'cool'; // var another = [1,2,3]; // function doSomething(){ // console.log(something); // } // function doAnother(){ // console.log(another); // } // return { // doSomething: doSomething, // doAnother: doAnother // }; // })(); // foo.doSomething();//cool // foo.doAnother();//[1,2,3] var foo = (function Module(id){ function change(){ publicAPI.id = id2; } function id1(){ console.log(id); } function id2(){ console.log(id.toUpperCase()); } var publicAPI = { change: change, id: id1 }; return publicAPI; })('hello'); foo.id();//hello foo.change(); foo.id();//HELLO </script> </body> </html>
可以看出:通过在模块实例的内部保留公共API对象的内部引用(API指的是引用return回来的字面量对象 {...}),可以从内部模块实例进行修改,包括添加、删除方法和属性,以及修改它们的值。
现代的模块机制
大多数模块依赖加载器/管理器,本质上都是将模块定义为封装进一个API。 现在,宏观了解一下模块机制:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script> var myModule = (function Module(){ var modules = []; //定义一个define函数用于定义一个模块 function define(name, deps, impl){ for (var i = 0;i < deps.length;i++){ deps[i] = modules[deps[i]]; } modules[name] = impl.apply(impl, deps); } function get(name){ return modules[name]; } return { define: define, get: get }; })(); </script> </body> </html>
这段代码的核心就是:modules[name] = impl.apply(impl, deps)。 为了模块的定义(define函数)引入包装函数(可以传入任何依赖),并且将返回值,也就是模块的API,存储在一个根据名字来管理的模块列表中。(不是很理解啊??)
下面展示了如何使用它来定义模块:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <script> var myModule = (function Module(){ var modules = []; function define(name, deps, impl){ for (var i = 0;i < deps.length;i++){ deps[i] = modules[deps[i]]; } modules[name] = impl.apply(impl, deps); } function get(name){ return modules[name]; } return { define: define, get: get }; })(); myModule.define('bar', [], function(){ function hello(who){ return "Hello," + who; }; return{ hello:hello }; }); myModule.define('foo', ['bar'], function(bar){ var hungry = 'hippo'; function awesome(){ console.log(bar.hello(hungry).toUpperCase()); }; return { awesome: awesome }; }); var bar = myModule.get('bar'); var foo = myModule.get('foo'); console.log(bar.hello('hippo'));//hello, hippo foo.awesome();//HELLO, HIPPO </script> </body> </html>
‘foo’和‘bar’模块都是通过一个返回公共API的函数来定义的。‘foo’甚至接受‘bar’的实例作为依赖参数,并使用它。
这就是模块的威力!!
总结一下:模块并不是什么神秘的东西,他只是一个外部函数返回内部函数的引用(字面量对象的形式返回),从而可以访问内部函数和变量的一种方式。
---摘自《你不知道的JavaScript》(上) 2017-3-22 23:24