介绍模块化编程,由于Node.js是一个高度模块化的平台,学习模块化能够帮助咱们快速理解和使用Node.js。详见以下,这是摘自我去年写的一篇博客笔记(博客目前已经没有在维护了)javascript
title:前端模块化演变
date:2018-10-23 23:27:56
tags:模块化css
前端产品的交付是基于浏览器,这些资源是经过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,而且保证他们在浏览器端快速、优雅的加载和更新,就须要一个模块化系统,这个理想中的模块化系统是前端工程师多年来一直探索的难题。前端
异步概念 js是单线程的,因为执行ajax请求会消耗必定的时间,甚至出现了网络故障而迟迟得不到返回结果;这时,若是同步执行的话,就必须等到ajax返回结果之后才能执行接下来的代码,若是ajax请求须要1分钟,程序就得等1分钟。若是是异步执行的话,就是告诉ajax代码“老兄,既然你迟迟不返回结果,我先不等你了,我还有一大堆代码要执行,等你执行完了给我说一下”java
模块系统主要解决模块的定义、依赖和导出,先来看看已经存在的模块系统。node
原始的 JavaScript 文件加载方式,若是把每个文件看作是一个模块,那么他们的接口一般是暴露在全局做用域下,也就是定义在 window 对象中,不一样模块的接口调用都是一个做用域中,一些复杂的框架,会使用命名空间的概念来组织这些模块的接口,典型的例子如 YUI 库jquery
这种原始的加载方式暴露了一些显而易见的弊端:web
1.全局做用域下容易形成变量冲突ajax
2.文件只能按照 <script>
的书写顺序进行加载npm
3.开发人员必须主观解决模块和代码库的依赖关系编程
4.在大型项目中各类资源难以管理,长期积累的问题致使代码库混乱不堪
服务器端的 Node.js 遵循 CommonJS规范,该规范的核心思想是容许模块经过 require 方法来同步加载所要依赖的其余模块,而后经过 exports 或 module.exports 来导出须要暴露的接口。
require("module");
module.exports = module;
复制代码
优势:
缺点:
实现:
Asynchronous Module Definition 规范其实只有一个主要接口 define(id?, dependencies?, factory),它要在声明模块的时候指定全部的依赖 dependencies,而且还要当作形参传到 factory 中,对于依赖的模块提早执行,依赖前置。
define("module", ["dep1", "dep2"], function(d1, d2) {
return someExportedValue;
});
require(["module", "../file"], function(module, file) { /* ... */ });
复制代码
优势:
实现:
Common Module Definition 规范和 AMD 很类似,尽可能保持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。
define(function(require, exports, module) {
var $ = require('jquery');
var Spinning = require('./spinning');
exports.doSomething = ...
module.exports = ...
})
复制代码
优势:
缺点:
实现:
Universal Module Definition 规范相似于兼容 CommonJS 和 AMD 的语法糖,是模块定义的跨平台解决方案。
ECMAScript6 标准增长了 JavaScript 语言层面的模块体系定义。ES6 模块的设计思想,是尽可能的静态化,使得编译时就能肯定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时肯定这些东西。
import "jquery";
export function doStuff() {}
module "localModule" {}
复制代码
优势:
缺点:
实现:
前端模块要在客户端中执行,因此他们须要增量加载到浏览器中。 模块的加载和传输,咱们首先能想到两种极端的方式,一种是每一个模块文件都单独请求,另外一种是把全部模块打包成一个文件而后只请求一次。显而易见,每一个模块都发起单独的请求形成了请求次数过多,致使应用启动速度慢;一次请求加载全部模块致使流量浪费、初始化过程慢。这两种方式都不是好的解决方案,它们过于简单粗暴。 分块传输,按需进行懒加载,在实际用到某些模块的时候再增量更新,才是较为合理的模块加载方案。 要实现模块的按需加载,就须要一个对整个代码库中的模块进行静态分析、编译打包的过程。
在上面的分析过程当中,咱们提到的模块仅仅是指JavaScript模块文件。然而,在前端开发过程当中还涉及到样式、图片、字体、HTML 模板等等众多的资源。这些资源还会以各类方言的形式存在,好比 coffeescript、 less、 sass、众多的模板库、多语言系统(i18n)等等。 若是他们均可以视做模块,而且均可以经过require的方式来加载,将带来优雅的开发体验,好比:如何使用reuqire管理.
require("./style.css");
require("./style.less");
require("./template.jade");
require("./image.png");
复制代码
在编译的时候,要对整个代码进行静态分析,分析出各个模块的类型和它们依赖关系,而后将不一样类型的模块提交给适配的加载器来处理。好比一个用 LESS 写的样式模块,能够先用 LESS 加载器将它转成一个CSS 模块,在经过 CSS 模块把他插入到页面的 <style>
标签中执行。Webpack 就是在这样的需求中应运而生。 同时,为了能利用已经存在的各类框架、库和已经写好的文件,咱们还须要一个模块加载的兼容策略,来避免重写全部的模块。 那么接下来,让咱们开始 Webpack 的神奇之旅吧。
命名冲突,文件依赖,多人协做,可维护性,开发效率
以一个计算器模块为案例说明
```javascript
function add(x,y){
return pareseInt(x) + parseInt(y)
}
function subtract(x,y){
return pareseInt(x) - parseInt(y)
}
function multiply(x,y){
return pareseInt(x) * parseInt(y)
}
function divide(x,y){
return pareseInt(x) / parseInt(y)
}
```
复制代码
>用来解决上述问题 可是命名冲突还在,若是有子命名空间就会致使命名空间愈来愈长
```javascript
var calculator={};
calculator.add=function(x,y){
return pareseInt(x) + parseInt(y)
}
...
...
calculator.add(1,3) //命名空间调用
...
...
//若是有子命名空间就会致使命名空间愈来愈长
calculator.sub={
foo:function(){
...
}
}
calculator.sub.foo()
```
复制代码
> 全局函数和对象命名空间都不能很好的解决命名冲突的问题,并且开发中会有一些不想被外部访问的私有属性,那么咱们能够经过封装函数的私有空间去让一些属性和方法私有化,也就是闭包
```javascript
var calculator=(function(){
function add(x,y){
return parseInt(x) + parseInt(y)
}
function subtract(x,y){
return pareseInt(x) - parseInt(y)
}
function multiply(x,y){
return pareseInt(x) * parseInt(y)
}
function divide(x,y){
return pareseInt(x) / parseInt(y)
}
return {
add:add,
subtarct:subtract,
multiply:multiply,
divide:divide
}
})()
calculator.add(x,y) //经过匿名函数.函数名
```
复制代码
需求:添加一个取余的方法,若是这个计算模块由第三方提供<br>
解决:经过参数的形式将原来的模块和第三方库传递进去<br>
```javascript
var calculator=(function(cal){
function add(x,y){
return parseInt(x) + parseInt(y)
}
function subtract(x,y){
return pareseInt(x) - parseInt(y)
}
function multiply(x,y){
return pareseInt(x) * parseInt(y)
}
function divide(x,y){
return pareseInt(x) / parseInt(y)
}
cal.add:add,
cal.subtarct:subtract,
cal.multiply:multiply,
cal.divide:divide
return cal;
})(calculator||{})
var carculator=(function(){
cal.mod=function(x,y){
return x % y
}
return cal;
})(calculator||{})
```
复制代码
1. js的runtime,让js脱离浏览器在服务端单独执行
2. 依赖于V8引擎进行解析代码
3. 事件驱动 事件触发才会执行响应的处理函数,这种机制就是标准的事件驱动机制
4. 非阻塞IO
5. 轻量可伸缩,适用于实时数据交互应用 利用socket能够实现双向通讯,例如聊天室的
6. 单线程和单进程
进程就是一个application的一次执行过程,
而线程是进程中的一部分,进程包含多个线程在运行,单线程就是进程中只有一个线程
阻塞Io模式下一个线程只能处理一个任务非阻塞Io下,一个线程永远在处理任务,这样的cpu利用率是100%
因此node.js采用单线程,利用事件驱动的异步编程模式来实现了非阻塞Io
复制代码
node中的global相似浏览器的window对象,用于定义全局命名空间,因此除了global以外都是他的属性
node中定义的变量默认是当前文件下的,不是全局global的,可是咱们能够手动挂载到globa上
var foo=10;
global.foo=foo;
consoloe.log(global.foo)
复制代码
exports只是返回一个Object对象,不能单独定义并返回数据类型
module.exports能够单独定义,返回数据类型
复制代码
全局做用域中任何变量函数和对象都是global对象的一个属性
全局可用就是node提供的一下全局可用变量函数以及对象不须要进行模块加载就能够直接使用
如:require是能够在每一个模块做用域中存在,不加载就可使用,能够说他全局可用而不是全局函数
全局变量
_dirname 当前文件所在目录
_filename
当前正在执行脚本的文件名,输出文件所在位置的绝对路径,若是在模块中则显示模块文件的路径
全局函数
定时器
console对象 info error warn dir time timeEnd trace(测试函数运行)
复制代码
加载模块分为两大类:文件模块和核心模块
文件模块:
-使用“/”开头的模块表示 执行当前文件所属的盘符根路径 c盘d盘...
- 以"./"或"../"开头的相对路径模块标识 能够省略后缀名js json node
核心模块:是被编译的二进制文件,保存在源码lib目录下
全局对象
经常使用工具
事件机制
文件系统访问
http服务器与客户端
复制代码
foo.js ==> console.log('foo模块被加载了')
index.js 下面只会加载一次(输出一次)。由于模块的缓存
reuqire("./foo")
reuqire("./foo")
reuqire("./foo")
reuqire("./foo")
不但愿模块缓存能够在被加载的模块(foo.js)中添加以下代码
delete require.cache[module.filename] 结果会输出四次
复制代码
js的执行环境是单线程,一次只能完成一个任务,因此常见的浏览器无响应就是由于某代码运行时间过长,致使后面任务没法执行,因此NodeJS加入了异步编程的概念,解决单线程阻塞问题
异步案例 setTimeout(()=>{},0)
复制代码
-同步代码中使用try...catch处理异常
-异步代码中不能使用try...catch处理异常
对于异步代码 try..catch是没法捕捉异步代码中出现的异常的
复制代码
异步编程提出了回调函数的设计
三个约定:
1.优先把callback当作最后一个形参
2.将代码出现的错误做为callback第一个参数
callback(err,result)
3.将代码成功返回的结果做为callback的第二个参数
callback(err,result)
** 异步编程的’事件驱动‘思路
异步函数执行时,不肯定什么时候执行完毕,回调函数会被压入到一个事件循环(Event Loop)队列,
而后往下执行其余代码,直至异步函数执行完毕后,才会开始处理事件循环,调用响应的回调函数
EventL Loop是一个先进先出的队列
复制代码
CommonJS是规范,开篇预热中已经介绍过,Node.js是这种规范的部分实现
-package.json 顶层目录的包描述文件,说明文件(开发者拿到第三方包的时候一目了然)
文件属性说明 属性和值经过json字符串形式描述
-name 包名
-description 包的描述
-version 版本号
-keywords 关键词用于npm包市场搜索
-author 包的做者
-main 配置包的入口,默认就是模块根目录下的index.js
-dependencies 包的依赖项,npm会自行下载
-scripts 指定运行脚本命令的npm命令行缩写
-bin 存放可执行的二进制文件
-lib 存放js的目录
-doc 存放文档的目录
-test 存放单元测试用例的代码
复制代码
-npm init --yes 初始一个package.json文件
-npm install 名字 安装包
-npm install 包名 --save 将安装的包添加到package.json的依赖中
-npm install 包名 -g 全局安装一个命令行工具
-npm install docs 查看包文档,很是有用
-npm root -g 查看全局包安装路径
-npm config set prefix '路径' 修改全局包安装路径
-npm list 查看当前目录下安装的全部包
-npm list -g 查看全局包的安装路径下的全部包
-npm uninstall 包 卸载当前目录下的某个包
-npm uninstall 包 -g
-npm update 包 更新当前目录下的某个包
复制代码
API: File System 简写fs
开发中建议使用异步函数,比起同步函数性能更高,速度更快,没有阻塞
方法:同步 Sync和异步
1.文件写入
fs.writeFileSync(file,data) 同步必须使用tryCatc捕获,防止出错程序意外退出
fs.writeFile(file,data,cllback) 在回调中判断err参数
2.向文件中追加内容
fs.appendFile(file,data,callback)
3.文件读取
fs.readFile(file,callback(err,data)) data.toString()转换二进制数据
4.文件复制
node没有提供这个函数,咱们本身能够封装,思路:读取一个文件写入另外一个文件
5.获取文件信息
fs.stat(path,callback)
复制代码
[ti:我爱你中国(Live)]
[ar:汪峰]
[al:歌手第二季 歌王之战]
[by:天龙888]
[00:00.00]汪峰 - 我爱你中国(Live)
[00:00.02]词:汪峰
[00:00.03]曲:汪峰
[00:00.04]原唱:汪峰
[00:00.05]编曲:黄毅
[00:00.06]Program:黄毅
[00:00.07]现场Program:汪涛
[00:00.08]制做人:黄毅
[00:00.09]音乐总监:梁翘柏
.....
复制代码
const fs = require("fs");
fs.readFile("./lrc.txt", function(err, data) {
if (err) {
return console.log("读取歌词文件失败")
}
data = data.toString();
var lines = data.split("\n");
var reg = /\[(\d{2})\:(\d{2})\.(\d{2})\]\s*(.+)/;
for (var i = 0; i < lines.length; i++) {
(function(index) {
var line = lines[index];
var matches = reg.exec(line);
if (matches) {
var m = parseFloat(matches[1]); //获取分
var s = parseFloat(matches[2]); //获取秒
var ms = parseFloat(matches[3]); //获取毫秒
var content = matches[4] //获取定时器要输出的内容
var time = m * 60 * 1000 + s * 1000 + ms; //将分+秒+毫秒转化为毫秒
setTimeout(() => {
console.log(content)
}, time);
}
})(i)
}
})
复制代码
1.Path模块之路径字符串模块
basename 获取文件名
dirname 获取文件目录
extname 获取文件扩展名
isAbsolute 判断是否为绝对路径
join(path1,path2,..) 拼接路径字符串 \\转义后为\
normalize(p) 将非标准路径转换为标准路径
sep 获取操做系统的文件路径分隔符
2.目录操做
对文件目录增长,读取,删除等操做
fs.mkdir(path,mode,callback) mode设置目录权限 默认0777
fs.rmdir(path,callback) 回调无参数 删除目录时目录中必须为空目录,须要提早读取目录和删除目录中的文件
复制代码
Buffer缓冲区 为Node提供存储原始数据的方法,用来在内存区域建立一个存放二进制数据的缓冲区
鼠标右键直接建立文件编码通常为ANSI 这时候文件中包含中文字符,这个编码不支持中文字符。因此会出现乱码
能够修改.txt文件编码为UTF-8,从新打印就没问题了
复制代码
** 缓冲区模块支持开发者在缓冲区结构中建立,读取,写入和操做二进制数据
** 此模块是全局的,使用时不须要require()函数来加载
建立方式:
1.传入字节 var buf=new Buffer(size) size=5表明建立了一个5字节的内存空间
2.传入数组 var buf=new Buffer([10,20,30,40])
3.传入字符串和编码 var buf=new Buffer('hello world','utf-8') utf-8为默认支持的编码方式能够省略
写入缓冲区:
首先将源文件的数据读取处理而后写入到Buffer缓冲区中
从缓冲区读取数据
buf.toString(encoding,start,end)
encoding 编码默认uft-8
start 指定开始读取的索引位置,默认0
end 缓冲区结束位置
拼接缓冲区
实际开发中遇到的需求:
输出多个缓冲区的内容的组合
buf.concat(list,totalLength)
list是合并的Buffer对象数组列表
totalLength用于指定合并后的Buffer对象总长度
复制代码
问题:因为Buffer缓冲区限制在1GB,超过限制的文件没法直接完成读写操做,读写大文件的时候,读写资源一直持续不停,node将没法继续其余工做,因此采用文件流的方式
解决: stream文件流来解决大数据文件操做问题,node读写很容易是内存爆仓(1GB),因此文件流的读写会防止这一现象
1.文件流的概念:
stream文件流方式: 读一部分,写一部分,好处是能够提早处理,缩短等待时间,提升速度
案例模拟:观看在线视频的时候,下载一点播放一点
Stream有4中流类型:
1.Readable 可读操做(可读流)
2.Writable 可写操做(可写流)
3.Duplex 可读可写操做(双向流,双工流)
4.Transform 操做被写入数据,而后读出结果(变换流)
NodeJS中不少模块涉及流的读写,以下:
HTTP requests and responses
Standard input/output
File reads and writes
Node.js中的I/O是异步的,因此对磁盘和网络的读写须要经过回到函数来读取数据,而回调函数须要经过事件来触发,全部的Stream对象都是EventEmitter(时间触发器)的实例
Stream经常使用事件以下:
1.data 当有数据可读时触发
2.end 没有更多的数据可读时触发
3.error 在接受和写入的过程当中发生错误时触发
4.finish 全部的数据被写入到底层系统时触发
2. Node.js的可读流和可写流
与buffer的读写操做相似,Stream中的可读流和可写流也用于读写操做
1.使用文件流进行文件复制,首先要建立一个可读流(Readable Stream) 可读流可让用户在源文件中分块读取文件中的数据,而后再从可读流中读取数据
fs.createReadStream(path,options)
options是一组 key-value值,经常使用设置以下
flags 对文件如何操做,默认为r 读文件
encoding
start
end
2.可写流(Writable Stream)
fs.createWriteStream(path,options)
方法:write将一个数据写入到可写流中
3.使用pipe()处理大文件
若是把数据比做水,chunk就至关于盆,使用盆来完成水的传递
在可读流中还有一个函数叫作pipe() 是一个很高效的文件处理方式,能够简化复制文件的操做
pipe中文管子,至关于用管子替换盆,经过管道来完成数据的读取和写入
复制代码