前端js编程的朋友一定会觉得,写js代码写了好多好多,感觉好乱。其实js里也是有模块化编程的,请参看CommonJS。貌似nodejs里用的解决方案就是CommonJS。我最早接触到javascript模块化编程的地方是在nodejs里。而CommonJS并不是一个实现,而是一组API和概念,它其中之一的实现就是大名鼎鼎的RequireJS,我今天稍微看了下。在网上找了一篇不错的tutorial,这里分享下,就不做翻译了(懒)。

CommonJS的官方网站:CommonJS

RequireJS的官方网站:RequireJS

RequireJS的源代码,请查github站点:jrburke/requirejs

Tutorial转自:Introduction to RequireJS

这篇tutorial里没有涉及到exports,但是其实exports也是非常重要且有用的一个组件。在nodejs里,用得更多的做法是exports,而不是如下tutorial所写的define。有需要的可以参考这篇blog:Node.js, Require and Exports。RequireJS里其实也有:RequireJS >> Circular Dependencies

-----------------------------------------------------------------------------

In this tutorial we are going to take a look at RequireJS, an AMD compatible asynchronous script loader that is incredibly powerful. In my experiments with RequireJS I've hugely enjoyed working with it and will be using it heavily in my future development. This is a fairly heavy post as far as complexity goes, but please do stick with it. I struggled to get my head around RequireJS and AMD for a long time but once it "clicks" it is really awesome.

The basic use case for RequireJS is as a basic script loader, but in this tutorial I wont concentrate on that, but on its uses for modular development. RequireJS implements the AMD (Asynchronous Module Definition) spec, which means we can write our own modules and load them with RequireJS, allowing it to manage dependencies for us. Have you ever had multiple script tags and had to load them in a particular order as one relied on the other? I have, and it's a nightmare. Working in a modular fashion really eliminates this issue and in this tutorial I hope to demonstrate just how.

To do this, we are going to build an app (sort of - it's all very basic snippets of code) that has dependencies. It depends on both Underscore and jQuery. We could just include this as a whole host of <script> tags, but that's absolutely no fun and is also not efficient, when loading all those in a browser the rest of the page load will be blocked. We could minify them, but then we have to minify them and maintain order of the code, and it just becomes a nightmare. With RequireJS, we include the RequireJS source, and from there can get it to load in files.

Firstly, create your project directory and the structure within. Mine looks like this:

├── app.js 
├── index.html 
├── lib 
│   ├── modules 
│   │   └── template.js 
│   ├── require.js 
│   └── underscore.js 
  • app.js is my main file, we will look into this shortly.
  • lib/modules is where all my self-written modules will go. With RequireJS all our code gets split into modules. I'll explain further in a moment.
  • Files immediately within lib are external libraries, in this case the RequireJS source and also Underscore.

To get started, head into your index.html file and add in this line:

[codesyntax lang="html4strict"]
<script src="lib/require.js" data-main="app"></script>
[/codesyntax]

That line loads in the RequireJS source, but also tells RequireJS to automatically load in app.js. This is what I will refer to from now on as our "main" JS file, it's where we will put our configuration for RequireJS and load in code. This also sets the base path for loading in files, whenever we load in a file with RequireJS, it will treat the folder app.js is within as the base path and load all files relative to that. Now we've got that done, we can get going.

Before I get ahead of myself, let me show you how we load in dependencies. This is done through the require function. To load in some code to run after a script, you use it like so:

[codesyntax lang="javascript"]
require(['myfile'], function(myFile) {
  myFile.init();
});
[/codesyntax]
That would look for myfile.js within the same directory as your main JS file, and whatever myfile returns will be referenced within the callback as myFile, as that's the variable name I passed into the callback. With libraries like jQuery and Underscore that register global objects, you don't need to do this.

What we are going to do is set up jQuery with RequireJS. As of jQuery 1.7, it comes with support for AMD as it implements the AMD spec, so we can use it. You can see this right at the bottom of the un-minified source:

[codesyntax lang="javascript"]

if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
  define( "jquery", [], function () { return jQuery; } );
}

