Angular Material串串学客户端开发 2 - Node.js模块加载机制Require()
2015-06-15 06:34
926 查看
题外话
> 首先,解一下博客标题,因为第一篇文章评论中,有人质疑离题很远,说了半天和Angular Material没有半毛关系。>1. 我的重心在后半句《串串学客户端开发》。这也是这一年以来,我自己的纠结和摸索的一个总结。客户端的开发自从NodeJS的出现以后,已经产生了天翻地覆的变化,不再是在html中引用js文件, 用alert调试。可以说NodeJS让javascript成一个完备的开发语言,这个完备包括它的功能和开发环境。所谓的《串串学客户端开发》就是展示开发环境。需要注意的是,这个开发环境不仅仅是IDE那么简单(现在可用的IDE有WebStorm, Sublime 甚至Visual Studio - 是的VS2015已经能非常好的支持NodeJS项目开发)。在我之后的系列中,会慢慢把这些展现出来。
>2. 为什么Angular Material。因为我确确实实在使用它,最新的项目客户端会全部改用它。另外,现在确实有很多开源的js项目,但是大部分都是框架项目,如Angular Material本身,而没有终端客户项目(即实际的网站项目)。而最近非常凑巧发现Angular Material源代码不仅有框架的代码,也有一个完整的网站代码。这个网站代码本身,即展现了客户端开发的全部内容,也包含了对框架的深度使用。所谓,一箭双雕非常完美,这才促使我启动念头,开始写这个系列。
>3. 如何学习?这是一个极浅又极深的话题。现在不仅客户端,任何一个软件的领域都有太多的东西。我前几天还和朋友聊到,看看github就能感觉到,太多的js框架(耳熟能详的就有jQuery, underscore, AngularJS, ReactJS,Grunt, Gulp ...), 根本就没有时间和精力一一掌握。我说,这些东西犹如一粒一粒散落的珠子,如果用一根线把它们串起来,就好把握多了。 这就是我文章标题的真正含义:我要用Angular Material这根线把客户端开发的各个方面串起来,这样才不那么散,不那么累。
>4. 如果有人只对ngMaterial本身感兴趣,可以看我其他的文章:手势模型和Angular Material的实现 拟物设计和Angular的实现 - Material Design 和深入探索AngularJS。
require()
> 不要把这里的Require()和RequireJS混为一谈。不过有意思的是,Typescript的模块定义,甚至同时支持这两种模块机制。导入和使用外部模块,只是简单的一句
require(),看看angular/material/docs下的编译文件gulpfile.js的代码片段。对模块导入和使用有个直观的感觉。
[code=language-js]var gulp = require('gulp'); var concat = require('gulp-concat'); var fs = require('fs'); ... //对模块gulp的使用 gulp.task('demos', function() { ... //对模块gulp-concat的使用 gulp.src([ 'node_modules/angularytics/dist/angularytics.js', 'dist/docs/js/**/*.js' ]) .pipe(concat('docs.js')) //对模块fs的使用 fs.writeFileSync(dest + '/demo-data.js', file);
gulp.task用于定义了一个任务;
cancat用于合并文件;fs是一个对磁盘文件操作的模块。可以看出,有模块的引入,代码更为清晰而明确,这些常用模块相当于对基本语言功能的扩展。
这里,关键词require()把一切联系在一起。那么这句简单的语句背后发生了什么事情呢?
> 1. require其实不是一个语言的关键词,在文章后面的研究,我们就可以看到。
> 2. 还没有使用过require()或者对它实现机制不感兴趣的开发人员,可以略个这一部分。确实,后面实现机制不太影响使用。
以下大部分内容都来自原文: How require() Actually Works
因为NodeJS是开源的,我们可以追溯
require()到node的核心代码中去。但是,我们找到的不是一个简单的函数,而是一个文件module.js。这个文件实现了node的整个模块加载系统。涵盖的过程有加载、编译和缓存。而我们使用的
require()只是其冰上一角。
module.js
[code=language-js]function Module(id, parent) { this.id = id; this.exports = {}; this.parent = parent; //...
我们可以看到module.js首先定义了一个类型(函数)Module。这个类型有两个功能。一个,它是所有模块的基类,之后每个模块都是这个Module的一个实例。这也是我们前面探讨的
module.exports最终来源。
这个Module的第二个功能就是完成Node模块的加载过程。我们使用的
require()最终就是调用module.require方法,而这个方法又调用了另外一个内部方法Module._load。最终的这个load方法才是真正加载模块文件的地方,也就是我么将要分析研究的。
Module._load
[code=language-js]Module._load = function(request, parent, isMain) { //1. Check Module._cache for the cached module. //2. Create a new module instance if cache empty //3. Save it to the cache //4. Call module.load() with your the given filename, this will call module.compile() after reading the file contents. //5. If there was error loading /parsing the file, delete the bad module from cache. //6. return module.exports };
Module._load负责装载新模块和管理模块的缓存。缓存机制在每个模块载入时减少重复读取文件,从而提高系统性能。另外,共享模块实例还可以使得单例模块在整个项目中保留状态。
如果在缓存中没有找到该模块,Module._load就会为该文件创建一个新的Module实例。并用该实例读取文件内容,然后发送给Module._compile。
注意到在上面第6步,返回了module.exports。这个返回语句可以解释,为什么在你的模块文件中要把公开的接口(方法)赋给module.exports(或者别名exports);这也解释了,require()返回的变量,可以直接调用导入模块的方法。到此,可以看到没有任何神奇或特别的地方。
module._compile
[code=language-js]Module.prototype._compile = function(content, filename) { // 1. Create the standalone require function that calls module require. // 2. Attach other helper methods to require. // 3. Wraps the JS code in a function that provides require, module, etc. variables locally to the module scope. // 4. Run that function.
这个方法,一开始就创建require函数,这就是我们非常熟悉的那个
require()(模块装载机制不仅仅有载入模块的处理,也有导入和调用的流程,这里可以看作调用方的流程,如我们提到的main.js)。而这个个函数本身只是简单的封装了Module.require和添加了一些帮助属性和方法,如下:
require() 就是我们使用的require()
require.main 主模块
require.cache 所有缓存的模块
require.extensions 不同文件类型(后缀)的编译方法
在构建require之后,所有原文件的代码被封装的一个新函数中,这个函数把require,module,exports作为参数。 这也可以解释为什么我们可以直接调用require(),为什么exports是module.exports的别名。
[code=language-js](function(exports,require, module, __filename, __dirname){ //原模块文件的所有代码注入在这 });
了解模块模式(Module Pattern)的人,很容易看出这段看时毫无意义的重新分帐,就是模块模式,就是为了防止模块文件中的定义污染系统的命名空间(记住javascript时全局变量)。
最后,这个新创建的函数直接被运行,这其实也是完整模块模式的一部分,最后那对空括弧就是运行部分:
[code=language-js](function(...){ //... })();
> 如果不熟悉模块模式(Module Patter),可以看看我另外一篇文章深入探索AngularJS的一个章节《模块模式 - Module Pattern》
完整全文:通过ANUGLAR MATERIAL串串学客户端开发
小结
至此,我们就走完了模块加载的全部流程。从创建模块module.exports到调用require,以及调用实现的内部过程。虽然这些对你代码还没有任何影响,原来该怎么做还怎么做。但是,可以让你写同样代码时,心里更有底,不再强行记忆模块的语法。最重要的事通过学习良好的代购架构,提高自己的架构水平。相关文章推荐
- node.js 初体验
- Node.js+Yeoman构建前端自动化Web应用
- HDU 4587 Two Nodes、POJ 2375 Reliable Nets (Tarjan)
- Clone Graph Leetcode 133
- DOM Nodes
- leetcode--Populating Next Right Pointers in Each Node
- Swap Nodes in Pairs
- 文件系统(一)--super.c bitmap.c inode.c 源码分析
- Swap Nodes in Pairs
- 19 Remove Nth Node From End of List(去掉链表中倒数第n个节点Easy)
- 一晚上 -- Populating Next Right Pointers in Each Node II
- [Node.js] 对称加密、公钥加密和RSA
- Node.js学习--基础知识(4)--模块与包
- Node.js学习--基础知识(3)--回调函数与事件
- node.js 初体验
- Node.js学习--基础知识(2)--异步IO与事件式编程
- LeetCode 24:Swap Nodes in Pairs
- Nodejs部署再思考
- nodejs中express安装失败解决方法
- 深入浅出NodeJS笔记(一)