RequireJS是一个很是小巧的JavaScript模块载入框架,是AMD规范最好的实现者之一。最新版本的RequireJS压缩后只有14K,堪称很是轻量。它还同时能够和其余的框架协同工做,使用RequireJS必将使您的前端代码质量得以提高。javascript
1、AMD 介绍
css
前端开发在近一两年发展的很是快,JavaScript做为主流的开发语言获得了史无前例的热捧。大量的前端框架出现了,这些框架都在尝试着解决一 些前端开发中的共性问题,可是实现又不尽相同。在这个背景下,CommonJS社区诞生了,为了让前端框架发展的更加成熟,CommonJS鼓励开发人员 一块儿在社区里为一些完成特定功能的框架制定规范。AMD(Asynchronous Module Definition)就是其中的一个规范。html
传统JavaScript代码的问题前端
让咱们来看看通常状况下JavaScript代码是如何开发的:经过<script>标签来载入JavaScript文件,用全局变量 来区分不一样的功能代码,全局变量之间的依赖关系须要显式的经过指定其加载顺序来解决,发布应用时要经过工具来压缩全部的JavaScript代码到一个文 件。当Web项目变得很是庞大,前端模块很是多的时候,手动管理这些全局变量间的依赖关系就变得很困难,这种作法显得很是的低效。java
AMD(Asynchronous Module Definition)的引入jquery
从名称上看便知它是适合script tag的。也能够说AMD是专门为浏览器中JavaScript环境设计的规范。它吸收了CommonJS的一些优势,但又不照搬它的格式。开始AMD做为CommonJS的transport format 存在,因没法与CommonJS开发者达成一致而独立出来。它有本身的wiki 和讨论组 。git
AMD提出了一种基于模块的异步加载JavaScript代码的机制,它推荐开发人员将JavaScript代码封装进一个个模块,对全局对象的依 赖变成了对其余模块的依赖,无须再声明一大堆的全局变量。经过延迟和按需加载来解决各个模块的依赖关系。模块化的JavaScript代码好处很明显,各 个功能组件的松耦合性能够极大的提高代码的复用性、可维护性。这种非阻塞式的并发式快速加载JavaScript代码,使Web页面上其余不依赖 JavaScript代码的UI元素,如图片、CSS以及其余DOM节点得以先加载完毕,Web页面加载速度更快,用户也获得更好的体验。github
CommonJS的AMD规范中只定义了一个全局的方法,如清单1所示。web
define(id?, dependencies?, factory);
该方法用来定义一个JavaScript模块,开发人员能够用这个方法来将部分功能模块封装在这个define方法体内。apache
id表示该模块的标识,为可选参数。
dependencies是一个字符串Array,表示该模块依赖的其余全部模块标识,模块依赖必须在真正执行具体的factory方法前解决,这 些依赖对象加载执行之后的返回值,能够以默认的顺序做为factory方法的参数。dependencies也是可选参数,当用户不提供该参数时,实现 AMD的框架应提供默认值为[“require”,”exports”,“module”]。
factory是一个用于执行改模块的方法,它可使用前面dependencies里声明的其余依赖模块的返回值做为参数,若该方法有返回值,当该模块被其余模块依赖时,返回值就是该模块的输出。
CommonJS在规范中并无详细规定其余的方法,一些主要的AMD框架如RequireJS、curl、bdload等都实现了define方法,同时各个框架都有本身的补充使得其API更实用。
AMD设计出一个简洁的写模块API:
define(id?, dependencies?, factory);
其中:
define(function() { return { mix: function(source, target) { } }; });
define(['base'], function(base) { return { show: function() { // todo with module base } } });
page.js
define(['data', 'ui'], function(data, ui) { // init here });
define({
users: [],
members: []
});
define('index', ['data','base'], function(data, base) { // todo });
define(function(require, exports, module) { var base = require('base'); exports.show = function() { // todo with module base } });
2、RequireJS
RequireJS会让你以不一样于往常的方式去写JavaScript。你将再也不使用script标签在HTML中引入JS文件,以及不用经过script标签顺序去管理依赖关系。
一、简单示例
固然也不会有阻塞(blocking)的状况发生。好,以一个简单示例开始。
<!doctype html> <html> <head> <title>requirejs入门(一)</title> <meta charset="utf-8"> <!--引入require.js(实际上除了require.js,其它文件模块都再也不使用script标签引入)---> <script data-main="main" src="require.js"></script> </head> <body> ... </body> </html>
main.js
require.config({ paths: { jquery: 'jquery-1.7.2' } }); require(['jquery'], function($) { alert($().jquery); });
main.js中就两个函数调用require.config和require。
这里require函数的第一个参数是数组,数组中存放的是模块名(字符串类型),数组中的模块与回调函数的参数一一对应。这里的例子则只有一个模块“jquery”。
jQuery中的支持AMD代码以下
if ( typeof define === "function" && define.amd && define.amd.jQuery ) { define( "jquery", [], function () { return jQuery; } ); }
咱们知道jQuery最终向外暴露的是全局的jQuery和 $。以下
// Expose jQuery to the global object window.jQuery = window.$ = jQuery;
若是将jQuery应用在模块化开发时,其实能够不使用全局的,便可以不暴露出来。须要用到jQuery时使用require函数便可,
把目录r1放到apache或其它web服务器上,访问index.html。
网络请求以下
咱们看到除了require.js外main.js和jquery-1.7.2.js也请求下来了。而它们正是经过requirejs请求的。
页面上会弹出jQuery的版本
这是一个很简单的示例,使用requirejs动态加载jquery。
二、写一个本身的模块:选择器
为演示方便这里仅实现经常使用的三种选择器id,className,attribute。RequireJS使用define来定义模块。
本例目的:
<!doctype html> <html> <head> <title>requirejs入门(二)</title> <meta charset="utf-8"> <style type="text/css"> .wrapper { width: 200px; height: 200px; background: gray; } </style> </head> <body> <div class="wrapper"></div> <script data-main="js/main" src="require.js"></script> </body> </html>
注意:
selector.js代码
define(function() { function query(selector,context) { var s = selector, doc = document, regId = /^#[\w\-]+/, regCls = /^([\w\-]+)?\.([\w\-]+)/, regTag = /^([\w\*]+)$/, regNodeAttr = /^([\w\-]+)?\[([\w]+)(=(\w+))?\]/; var context = context == undefined ? document : typeof context == 'string' ? doc.getElementById(context.substr(1,context.length)) : context; if(regId.test(s)) { return doc.getElementById(s.substr(1,s.length)); } // 略... } return query; });
define的参数为一个匿名函数,该匿名函数执行后返回query,query为函数类型。query就是选择器的实现函数。
main.js 以下
require.config({ baseUrl: 'js' }); require(['selector'], function(query) { var els = query('.wrapper'); console.log(els) });
require.config方法执行配置了baseUrl为“js”,baseUrl指的模块文件的根目录,能够是绝对路径或相对路径。这里用的是相对路径。相对路径指引入require.js的页面为参考点,通常是index.html。
把目录r2放到apache或其它web服务器上,访问index.html。
网络请求以下
main.js和selector.js都请求下来了。
selector.js下载后使用query获取页面class为“.wrapper”的元素,控制台输出了该元素。以下
3、写一个具备依赖的事件模块
具备依赖的事件模块event提供三个方法bind、unbind、trigger来管理DOM元素事件。
event依赖于cache模块,cache模块相似于jQuery的$.data方法。提供了set、get、remove等方法用来管理存放在DOM元素上的数据。
示例实现功能: 为页面上全部的段落P元素添加一个点击事件,响应函数会弹出P元素的innerHTML。
为了获取元素,用到了上一例写的selector.js。不在重复贴其代码
<!doctype html> <html> <head> <title>requirejs入门(三)</title> <meta charset="utf-8"> <style type="text/css"> p { width: 200px; background: gray; } </style> </head> <body> <p>p1</p><p>p2</p><p>p3</p><p>p4</p><p>p5</p> <script data-main="js/main" src="require.js"></script> </body> </html>
cache.js
define(function() { var idSeed = 0, cache = {}, id = '_ guid _'; // @private function guid(el) { return el[id] || (el[id] = ++idSeed); } return { set: function(el, key, val) { if (!el) { throw new Error('setting failed, invalid element'); } var id = guid(el), c = cache[id] || (cache[id] = {}); if (key) c[key] = val; return c; }, // 略去... }; });
cache模块的写法没啥特殊的,与selector不一样的是返回的是一个JS对象。
event.js 以下
define(['cache'], function(cache) { var doc = window.document, w3c = !!doc.addEventListener, expando = 'snandy' + (''+Math.random()).replace(/\D/g, ''), triggered, addListener = w3c ? function(el, type, fn) { el.addEventListener(type, fn, false); } : function(el, type, fn) { el.attachEvent('on' + type, fn); }, removeListener = w3c ? function(el, type, fn) { el.removeEventListener(type, fn, false); } : function(el, type, fn) { el.detachEvent('on' + type, fn); }; // 略去... return { bind : bind, unbind : unbind, trigger : trigger }; });
event依赖于cache,定义时第一个参数数组中放入“cache”便可。第二个参数是为函数类型,它的参数就是cache模块对象。
这样定义后,当require事件模块时,requirejs会自动将event依赖的cache.js也下载下来。
main.js 以下
require.config({ baseUrl: 'js' }); require(['selector', 'event'], function($, E) { var els = $('p'); for (var i=0; i<els.length; i++) { E.bind(els[i], 'click', function() { alert(this.innerHTML); }); } });
依然先配置了下模块的根目录js,而后使用require获取selector和event模块。
回调函数中使用选择器$(别名)和事件管理对象E(别名)给页面上的全部P元素添加点击事件。
注意:require的第一个参数数组内的模块名必须和回调函数的形参一一对应。
把目录r3放到apache或其它web服务器上,访问index.html。网络请求以下
咱们看到当selector.js和event.js下载后,event.js依赖的cache.js也被自动下载了。这时点击页面上各个P元素,会弹出对应的innerHTML。以下
总结:
当一个模块依赖(a)于另外一个模块(b)时,定义该模块时的第一个参数为数组,数组中的模块名(字符串类型)就是它所依赖的模块。
当有多个依赖模时,须注意回调函数的形参顺序得和数组元素一一对应。此时requirejs会自动识别依赖,且把它们都下载下来后再进行回调。
说明和其余问题:
一、路径与后缀名
在 require 一个 js 文件的时候,通常不须要加上后缀名。若是加上后缀名,会按照绝对路径加载。没有后缀名,是按照下面的路径加载:
<script data-main=
"js/main"
src=
"js/require-jquery.js"
></script
>
也就是默认加载 data-main 指定的目录,即 js/main.js 文件所在的目录。固然,你能够经过配置文件修改。
二、define 定义模块方法只能用在独立的js文件中,不能在页面中直接使用。
不然会报 Mismatched anonymous define() module 错误。
三、和其余第三方js类库是否冲突?
不会冲突。通常比较规范的类库,都会给本身的js加上命名空间。好比 wojilu 旧有的 wojilu.common.js ,其实就是放在 wojilu 命名空间中(固然是经过更原始的方式实现命名空间的)。
在经过 RequireJS 加载这些第三方的 js 的时候,彻底不要有任何担心。
固然,若是第三方类库可以使用 RequireJS 的方式进行改造,那是最好。好比 wojilu 中大多数js 都按照 RequireJS 的方式进行了改造。可是,若是你不改造,也是彻底没关系的。
四、在代码中 require 一个文件屡次,是否会致使浏览器反复加载?
不会,这是 RequrieJS 的优势,即便你反复 require 它,它只加载一次。
参考: