初探 RequireJS requirejs

網站製作學習誌

記錄學習製作網站的一切

RSS

Navigate…? Blog? 《PHP Smarty 樣版引擎》勘誤表 Blog

《PHP Smarty 樣版引擎》勘誤表

初探 RequireJS

2012-04-08

一直以來,我們都習慣使用 script 這個 HTML 標籤來載入 JavaScript 檔案。這種方式有兩種缺點:

無法在 JavaScript 程式中直接管理相依性,必須在 HTML 中處理。

雖然目前新式瀏覽器已經能夠以非同步的方式來載入 js 檔案,但是舊型瀏覽器還是會有阻塞 (blocking) 問題。

終於 CommonJS 提出了 AMD 這個 API 規範,用以讓我們的 JavaScript 程式可以模組化,並同時解決 js 檔案載入時的阻塞問題。

目前已經有許多實作 AMD 規範的 JavaScript Library 了,而 RequireJS 則是目前討論最多,應用最廣的其中一個實作。

以下是我在研究 RequireJS 時的筆記,若有謬誤還請大家指正。

起手式

先來看看我們的程式目錄架構:

<project>├── index.html├── js│ ├── app.js│ └── main.js└── lib ├── backbone │ ├── backbone-min.js │ └── wrapper.js ├── jquery │ ├── jquery-min.js │ └── wrapper.js ├── underscore │ ├── underscore-min.js │ └── wrapper.js └── requirejs ├── order.js └── require.js

其中 index.html 的內容如下:

index.html 12345678910<!doctype html><html lang="en"><head> <meta charset="utf-8" /> <title>Beginning Require.JS</title> <script data-main="js/main" src="lib/requirejs/require.js"></script></head><body></body></html>

你會發現,我們只需要用一個 script 標籤來載入 lib/requirejs/require.js 即可,剩下的 js 檔案都可以讓 RequireJS 來幫我們載入。

可是 RequireJS 怎麼知道要載入哪些檔案呢?注意 script 標籤上的 data-main 屬性,它指向了 js/main.js (可以將 js 副檔名省略) 。在 js/main.js 中,我們就可以指定我們要載入的模組:

js/main.js 12345require([ '../lib/jquery/jquery-min'], function () { console.log($);});

所以一切都是從 js/main.js 開始執行。

在 js/main.js 裡,我們用到了 require 這個 RequireJS 中最主要的 API ,它的基本用法如下:

1require(dependencies, callback);

其中 dependencies 的格式必須為陣列, callback 則為函式。

dependencies 表示我們要載入的 js ,而其路徑則是相對於 js/main.js ,而且一樣不需要寫副檔名。

因此在 dependencies 中,我們就可以將所有會用到的 js 載入,然後在 callback 中撰寫我們真正要處理的程式邏輯。

模組化

當然把所有的程式邏輯都寫在 js/main.js 的 callback 裡面是沒問題的,但那就沒辦法達到我們想要的模組化了。

而 RequireJS 也實作 AMD 所定義的 define API 方法,所以我們就可以用它來實現程式的模組化。 define 的 API 如下:

1define(id?, dependencies?, factory);

其中 id 格式為字串,代表模組的名稱,可以不寫。如果要寫的話,就必須是相對於 js/main.js 的檔案路徑,但不用加上 js 副檔名,例如 ../lib/foo 或 ./js/bar 。

而 dependencies 格式為陣列,作用與 require 中的 dependencies 相同。一般來說如果我們在 js/main.js 中定義好相依性後,這裡可以不需要特別指定。

最後的 factory 則為一個工廠方法,它必須回傳一個物件,也就是我們的模組。

接著我們把原來 require API 中的 callback 改成模組,並將它放到 js/app.js 中:

js/app.js 1234567define(function () { return { initialize: function () { console.log($); } }});

js/app.js 會回傳一個包含 initialize 方法的物件模組,而這個方法就是我們前面的 callback 。注意這個例子裡並沒有設定模組的 id 。

接下來我們把 js/app.js 加到 require 的第一個參數中,特別注意這裡的 app 是指 js/app.js ,而不是模組名稱。

js/main.js 123456require([ 'app', '../lib/jquery/jquery-min'], function (App) { App.initialize();});

在 callback 的第一個參數 App 會對應到 js/app.js 中所回傳的物件,這意謂著我們可以為該物件指定新的 namespace 。

到這裡其實可以應付很多基本的應用了,不過如果當 library 間有相依性問題時,這樣的寫法就可能會出錯了。

順序問題

因為使用非同步的載入方式,所以用 require 載入套件時,是有可能會造成相依性上的問題。 所幸 RequireJS 提供了一個 order plugin ,讓我們可以依序載入正確的套件。

以 Backbone 為例,我們需要依序載入 jQuery 、 underscore 及 Backbone 等三個套件,方法如下:

js/main.js 12345678require([ 'app', '../lib/requirejs/order!../lib/jquery/jquery-min', '../lib/requirejs/order!../lib/underscore/underscore-min', '../lib/requirejs/order!../lib/backbone/backbone-min'], function (App) { App.initialize();});

如上面的範例所示,在每一行載入 js 的字串中,我們先載入 plugin ,然後利用 ! 符號來將 library 的位置傳給 plugin 。

其他有用的 plugin ,可以在官方網站的 Plugins 頁找到。

路徑別名

不過每次都要輸入這麼長的路徑實在是很麻煩,還好 RequireJS 也提供了 paths 讓我們設定路徑的別名,就不需要輸入這麼多字:

js/main.js 123456789101112131415require({ paths: { "order": "../lib/requirejs/order", "lib": "../lib" }});require([ 'app', 'order!lib/jquery/jquery-min', 'order!lib/underscore/underscore-min', 'order!lib/backbone/backbone-min'], function (App) { App.initialize();});

要特別注意的是,這裡設定的別名,也會影響到其他模組裡所使用的 define API 。

require 還有其他設定,請參考官方文件的 Configuration Options 。

Namespace

前面提到 require API 可以讓我們對載入的 js 檔案所回傳的模組物件做 namespace 對應,也就是上述例子的 App 。事實上我們可以針對每個模組都設定一個 namespace ,例如:

js/main.js 1234567891011require([ '../lib/a', '../lib/b', '../lib/c', '../lib/d'], function (moduleA, moduleB, moduleC) { moduleA.doSomething(); moduleB.doSomething(); moduleC.doSomething(); namespaceD.doSomething();});

可以看到 '../lib/a' 這個模組對應到 moduleA 這個 namespace ,'../lib/b' 則對應到 moduleB ,以此類推。

但是 namespaceD 並沒有在 require 方法的 callback 參數中,那為什麼我們可以取用呢?

回到一開始我們用 require 載入第三方套件的方式,其實可以看到我們是直接利用該套件定義好的 namespace ,例如 jQuery 的 $ 符號,或是 underscore.js 的 _ 符號。

而我們並沒有再為這些套件指定新的 namespace ,是因為這些 namespace 已經被綁在 global 變數裡了 (在瀏覽器環境下是指 window 變數) ,所以我們可以直接取用。

所以 namespaceD 其實就是 lib/d.js 裡已經定義好的,例如:

lib/d.js 12345678define(function () { var namespaceD = window.namespaceD = { doSomething: function () { console.log('namespaceD.doSomething()'); } }; return namespaceD;});

瞭解這個回到我們前面所提到的 Backbone.js 範例,有些文章的例子會教大家這麼用:

js/main.js 1234567891011require([ 'order!lib/jquery/jquery-min', 'order!lib/underscore/underscore-min', 'order!lib/backbone/backbone-min', 'order!app'], function ($, _, Backbone, App) { console.log($); console.log(_); console.log(Backbone); console.log(App);});

如果各位是使用 Underscore.js 及 Backbone.js 的官方版本時,這樣做是錯誤的,你會發現 callback 裡的 _, Backbone 都會是 null 值。為什麼呢?主要是因為這兩個套件目前不支援 AMD 架構,所以無法正確回傳對應的 Underscore.js 及 Backbone.js 物件回來。所以很多人在透過 RequireJS 使用這兩個套件時,就會在這裡卡關。

最簡單的方式就是不要再為這些第三方套件設定一個 namespace ,也就是一開始為大家介紹的用法。

另一種方式就是直接使用 RequireJS 所 fork 出來的 Underscore.js 及 Backbone.js 的 AMD 版本。

還有一種方法是為這些套件的官方版本定義一個 wrapper ,以 Underscore.js 為例:

lib/underscore/wrapper.js 12345define([ 'lib/underscore/underscore-min'], function(){ return _.noConflict();});

這樣在 js/main.js 裡就可以重新使用 Underscore.js 的 namespace 了,例如:

js/main.js 12345require([ 'order!lib/underscore/wrapper'], function (_) { console.log(_);});

不過因為非同步載入的關係,要使用 wrapper 方法處理套件相依性時,其流程就稍微複雜些了,大家可以參考 Organizing your application using Modules (require.js) 一文的介紹。

編譯

當我們把 JavaScript 拆成這麼多模組檔案後,那麼不就會讓 HTTP Request 變多了嗎?有沒有什麼方法可以幫我們把這些檔案再組合成為一支檔案呢?

RequireJS 就提供了一個好用的工具,叫做 r.js 。它必須透過 node.js 的套件管理系統來安裝,也就是 npm ;安裝方法如下:

npm -g install requirejs

若無錯誤的話,應該會出現以下畫面:

