搜尋
首頁後端開發php教程javascript模組化程式設計轉載

Javascript 模組化程式設計

作者: 阮一峰  發佈時間: 2013-01-08 18:04  閱讀: 7632 次  推薦: 40   原始連結@ 網站逐漸變成"網路應用程式",嵌入網頁的Javascript 程式碼越來越龐大,越來越複雜。

  網頁越來越像桌面程序,需要一個團隊分工協作、進度管理、單元測試等等......開發者不得不使用軟體工程的方法,管理網頁的業務邏輯。

  Javascript 模組化編程,已經成為一個迫切的需求。理想情況下,開發者只需要實作核心的業務邏輯,其他都可以載入別人已經寫好的模組。

  但是,Javascript 不是一種模組化程式語言,它不支援"類別"(class),更遑論"模組"(module)了。 (正在製定中的ECMAScript 標準第六版,將正式支援"類"和"模組",但還需要很長時間才能投入實用。)

  Javascript 社群做了很多努力,在現有的運作環境中,實現"模組"的效果。本文總結了目前"Javascript 模組化程式設計"的最佳實踐,說明如何投入實用。雖然這不是初級教程,但只要稍稍了解 Javascript 的基本語法,就能看懂。

一、原始寫法

  模組就是實現特定功能的一組方法。

  只要把不同的函數(以及記錄狀態的變數)簡單地放在一起,就算是一個模組。

function m1(){
  //...
}
function m2(){
  //...
} 

  上面的函數 m1() 和 m2(),組成一個模組。使用的時候,直接呼叫就行了。

  這種做法的缺點很明顯:"污染"了全局變量,無法保證不與其他模組發生變量名衝突,而且模組成員之間看不出直接關係。

二、對象寫法

  為了解決上面的缺點,可以把模組寫成一個對象,所有的模組成員都放到這個對象裡面。

var module1 = new Object ({
  _count : 0,
  m1 : function (){
    //...
  },
  m2 : function (){
    //...
  }
}); 

  上面的函數 m1()和 m2(),都封裝在 module1 物件裡。使用的時候,就是呼叫這個物件的屬性:

module1.m1(); 

  但是,這樣的寫法會暴露所有模組成員,內部狀態可以被外部改寫。例如,外部程式碼可以直接改變內部計數器的值。

module1._count = 5; 

三、立即執行函數寫法

  使用"立即執行函數"(Immediately-Invoked Function Expression,IIFE),可以達到不暴露私有成員的目的。

var module1 = (function(){
  var _count = 0;
  var m1 = function(){
    //...
  };
  var m2 = function(){
    //...
  };
  return {
  m1 : m1,
  m2 : m2
  };
})(); 

  使用上面的寫法,外部程式碼無法讀取內部的_count 變數。

console.info (module1._count); //undefined

  module1就是 Javascript 模組的基本寫法。下面,再對這種寫法進行加工。

四、放大模式

  如果一個模組很大,必須分成幾個部分,或者一個模組需要繼承另一個模組,這時就有必要採用"放大模式"(augmentation)。

var module1 = (function (mod){
  mod.m3 = function () {
    //...
  };
  return mod;
})(module1); 

  上面的程式碼為 module1 模組新增了一個新方法 m3(),然後傳回新的 module1 模組。

五、寬放大模式(Loose augmentation)

  在瀏覽器環境中,模組的各個部分通常都是從網路上取得的,有時無法知道哪個部分會先載入。如果採用上一節的寫法,第一個執行的部分有可能載入一個不存在空對象,這時就要採用"寬放大模式"。

var module1 = ( function (mod){
  //...
  return mod;
})(window.module1 {}); 

  與"放大模式"相比,"寬放大模式"就是"立即執行函數"的參數可以是空物件。

六、輸入全域變數

  獨立性是模組的重要特點,模組內部最好不與程式的其他部分直接互動。

  為了在模組內部呼叫全域變量,必須明確地將其他變數輸入模組。

var module1 = (function ($, YAHOO) {
  //...
})(jQuery, YAHOO); 

  上面的 module1 模組需要使用 jQuery 函式庫和 YUI 函式庫,就把這兩個函式庫(其實是兩個模組)當作參數輸入 module1。這樣做除了保證模組的獨立性,也使得模組之間的依賴關係變得明顯。這方面更多的討論,請參見 Ben Cherry 的著名文章《JavaScript Module Pattern: In-Depth》。

七、模組的規範

  先想一想,為什麼模組很重要?

  因為有了模組,我們就可以更方便地使用別人的程式碼,想要什麼功能,就載入什麼模組。

  但是,這樣做有一個前提,那就是大家必須以同樣的方式編寫模組,否則你有你的寫法,我有我的寫法,豈不是亂了套!考慮到 Javascript 模組現在還沒有官方規範,這一點就更重要了。

  目前,通行的 Javascript 模块规范共有两种:CommonJS 和 AMD。我主要介绍 AMD,但是要先从 CommonJS 讲起。

八、CommonJS

  2009年,美国程序员 Ryan Dahl 创造了 node.js 项目,将 Javascript 语言用于服务器端编程。

  这标志"Javascript 模块化编程"正式诞生。因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。

  node.js 的模块系统,就是参照 CommonJS 规范实现的。在 CommonJS 中,有一个全局性方法 require (),用于加载模块。假定有一个数学模块 math.js,就可以像下面这样加载。

var math = require ('math'); 

  然后,就可以调用模块提供的方法:

var math = require ('math');
math.add (2,3); // 5 

  因为这个系列主要针对浏览器编程,不涉及 node.js,所以对 CommonJS 就不多做介绍了。我们在这里只要知道,require () 用于加载模块就行了。

九、浏览器环境

  有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。

  但是,由于一个重大的局限,使得 CommonJS 规范不适用于浏览器环境。还是上一节的代码,如果在浏览器中运行,会有一个很大的问题,你能看出来吗?

var math = require ('math');
math.add (2, 3); 

  第二行 Math.add (2, 3),在第一行 require ('math') 之后运行,因此必须等 math.js 加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。

  这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。

  因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是 AMD 规范诞生的背景。

十、AMD

  AMD 是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

  AMD 也采用 require ()语句加载模块,但是不同于 CommonJS,它要求两个参数:

require ([module], callback); 

  第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数 callback,则是加载成功之后的回调函数。如果将前面的代码改写成 AMD 形式,就是下面这样:

require (['math'], function (math) {
    math.add (2, 3);
}); 

  math.add () 与 math 模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD 比较适合浏览器环境。

  目前,主要有两个 Javascript 库实现了 AMD 规范:require.js 和 curl.js。本系列的第三部分,将通过介绍 require.js,进一步讲解 AMD 的用法,以及如何将模块化编程投入实战。

  我采用的是一个非常流行的库 require.js。

一、为什么要用 require.js?

  最早的时候,所有 Javascript 代码都写在一个文件里面,只要加载这一个文件就够了。后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载。下面的网页代码,相信很多人都见过。

<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
<script src="6.js"></script>

  这段代码依次加载多个 js 文件。

  这样的写法有很大的缺点。首先,加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长;其次,由于 js 文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js 要在2.js 的前面),依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难。

  require.js 的诞生,就是为了解决这两个问题:

(1)实现 js 文件的异步加载,避免网页失去响应;

(2)管理模块之间的依赖性,便于代码的编写和维护。

二、require.js 的加载

  使用 require.js 的第一步,是先去官方网站下载最新版本。

  下载后,假定把它放在 js 子目录下面,就可以加载了。

<script src="js/require.js"></script>

  有人可能会想到,加载这个文件,也可能造成网页失去响应。解决办法有两个,一个是把它放在网页底部加载,另一个是写成下面这样:

<script src="js/require.js" defer async="true"></script>

  async 属性表明这个文件需要异步加载,避免网页失去响应。IE 不支持这个属性,只支持 defer,所以把 defer 也写上。

  加载 require.js 以后,下一步就要加载我们自己的代码了。假定我们自己的代码文件是 main.js,也放在 js 目录下面。那么,只需要写成下面这样就行了:

<script src="js/require.js" data-main="js/main"></script>

  data-main 属性的作用是,指定网页程序的主模块。在上例中,就是 js 目录下面的 main.js,这个文件会第一个被 require.js 加载。由于 require.js 默认的文件后缀名是 js,所以可以把 main.js 简写成 main。

三、主模块的写法

  上一节的 main.js,我把它称为"主模块",意思是整个网页的入口代码。它有点像C语言的 main ()函数,所有代码都从这儿开始运行。

  下面就来看,怎么写 main.js。

  如果我们的代码不依赖任何其他模块,那么可以直接写入 javascript 代码。

// main.js
alert ("加载成功!");

  但这样的话,就没必要使用 require.js 了。真正常见的情况是,主模块依赖于其他模块,这时就要使用 AMD 规范定义的的 require ()函数。

// main.js
require (['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// some code here
});

  require () 函数接受两个参数。第一个参数是一个数组,表示所依赖的模块,上例就是['moduleA', 'moduleB', 'moduleC'],即主模块依赖这三个模块;第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。

  require () 异步加载 moduleA,moduleB 和 moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。

  下面,我们看一个实际的例子。

  假定主模块依赖 jquery、underscore 和 backbone 这三个模块,main.js 就可以这样写:

require (['jquery', 'underscore', 'backbone'], function ($, _, Backbone){
// some code here
});

  require.js 会先加载 jQuery、underscore 和 backbone,然后再运行回调函数。主模块的代码就写在回调函数中。

四、模块的加载

  上一节最后的示例中,主模块的依赖模块是['jquery', 'underscore', 'backbone']。默认情况下,require.js 假定这三个模块与 main.js 在同一个目录,文件名分别为 jquery.js,underscore.js 和 backbone.js,然后自动加载。

  使用 require.config () 方法,我们可以对模块的加载行为进行自定义。require.config () 就写在主模块(main.js)的头部。参数就是一个对象,这个对象的 paths 属性指定各个模块的加载路径。

require.config ({
    paths: {
        "jquery": "jquery.min.js",
        "underscore": "underscore.min.js",
        "backbone": "backbone.min.js"
    }
});

  上面的代码给出了三个模块的文件名,路径默认与 main.js 在同一个目录(js 子目录)。如果这些模块在其他目录,比如 js/lib 目录,则有两种写法。一种是逐一指定路径。

require.config ({
    paths: {
        "jquery": "lib/jquery.min.js",
        "underscore": "lib/underscore.min.js",
        "backbone": "lib/backbone.min.js"
    }
}); 

  另一种则是直接改变基目录(baseUrl)。

require.config ({
    baseUrl: "js/lib",
    paths: {
        "jquery": "jquery.min.js",
        "underscore": "underscore.min.js",
        "backbone": "backbone.min.js"
    }
});

  如果某个模块在另一台主机上,也可以直接指定它的网址,比如:

require.config ({
    paths: {
        "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min"
    }
});

  require.js 要求,每个模块是一个单独的 js 文件。这样的话,如果加载多个模块,就会发出多次 HTTP 请求,会影响网页的加载速度。因此,require.js 提供了一个优化工具,当模块部署完毕以后,可以用这个工具将多个模块合并在一个文件中,减少 HTTP 请求数。

五、AMD 模块的写法

  require.js 加载的模块,采用 AMD 规范。也就是说,模块必须按照 AMD 的规定来写。

  具体来说,就是模块必须采用特定的 define () 函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在 define () 函数之中。

  假定现在有一个 math.js 文件,它定义了一个 math 模块。那么,math.js 就要这样写:

// math.js
define(function () {
    var add = function (x, y) {
        return x + y;
    };
    return {
        add: add
    };
});

  加载方法如下:

// main.js
require(['math'], function (math) {
    alert(math.add(1, 1));
});

  如果这个模块还依赖其他模块,那么 define ()函数的第一个参数,必须是一个数组,指明该模块的依赖性。

define(['myLib'], function (myLib) {
    function foo() {
        myLib.doSomething();
    }
    return {
        foo: foo
    };
});

  当 require ()函数加载上面这个模块的时候,就会先加载 myLib.js 文件。

六、加载非规范的模块

  理论上,require.js 加载的模块,必须是按照 AMD 规范、用 define () 函数定义的模块。但是实际上,虽然已经有一部分流行的函数库(比如 jQuery)符合 AMD 规范,更多的库并不符合。那么,require.js 是否能够加载非规范的模块呢?

  回答是可以的。

  这样的模块在用 require () 加载之前,要先用 require.config ()方法,定义它们的一些特征。

  举例来说,underscore 和 backbone 这两个库,都没有采用 AMD 规范编写。如果要加载它们的话,必须先定义它们的特征。

