為什麼要使用模組模式?
因為在全域作用域中宣告的變數和函數都會自動成為全域物件Window的屬性,這常常會導致命名衝突,還會導致一些非常重要的可維護性難題,全局變量越多,引入錯誤BUG的概率就越大!所以我們應盡可能少地使用全局變量,模組化的目的之一就是為了解決該問題的!
零全域變數模式
該模式應用場景較少,透過一個IIFE(立即執行的匿名函數),將所有程式碼包裝起來,這樣一來所有的變數、函數都被隱藏在該函數內部,不會污染全域。
使用情境:
# 當程式碼不會被其它程式碼所依賴時;
當不需要在執行時不斷的擴充或修改該程式碼時;
當程式碼較短,且無需和其它程式碼產生互動時;
# 單全域變數模式
基本定義
# 單全域變數模式即只建立一個全域變數(或盡可能少地建立全域變數),且該全域變數的名稱必須是獨一無二的,不會和現在、將來的內建API產生衝突,將所有的函數程式碼都掛載到這個全域變數上。
它已經被廣泛應用於各種流行的類別庫中,如:
YUI定義了唯一的YUI全域物件
JQuery定義了兩個全域對象,$和JQuery
# # Dojo定義了一個dojo全域物件
# Closure定義了一個goog全域物件
var Mymodule= {};
Mymodule.Book = function(){...};
Mymodule.Book.prototype.getName = function(){....};
Mymodule.Car = function(){...};
Mymodule.Car.prototype.getWheels = function(){....};
#一個模組的定義
####模組是一種通用的功能片段,它並沒有創造新的全域變數或命名空間,相反,所有的程式碼都存放在一個單函數中,可以用一個名稱來表示這個模組,同樣這個模組可以依賴其他模組。 ######
function CoolModule(){ var something = 'cool'; var another = [1,2,3]; function doSomething(){ console.log( something); } function doAnother(){ console.log(another.join('!')); } return { doSomething: doSomething, doAnother: doAnother }; } var foo = CoolModule(); foo.doSomething(); //cool foo.doAnother(); //1!2!3###### 這裡的CoolModule 就是一個模組,不過它只是一個函數,這裡調用CoolModule函數來創建一個模組的實例foo,此時就形成了閉包(因為CoolModule返回一個對象,其中的一個屬性引用了內部函數),模組CoolModule返回的物件就是該模組的公共API(也可以直接傳回一個內部函數)############ 所以,模組模式需要具備兩個必要條件:########### ####### 必須有外部的封閉函數,且函數必須至少被呼叫一次(每次呼叫都會建立一個新的模組實例),如CoolModule############# ##### 封閉函數必須至少有一個內部函數被傳回,這樣內部函數才能在私有作用域中形成閉包,並且可以存取或修改私有的狀態############# ##### 單例模組模式的實作:######
var foo = ( function CoolModule(){ ...//代码同上例 })(); foo.doSomething(); foo.doAnother();###### 也可以透過在模組內部保留對公共API物件的內部引用,這樣就可以在內部對模組實例進行修改,包括新增、刪除方法和屬性######
function CoolModule(){ var something = 'cool'; var another = [1,2,3]; function change() { pubicAPI.doSomething = doAnother; } function doSomething(){ console.log( something); } function doAnother(){ console.log(another.join('!')); } var pubicAPI = { change: change, doSomething: doSomething }; return pubicAPI; } var foo = CoolModule(); foo.doSomething(); //cool foo.change(); foo.doSomething(); //1!2!3 var foo1 = CoolModule(); foo1.doSomething(); //cool######### 現代的模組機制############### 命名空間是簡單的透過在在全域變數中加入屬性來表示的功能性分組。 ############ 將不同功能按照命名空間進行分組,可以讓你的單全域變數變得井然有序,同時可以讓團隊成員能夠知曉新功能應該在哪個部分中定義,或去哪個部分找已有功能。 ######
例如:定义一个全局变量Y,Y.DOM下的所有方法都是和操作DOM相关的,Y.Event下的所有方法都是和事件相关的。
常见的用法是为每一个单独的JS文件创建一个新的全局变量来声明自己的命名空间;
每个文件都需要给一个命名空间挂载功能;这时就需要首先保证该命名空间是已经存在的,可以在单全局变量中定义一个方法来处理该任务:该方法在创建新的命名空间时不会对已有的命名空间造成破坏,使用命名空间时也不需要再去判断它是否存在。
var MyGolbal = { namespace: function (ns) { var parts = ns.split('.'), obj = this, i, len = parts.length; for(i=0;i<len;i++){ if(!obj[parts[i]]){ obj[parts[i]] = {} } obj = obj[parts[i]]; } return obj; } }; MyGolbal.namespace('Book'); //创建Book MyGolbal.Book; //读取 MyGolbal.namespace('Car').prototype.getWheel = function(){...}
大多数模块依赖加载器或管理器,本质上都是将这种模块定义封装进一个友好的API
var MyModules = (function Manager() { 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 }; })();
以上代码的核心是modules[name] = impl.apply(impl,deps);,为了模块的定义引入了包装函数(可以传入任何依赖),并且将模块的API存储在一个根据名字来管理的模块列表modules对象中;
使用模块管理器MyModules来管理模块:
MyModules.define('bar',[],function () { function hello(who) { return 'let me introduce: '+who; } return{ hello: hello }; }); MyModules.define('foo',['bar'],function (bar) { var hungry = 'hippo'; function awesome() { console.log(bar.hello(hungry).toUpperCase()); } return { awesome: awesome }; }); var foo = MyModules.get('foo'); foo.awesome();//LET ME INTRODUCE: HIPPO
异步模块定义(AMD):
define('my-books', ['dependency1','dependency2'], function (dependency1, dependency2) { var Books = {}; Books.author = {author: 'Mr.zakas'}; return Books; //返回公共接口API } );
通过调用全局函数define(),并给它传入模块名字、依赖列表、一个工厂方法,依赖列表加载完成后执行这个工厂方法。AMD模块模式中,每一个依赖都会对应到独立的参数传入到工厂方法里,即每个被命名的依赖最后都会创建一个对象被传入到工厂方法内。模块可以是匿名的(即可以省略第一个参数),因为模块加载器可以根据JavaScript文件名来当做模块名字。要使用AMD模块,需要通过使用与AMD模块兼容的模块加载器,如RequireJS、Dojo来加载AMD模块
requre(['my-books'] , function(books){ books.author; ... } )
以上所说的模块都是是基于函数的模块,它并不是一个能被稳定识别的模式(编译器无法识别),它们的API语义只是在运行时才会被考虑进来。因此可以在运行时修改一个模块的API
未来的模块机制
ES6为模块增加了一级语法支持,每个模块都可以导入其它模块或模块的特定API成员,同样也可以导出自己的API成员;ES6的模块没有‘行内’格式,必须被定义在独立的文件中(一个文件一个模块)ES6的模块API更加稳定,由于编译器可以识别,在编译时就检查对导入的API成员的引用是否真实存在。若不存在,则编译器会在运行时就抛出‘早期’错误,而不会像往常一样在运行期采用动态的解决方案;
bar.js
function hello(who) { return 'let me introduce: '+who; } export hello; //导出API: hello
foo.js
//导入bar模块的hello() import hello from 'bar'; var hungry = 'hippo'; function awesome() { console.log(hello(hungry).toUpperCase()); } export awesome;//导出API: awesome
baz.js
//完整导入foo和bar模块 module foo from 'foo'; module bar from 'bar'; foo.awesome();
import可以将一个模块中的一个或多个API导入到当前作用域中,并分别绑定在一个变量上;
module会将整个模块的API导入并绑定到一个变量上;
export会将当前模块的一个标识符(变量、函数)导出为公共API;
模块文件中的内容会被当做好像包含在作用域闭包中一样来处理,就和函数闭包模块一样;
以上是如何實現JS程式碼的模組化的詳細內容。更多資訊請關注PHP中文網其他相關文章!