npm http GET https://registry.npmjs.org/requirejsnpm http 200 https://registry.npmjs.org/requirejsnpm http GET https://registry.npmjs.org/requirejs/-/requirejs-1.0.7.tgznpm http 200 https://registry.npmjs.org/requirejs/-/requirejs-1.0.7.tgz/usr/local/bin/r.js -> /usr/local/lib/node_modules/requirejs/bin/r.jsrequirejs@1.0.7 /usr/local/lib/node_modules/requirejs

r.js 會幫我們處理以 require 或 define 所定義的模組,再參照其相依性把所有檔案合併為單一的 JavaScript 檔案。用法如下:

r.js -o name=js/main out=js/main-built.js baseUrl=. paths.order="lib/requirejs/order"

其中 -o 為最佳化; name 則為要處理的 JavaScript 檔案; out 則是輸出的檔案名稱; baseUrl 為指定 r.js 在處理相依性時所要參考的相對路徑; paths.order 是路徑別名,但不相對於 js/main.js ,而是相對於 baseUrl 。

處理完成後,我們就可以直接改用以下方式載入:

index.html 1<script data-main="js/main-built" src="lib/requirejs/require.js"></script>

當然聰明如你,應該想到該怎麼讓開發和上線環境使用不同的 JavaScript 檔案了吧?

心得

以往在寫 JavaScript 時,雖然都會儘可能模組化,但變數的管理還有程式拆解不易的狀況,都是自己在維護 JavaScript 程式時很大的痛處。

在瞭解 RequireJS 的強大後,我相信以這樣的模組化方式再搭配 Backbone.js 的架構,一定可以讓系統在開發與維護上更為有組織性。

參考

Organizing your application using Modules (require.js)

AMD 規範:簡單而優雅的動態載入 JavaScript 代碼



Posted by jaceju 2012-04-08 RequireJS

Tweet

? Head First Patterns in PHP 利用 RequireJs 將 Backbone.js 程式模組化 ?

Comments

Recent Posts

PHP + MongoDB 設定心得

網站架構與部署策略筆記

WebConf Taiwan 2013 心得與簡報

[Web] 連結分享

網站技術發展史

Categories

JavaScript

CSS

好書推薦

PHP

Windows

伺服器安裝與設定

Smarty

資料庫

作品

ASP

好站推薦

心得感想

好文推薦

程式開發

Zend Framework

開發工具

Web 開發

電腦知識

MySQL

業界資訊

連結分享

Linux

未分類

網路趣味

jQuery

IE6

設計模式

NetBeans

持續整合

Mobile 開發

Titanium

初探 RequireJS requirejs
PHPConf.TW

Compass

CoffeeScript

Backbone.js

Octopress

RequireJS

JSDC

WebConf

MongoDB

Latest Tweets

Status updating...

Follow @jaceju Google+

Copyright ? 2013 - jaceju - Powered by Octopress

  

爱华网本文地址 » http://www.413yy.cn/a/25101014/198980.html

更多阅读

新疆农业可持续发展初探 东北农业可持续发展

新疆农业可持续发展初探作者:阿德力汗·叶斯汗 在即将进入21世纪时,农业作为新疆国民经济的基础产业,其发展状况如何对新疆整个社会经济的发展起到举足轻重的作用,因此,农业可持续发展就成为新疆国民经济可持续发展的根本保证。但就现

转载 1936年齐白石王瓒绪关系破裂原因初探 齐白石字画拍卖

原文地址:1936年齐白石王瓒绪关系破裂原因初探作者:貞與1936年齐白石王瓒绪关系破裂原因初探1941年,蜀游后的第五年,八十一岁的齐白石在《蜀游杂记》末页题到“辛巳冬十月十又八日因忆在成都时有一门客,日久忘其姓名,翻阅此日记,始愧,虚走

关于西昌市小升初即中考的初探 西昌市教育网

关于西昌市小升初即中考的初探---谨以此文献给小考以及中考乃至于未来高考的家长们每年到6月份的时候,小考,中考乃至于高考,牵动了成千上万家长的心。这也是人之常情,哪个家长不“望子成龙”呢!本文作者研究小考,中考,高考10余年,着重谈谈

初探即将开馆的北京铁军纪念馆...... 怀柔铁军纪念馆

赶在后天“北京市爱国主义教育基地(北京铁军纪念园)揭牌暨铁军纪念馆开馆仪式”之前,先发以下初探铁军园的图文,抢个早! 2013年6月20日北京怀柔九公山铁军纪念园,北京新四军研究会的领导来到这里现场办公。北京怀柔九公山铁军纪念园于2010

图 CPL、ND、GND初探 cpl nd gnd使用顺序

CPL、ND、GND初探——风光摄影中,一套滤镜系统的价值远大于镜头本身很多人喜欢风光摄影,而常常在追求镜头本身的同时,却忽略了滤镜系统的存在,对佳能系统来说,语气追求昂贵的16-35 f2.8,倒不如使用更为实惠的17-40、15-85或者24-105这些中

声明:《初探 RequireJS requirejs》为网友毒魂分享!如侵犯到您的合法权益请联系我们删除