require.config({
    shim: {
        'underscore': {
            exports: '_'
        },
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        }
    }
});

  require.config () 接受一个配置对象,这个对象除了有前面说过的 paths 属性之外,还有一个 shim 属性,专门用来配置不兼容的模块。具体来说,每个模块要定义(1)exports 值(输出的变量名),表明这个模块外部调用时的名称;(2)deps 数组,表明该模块的依赖性。

  比如,jQuery 的插件可以这样定义:

shim: {
    'jquery.scroll': {
        deps: ['jquery'],
        exports: 'jQuery.fn.scroll'
    }
}

七、require.js 插件

  require.js 还提供一系列插件,实现一些特定的功能。

  domready 插件,可以让回调函数在页面 DOM 结构加载完成后再运行。

require(['domready!'], function (doc) {
    // called once the DOM is ready
});

  text 和 image 插件,则是允许 require.js 加载文本和图片文件。

define([
    'text!review.txt',
    'image!cat.jpg'
    ],
    function (review, cat) {
        console.log(review);
        document.body.appendChild(cat);
    }
);

  类似的插件还有 json 和 mdown,用于加载 json 文件和 markdown 文件。

以上就介绍了javascript模块化编程转载,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
超越炒作:評估當今PHP的角色超越炒作:評估當今PHP的角色Apr 12, 2025 am 12:17 AM

PHP在現代編程中仍然是一個強大且廣泛使用的工具,尤其在web開發領域。 1)PHP易用且與數據庫集成無縫,是許多開發者的首選。 2)它支持動態內容生成和麵向對象編程,適合快速創建和維護網站。 3)PHP的性能可以通過緩存和優化數據庫查詢來提升,其廣泛的社區和豐富生態系統使其在當今技術棧中仍具重要地位。

PHP中的弱參考是什麼?什麼時候有用?PHP中的弱參考是什麼?什麼時候有用?Apr 12, 2025 am 12:13 AM

在PHP中,弱引用是通過WeakReference類實現的,不會阻止垃圾回收器回收對象。弱引用適用於緩存系統和事件監聽器等場景,需注意其不能保證對象存活,且垃圾回收可能延遲。

解釋PHP中的__ Invoke Magic方法。解釋PHP中的__ Invoke Magic方法。Apr 12, 2025 am 12:07 AM

\_\_invoke方法允許對象像函數一樣被調用。 1.定義\_\_invoke方法使對象可被調用。 2.使用$obj(...)語法時,PHP會執行\_\_invoke方法。 3.適用於日誌記錄和計算器等場景,提高代碼靈活性和可讀性。

解釋PHP 8.1中的纖維以進行並發。解釋PHP 8.1中的纖維以進行並發。Apr 12, 2025 am 12:05 AM

Fibers在PHP8.1中引入,提升了並發處理能力。 1)Fibers是一種輕量級的並發模型,類似於協程。 2)它們允許開發者手動控制任務的執行流,適合處理I/O密集型任務。 3)使用Fibers可以編寫更高效、響應性更強的代碼。

PHP社區:資源,支持和發展PHP社區:資源,支持和發展Apr 12, 2025 am 12:04 AM

PHP社區提供了豐富的資源和支持,幫助開發者成長。 1)資源包括官方文檔、教程、博客和開源項目如Laravel和Symfony。 2)支持可以通過StackOverflow、Reddit和Slack頻道獲得。 3)開發動態可以通過關注RFC了解。 4)融入社區可以通過積極參與、貢獻代碼和學習分享來實現。

PHP與Python:了解差異PHP與Python:了解差異Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

php:死亡還是簡單地適應?php:死亡還是簡單地適應?Apr 11, 2025 am 12:13 AM

PHP不是在消亡,而是在不斷適應和進化。 1)PHP從1994年起經歷多次版本迭代,適應新技術趨勢。 2)目前廣泛應用於電子商務、內容管理系統等領域。 3)PHP8引入JIT編譯器等功能,提升性能和現代化。 4)使用OPcache和遵循PSR-12標準可優化性能和代碼質量。

PHP的未來:改編和創新PHP的未來:改編和創新Apr 11, 2025 am 12:01 AM

PHP的未來將通過適應新技術趨勢和引入創新特性來實現:1)適應云計算、容器化和微服務架構,支持Docker和Kubernetes;2)引入JIT編譯器和枚舉類型,提升性能和數據處理效率;3)持續優化性能和推廣最佳實踐。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。