js最初做爲一個在瀏覽器中運行的腳本語言,設計的目標是用來給html增長交互行爲,早期的網站都是在服務器端生成並返回給瀏覽器,js也只對單獨的一個html進行操做,因此模塊化並無在早期的JS中獲得很好的考慮,隨着瀏覽器js引擎愈加的快速,如今已經有不少前端框架,並不依賴與服務器生成html,而是本身直接經過js來生成,最典型的例子就是咱們常聽到的webapp。如今全部的js庫都包裝的很是好了,咱們今天看看一些js模塊化的基礎知識吧。javascript
在js中如何實現命名空間,咱們來看一個例子。html
<!DOCTYPE html> <html> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <body> <button id="mybtn" onclick="display();">點我加載列表</button> <h1>我想要不死族的英雄</h1> <ul id="mylist"></ul> <script type="text/javascript" src="./namespacejs1.js"></script> <script type="text/javascript" src="./namespacejs2.js"></script> </body> </html>
咱們先定義一個html文件。點擊裏面的按鈕,顯示魔獸爭霸的軍隊。響應按鈕的代碼分別定義在namespacejs1.js和namespacejs2.js兩個文件中。
這是namespacejs1.js前端
var list = ['死亡騎士','巫妖','恐懼魔王']; function display(){ var mylist = document.getElementById("mylist"), fragment = document.createDocumentFragment(), element; for(var i = 0, x = list.length; i<x; i++){ element = document.createElement("li"); element.appendChild( document.createTextNode( list[i]) ); fragment.appendChild(element); } console.log(importGlobalVariable); mylist.appendChild(fragment); }
這是namespacejs2.jsjava
var list = ['聖騎士','大法師','山丘之王'];
當咱們點擊按鈕的時候,會顯示什麼東東呢。咱們指望顯示不死族的英雄(namespacejs1.js中的list),可是這裏會顯示人類的英雄(namespacejs2.js中的list)。顯然,咱們想要的數據被覆蓋了。怎麼樣才能解決這個問題呢?咱們來加上名稱空間吧。看看改進後的namespacejs1.js。node
var namespace1 = { list : ['死亡騎士','巫妖','恐懼魔王'], display : function display(){ var mylist = document.getElementById("mylist"), fragment = document.createDocumentFragment(), element; for(var i = 0, x = list.length; i<x; i++){ element = document.createElement("li"); element.appendChild( document.createTextNode( this.list[i]) ); fragment.appendChild(element); } mylist.appendChild(fragment); } };
這時候點擊按鈕會沒有用,須要稍微改動一下web
<button id="mybtn" onclick="namespace1.display();">點我加載列表</button>
能夠看到display在namespace1的名稱空間下面了。問題已經圓滿解決。可是若是咱們想擴展namespace1的功能怎麼辦呢,難道只能修改namespace1.js的源文件嗎,還有list很不安全啊,誰均可以改動它,如何把它變成私有變量呢?編程
在js中並無私有公有的直接支持,可是不表明js語言不能完成這個。咱們看一下如何隱藏list。瀏覽器
var namespace1 = (function(){ var importGlobalVariable = gv; var list = ['死亡騎士','巫妖','恐懼魔王']; function display(){ var mylist = document.getElementById("mylist"), fragment = document.createDocumentFragment(), element; for(var i = 0, x = list.length; i<x; i++){ element = document.createElement("li"); element.appendChild( document.createTextNode( list[i]) ); fragment.appendChild(element); } mylist.appendChild(fragment); } return { display:display }; })();
這段代碼返回了一個對象,在這個對象中咱們只能訪問display方法,是否是很牛逼呢,這樣就解決隱藏問題。安全
咱們知道js的繼承是用原型鏈來實現的,可是這裏要討論的是模塊的擴展,因此這邊不會說道繼承的問題。如何擴展namespace1的功能呢。我麼看一下下面的代碼。前端框架
var namespace1 = (function(n1){ var listArmy = ['4個蜘蛛','2個食屍鬼','2個冰龍'] n1.displayArmy = function(){ n1.display(); var mylist = document.getElementById("mylist"), fragment = document.createDocumentFragment(), element; for(var i = 0, x = listArmy.length; i<x; i++){ element = document.createElement("li"); element.appendChild( document.createTextNode( listArmy[i]) ); fragment.appendChild(element); } mylist.appendChild(fragment); } return n1; })(namespace1);
這段代碼咱們用來增長一個displayArmy的方法,用來顯示不死族軍隊,也就是listArmy的數據吧。
咱們看到上面的代碼把namespace1做爲參數傳入到一個馬上調用函數中,這樣在裏面給它增長一個函數。有沒有感受js很強大啊。
若是已經習慣了C,C++,C#或者Java,那麼上面的實現簡直匪夷所思,感受變量的做用域和生命週期都很奇怪。那麼咱們說說js中的一個重要概念閉包吧。
Closures are functions that refer to independent (free) variables.
這裏就不翻譯了,由於翻譯過來實在是很奇怪,好比,閉包是那些引用了獨立變量的函數。那麼按照定義是否能夠認爲函數就是閉包呢,爲了搞清楚閉包的概念,咱們須要瞭解函數對象,變量生命週期,和嵌套的做用域三個概念。
什麼是函數對象呢,在C語言中和C++語言中,能夠想函數那樣用()去調用,看起來和函數同樣的對象就叫函數對象。好比在C語言中:
typedef void (*func_t)(int); //定義函數指針 //定義一個函數 void f(int n){ printf("node(?)=%d\n",n); } int main(){ func_t pf = f; f(1); }
能夠看到f很像一個函數吧,可是呢,其實它只是一個函數指針而已。在C++語言中,咱們也看一個例子。
class Foreach { private: struct node * myList; public: Foreach(){ } ~Foreach(){} void operator()(){ //作點有意義的事情吧 } }; int main(){ Foreach *foreach = new Foreach(); (*foreach)(); }
能夠看到foreach對象也很像函數。
咱們在看一下js中的函數對象吧。
var f = function(){}; f();
看看,是否是很簡單。
咱們已經看到js的函數對象的定義已經很方便了,那麼還有什麼和傳統語言不同的地方呢?
咱們看一下這個代碼。
var x = 10; function f(){ y=15; function g(){ var z=25; alert(x+y+z); } g(); } f();//顯示50
這段代碼在C語言中無法實現,緣由是C語言的變量沒法訪問當前做用域外的變量。也就是說函數g裏面訪問不了y,函數f也訪問不了x。可是js卻能作到。咱們看一下這樣有什麼強大的地方。
#include <stdio.h> #include <stdlib.h> struct node{ struct node *next; int val; }; // 函數指針,JS中的函數對象 typedef void (*func_t)(int); void foreach(struct node *list, func_t func){ while(list){ func(list->val); list = list->next; } } void f(int n){ printf("node(?)=%d\n",n); } int main(){ func_t pf = f; f(1); // bool b = false; // b = true; // b = "zifuchuan"; struct node * list = 0, *l; int i; for(i=0; i<4; i++){ l = malloc(sizeof(struct node)); l->val = i; l->next = list; list = l; } i=0;l=list; //這個循環能夠打印出index,也就是i,以下是打印結果 //node(0) = 3 //node(1) = 2 //node(2) = 1 //node(3) = 0 while(l){ printf("node(%d) = %d\n", i++, l->val); l = l->next; } //foreach裏面再調用函數f,就不能訪問i了,以下是打印結果 //node(?)=3 //node(?)=2 //node(?)=1 //node(?)=0 foreach(list,f); }
咱們看到C語言的高階函數(函數調用函數)是無法訪問外部變量的。那麼js寫這段代碼怎麼弄呢?
function foreach(list, func){ while(list){ func(list.val); list=list.next; } } var i = 0; //這裏可使用變量i foreach(list,function(n){ console.log("node("+ i +") = " +n); i++; });
咱們發現js的變量做用域比C語言要牛逼一點吧。
下面再看看js閉包的更牛逼的地方吧。
function extent(){ var n=0; return function (){ n++; console.log("n="+n); } } f = extent(); f();//=>n=1 f();//=>n=2
這裏,當extent函數執行完畢後,n變量應該掛了纔對,可是,咱們經過結果看到,n變量還活的好好的呢。
屬於外部做用域的局部變量,被函數對象給「封閉」在裏面了。閉包(「closure」)這個詞本來就是封閉的意思。被封閉起來的變量的壽命,與封閉它的函數對象的壽命相等。也就是說,當封閉這個變量的函數對象再也不被訪問,被垃圾回收器回收掉時,這個變量才掛。
如今你們明白閉包的定義了吧。在函數對象中,將局部變量封閉起來的結構被叫作閉包。所以,C語言的函數指針並非閉包,JavaScript中的函數對象纔是閉包。另外C++等傳統面嚮對象語言,也加入了對函數式編程的支持,其中一方面就是Lambda表達式,也有閉包的概念。
如今你們理解閉包的概念了,咱們看看全局變量導入的問題。由於閉包中內部變量會一直持有外部變量,因此咱們最好把外部變量當作參數傳遞給咱們要使用的內部函數,這樣會節省內存和查找變量的時間。由於找到一個外部變量,須要從內部往外部一層層的查找,很費時間(對解析器來講)。另外,一塊兒開發代碼的人也會很迷惑這個變量到底在那裏定義的,容易出錯,咱們來看一個例子。
var globalVariable = "我是外層變量,從disply函數找到我須要好久好久"; var namespace1 = (function(gv){ var importGlobalVariable = gv; var list = ['死亡騎士','巫妖','恐懼魔王']; function display(){ var mylist = document.getElementById("mylist"), fragment = document.createDocumentFragment(), element; for(var i = 0, x = list.length; i<x; i++){ element = document.createElement("li"); element.appendChild( document.createTextNode( list[i]) ); fragment.appendChild(element); } console.log(importGlobalVariable); mylist.appendChild(fragment); } return { display:display }; })(globalVariable);
這個例子把globalVariable當成參數傳入,這樣他就在匿名function函數的做用域裏面了。
在看一個圖,會更直觀
咱們不但願解析器去一直往上查找,因此幫幫它嘍。