[/codesyntax]


The define function is implemented by RequireJS to allow us to define modules. This one defines a named module named "jquery". Usually when defining our own modules we don't explicitly name it (you'll see that later when we write our own) because the name is automatically generated by the file name, and we reference it based on that file name and the directory structure. Because jQuery has declared itself as a named module, we have to reference it as "jquery" when we load it in. This means, to make it work, we'd have to have the jQuery source within our main directory (alongside app.js) and name it jquery.js, so when we reference it within require() as "jquery", it loads properly (remember that RequireJS doesn't care about .js on the end). However, I prefer to load my jQuery version in from the Google CDN, so I need some way of telling RequireJS that when I try to load "jquery", to fetch it from the CDN. Thankfully this is really easy: [codesyntax lang="javascript"]

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

[/codesyntax]


That line means whenever I do:

[codesyntax lang="javascript"]
require(['jquery'], function() {
  // some code
});
[/codesyntax]

It will pull in jQuery from the Google CDN. Note that I've removed ".js" from the end of the URL. We'll also be using Underscore, and to save typinglib/underscore to load it in, I set up a path for that too (I tend to set up paths for most of my libraries I'm depending on. This means my config looks like:

[codesyntax lang="javascript"]
require.config({
  paths: {
    "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min",
    "underscore": "lib/underscore",
  }
});
[/codesyntax]

Now we have our set up sorted, lets write our first AMD module, called template.js. This will provide a method that will compile a basic Underscore template and display it on the page. The functionality is very easy, as the idea here is to look more at the AMD side of things. To define a method, we use the define() function. As we saw, we can explicitly name our module, like jQuery did, or we can let it be done based on the filename, which is fine. We need to pass define() two things, an array of dependencies, and a function that will have our implementation in it. This module is going to depend on Underscore and jQuery:

[codesyntax lang="javascript"]
define(['underscore', 'jquery'], function() {});
[/codesyntax]

What we're going to do is write a function that will add a string to the body that says "Hello Name", but let the name be passed into the function. It's a really easy implementation:

[codesyntax lang="javascript"]
var showName = function(n) {
  var temp = _.template("Hello <%= name %>");
  $("body").html(temp({name: n}));
};
[/codesyntax]

All we do is create a basic Underscore template and compile it, passing in the name variable. I then use jQuery to add it to the body of the page. Nothing complex at all.

Now, to expose this method we simply need to return it. What we do is return an object containing properties that are the methods to expose. In our case:

[codesyntax lang="javascript"]
return { showName: showName };
[/codesyntax]

And with that, our entire module looks like so:

[codesyntax lang="javascript"]
define(['underscore', 'jquery'], function() {
  var showName = function(n) {
    var temp = _.template("Hello <%= name %>");
    $("body").html(temp({name: n}));
  };
  return { showName: showName };
});
[/codesyntax]

The great thing about this is that you can have functions in your modules that are useful for internal use but avoid exposing them, and by dividing your app into multiple modules it's a great way to organise your code.

Finally, all that's left to do is require our module in app.js and then call showName() on it:

[codesyntax lang="javascript"]
require(['lib/modules/template'], function(template) {
  template.showName("Jack");
});
[/codesyntax]

Here the module we're loading does not expose itself globally, so to get at whatever it returns, we pass in a variable to the callback function that will be bound to what our module returns. If you're loading multiple modules, add multiple variables. For example:

[codesyntax lang="javascript"]
require(['moduleA', 'moduleB', 'moduleC'], function(a, b, c) {});
[/codesyntax]

Once the module is loaded, I can call showName and sure enough, I get "Hello Jack" in the browser if I refresh my index page.

Although this is a simple example I hope it helps to show the power behind RequireJS and what it can do with its modular approach. I've really enjoyed using it and will no doubt be exploring it further in future tutorials as it does plenty more stuff I haven't covered here.

As always, please do feel free to leave feedback and ask questions, I will endeavour to respond to them.