Node.js能够让javascript在服务端运行
异步I/O
说到异步I/O,就不得不说一下I/O,他们分别是input和output的缩写,也就是输入、输出。它是人类用来和计算机进行通信的外部硬件。输入/输出设备能够向计算机
发送数据(输出)
并从计算机接收数据(输入)
。I/O分为同步和异步,其中同步又有阻塞和非阻塞之分。异步没有,异步一定是非阻塞的。
同步过程中进程,触发IO操作并等待(也就是我们说的阻塞)或者轮询的去查看IO操作(也就是我们说的非阻塞)是否完成。
异步过程中进程触发IO操作以后,直接返回,做自己的事情,IO交给内核来处理,完成后内核通知进程IO完成。
事件驱动
单线程
跨平台
除了HTML、WebKit还有显卡这些UI技术node没有支持以外,node和谷歌浏览器非常相似,他们都是基于事件驱动的异步架构,浏览器通过事件驱动来服务页面上的交互,node通过事件驱动来服务I/O。
node不处理UI,但是node和浏览器有相同的机制和运行原理。node打破了JS只能在浏览器运行的局面,使前后端编程环境统一,可以大大降低前后端转化所需要的交换代价
在node中一个模块就是一个文件,每一个模块都有自己的作用域
对模块的定义非常简单,分为模块引用、模块标识、模块定义。
模块引用:通过require方法,接受模块标识为参数,进行引入
模块标识:必须是小驼峰命名的字符串,或者是文件相对或者绝对路径。
模块定义:也就是导出,提供了exports对象用于导出当前模块的方法或者是变量(或者是module.exports)
使用案例:
// export.js
const testVar = 1;
function test() {
console.log(this.testVar)
};
module.exports.testVar = testVar;
module.exports.fn = test;
// requare.js
const val = require('./export');
console.log(val.testVar); // 1
val.fn(); // 2
在node中,模块可以被分为两类:
- 一类是node提供的内置模块,称为核心模块。在源代码的编译过程中,编译进了二进制执行文件,在node启动的时候,部分的内置模块就会被直接加载进内存中,所以当我们引入这些模块的时候,文件定位和编译执行就可以直接省略掉,并且在路径分析中会优先判断,核心模块的加载速度是最快的。
- 另一类是用户编写的模块(第三方库也是文件模块),称为文件模块,文件模块在运行的时候动态加载的,需要完整的路径分析,文件定位和编译执行过程,速度会比核心模块的速度慢。
在node引入模块,需要经历三个步骤,
与前段浏览器会缓存静态资源脚步一样,node也会对引入的模块进行缓存,以减少二次引入时的开销。但不同的是,浏览器仅仅缓存文件,node缓存的是编译和执行之后的对象。
不论是任何模块,require对于相同模块的二次引入都是采用缓存优先的方式。
标识符有以下几种形式,所以对于不同的标识符,模块查找和定位在一定程度上也有不同。
- 核心模块,也就是node内置模块,直接通过内置模块名引入
- .或者…开始的相对路径文件模块
- /开头的绝对路径的文件模块
- 非路径形式的文件模块,比如自定义三方库名等,直接通过第三方库名引入,但注意不要和内置模块重名。
核心模块
对于核心模块,直接通过内置模块名引入,其加载速度是最快的,但注意,要加载一个和核心模块相同标识符的自定义模块,是无法成功的,因为会优先查找核心模块。
路径形式的文件模块
在分析文件模块的时候,require会将路径转化为真实路径,并以真实路径作为索引,将编译执行后的结果放入缓存中,以便于二次加载。其加载速度仅次于核心模块。
自定义模块
自定义模块指的是非核心模块,也不是路径形式的标识符,是一种特殊的文件模块,可能是一个文件或者是包的形式,这类模块查找是最费时间的,会去逐级查找node_modules目录是否有,由内至外,从当前级开始。所以当前文件的路径越深,模块查找的耗时就越长。其加载速度最慢。
文件定位其实我理解就是查找文件,确定文件位置。这个过程中会包括对文件扩展名的分析、目录和包的处理
文件扩展名分析
require引入标识符的过程中,会出现标识符不包含文件扩展名的情况,此时node会按照.js、.json、.node的次序补足扩展名,依次尝试。
但是在尝试的时候,需要调用同步阻塞式地判断文件是否存在。因为node是单线程的,所以这里会存在性能问题,如果是.json、.node的文件,在写入的时候带上扩展名,会加快一点寻找速度
目录分析和包
有的时候require分析标识符的死活,可能没有找到对应的文件,但是这个时候得到了一个对应的目录,此时node会将其当作一个包来进行处理。
首先,node会在当前目录下查找package.json文件,通过JSON.parse()解析出包的描述对象。从中取出main属性对应的入口文件进行定位。如果main属性指定的文件名错误,或者压根没有package.json文件,node会默认将index当作默认文件名,然后依次查找index.js,index.json,index.node.
在node中,每一个文件模块都是一个对象,它的定义如下:
创建一个新模块
function Module(id,parent) {
this.id = id;
this.exports = {};
this.parent = parent;
if (parent && parent.children) {
parent.children.push(this);
}
this.fillname = null;
this.loaded = false;
this.children = [];
}
在node中,不同的文件扩展名有不同的编译方式
- .js文件:通过fs同步读取文件后编译执行
- .node文件:这是用C/C++编写的扩展文件,通过dlopen()方法加载最后编译生产的文件
- .json文件:通过fs模块同步读取文件后,用JSON.parse()解析返回的结果
- 其余扩展名:都被当作js文件载入
对JS模块的编译
在模块的使用过程中,我们发现每个模块文件都有require、exports、module这三个方法,但是他们在模块中并没有定义,这是因为node对获取到的js文件会进行头尾包装
// module表示模块自身
(function (exports, require, module, __filename, __dirname) {
文件内容...
})
这样使得每个模块时间作用域都隔离开。
但这里有一个问题就是,为何exports都已经存在了,还存在module.exports,其实由于module代表的是模块本身,module.exports其实就是exports,但是这里需要注意的是,exports其实是形参传入该模块的,直接复制形参会改变形参的引用,但不能改变作用域外的值,所以不能这样写
错误写法
exports = function() {
}
正确
module.exports = {}
node编译(C/C++编译)
node调用process.dlopen()方法进行加载和执行。
JSON文件编译
node利用fs同步读取json文件的内容,通过JSON.parse()得到对象,然后把他赋给模块的exports,对外调用
案例
如果有两个模块,重复引用
// main.js
require('./moduleA');
require('./moduleB');
// 执行输出结果
B A
A BB
// moduleA.js
module.exports.test = 'A';
const moduleB = require('./moduleB'); // 引用的时候会执行moduleB
console.log('A', moduleB.test);
module.exports.test = 'AA';
// moduleB.js
module.exports.test = 'B';
const moduleA = require('./moduleA');
console.log('B', moduleA.test);
module.exports.test = 'BB';
不要路径引入,直接引入名字,比如系统内置模块fs
const fs = require('fs');
fs.readFile('./moduleA.js', (err, data) => {
console.log(data);
})
Node.js 实现了一个简单的模块加载系统。在 Node.js 中,文件和模块是一一对应的关系,可以理解为一个文件就是一个模块。其模块系统的实现主要依赖于全局对象 module,其中实现了 exports(导出)、require()(加载)等机制。
Node.js 中一个文件就是一个模块。如,在 index.js 中加载同目录下的 circle.js:
// circle.js
const PI = Math.PI
exports.area = r => PI * r * r
exports.circumference = r => 2 * PI * r
// index.js
const circle = require('./circle.js')
console.log(`半径为 4 的圆面积为 ${circle.area(4)}`) // 半径为 4 的圆面积为 50.26548245743669
circle.js 中通过 exports 导出了 area()和 circumference 两个方法,这两个方法可以其它模块中调用。
exports 与 module.exports
exports 是对 module.exports 的一个简单引用。如果你需要将模块导出为一个函数(如:构造函数),或者想导出一个完整的出口对象而不是做为属性导出,这时应该使用 module.exports。
// square.js
module.exports = width => {
return {
area: () => width * width
}
}
// index.js
const square = require('./square.js')
const mySquare = square(2)
console.log(`The area of my square is ${mySquare.area()}`) // The area of my square is 4
当 Node.js 直接运行一个文件时,require.main 属性会被设置为 module 本身。这样,就可通过这个属性判断模块是否被直接运行:
require.main === module
比如,对于上面例子的 index.js 来说, node index.js 上面值就是 true, 而通过 require(‘./index’)时, 值却是 false.
module 提供了一个 filename 属性,其值通常等于__filename。 所以,当前程序的入口点可以通过 require.main.filename 来获取。
console.log(require.main.filename === __filename) // true
使用 require.resolve()函数,可以获取 require 加载的模块的确切文件名,此操作只返回解析后的文件名,不会加载该模块。
console.log(require.resolve('./square.js')) // /Users/null/meet-nodejs/module/square.js
require.resolve 的工作过程:
require(X) from module at path Y
1. If X is a core module,
a. return the core module
b. STOP
2. If X begins with './' or '/' or '../'
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
3. LOAD_NODE_MODULES(X, dirname(Y))
4. THROW "not found"
LOAD_AS_FILE(X)
1. If X is a file, load X as JavaScript text. STOP
2. If X.js is a file, load X.js as JavaScript text. STOP
3. If X.json is a file, parse X.json to a JavaScript Object. STOP
4. If X.node is a file, load X.node as binary addon. STOP
LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
a. Parse X/package.json, and look for "main" field.
b. let M = X + (json main field)
c. LOAD_AS_FILE(M)
2. If X/index.js is a file, load X/index.js as JavaScript text. STOP
3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
4. If X/index.node is a file, load X/index.node as binary addon. STOP
LOAD_NODE_MODULES(X, START)
1. let DIRS=NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
a. LOAD_AS_FILE(DIR/X)
b. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
a. if PARTS[I] = "node_modules" CONTINUE
c. DIR = path join(PARTS[0 .. I] + "node_modules")
b. DIRS = DIRS + DIR
c. let I = I - 1
5. return DIRS
模块在第一次加载后会被缓存到 require.cache 对象中, 从此对象中删除键值对将会导致下一次 require 重新加载被删除的模块。
多次调用 require(‘index’),未必会导致模块中代码的多次执行。这是一个重要的功能,借助这一功能,可以返回部分完成的对象;这样,传递依赖也能被加载,即使它们可能导致循环依赖。
如果你希望一个模块多次执行,那么就应该输出一个函数,然后调用这个函数。
模块缓存的注意事项
模块的基于其解析后的文件名进行缓存。由于调用的位置不同,可能会解析到不同的文件(如,需要从 node_modules 文件夹加载的情况)。所以,当解析到其它文件时,就不能保证 require(‘index’)总是会返回确切的同一对象。
另外,在不区分大小写的文件系统或系统中,不同的文件名可能解析到相同的文件,但缓存仍会将它们视为不同的模块,会多次加载文件。如:require(‘./index’)和 require(‘./INDEX’)会返回两个不同的对象,无论’./index’和’./INDEX’是否是同一个文件。
当 require()存在循环调用时,模块在返回时可能并不会被执行。
// a.js
console.log('a starting')
exports.done = false
const b = require('./b.js')
console.log('in a, b.done = %j', b.done)
exports.done = true
console.log('a done')
// b.js
console.log('b starting')
exports.done = false
const a = require('./a.js')
console.log('in b, a.done = %j', a.done)
exports.done = true
console.log('b done')
// main.js
console.log('main starting')
const a = require('./a.js')
const b = require('./b.js')
console.log('in main, a.done=%j, b.done=%j', a.done, b.done)
首先 main.js 会加载 a.js,接着 a.js 又会加载 b.js。这时,b.js 又会尝试去加载 a.js。
为了防止无限的循环,a.js 会返回一个 unfinished copy 给 b.js。然后 b.js 就会停止加载,并将其 exports 对象返回给 a.js 模块。
这样 main.js 就完成了 a.js、b.js 两个文件的加载。输出如下:
$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done=true, b.done=true
当加载文件模块时,如果按文件名查找未找到。那么 Node.js 会尝试添加.js 和.json 的扩展名,并再次尝试查找。如果仍未找到,那么会添加.node 扩展名再次尝试查找。
对于.js 文件,会将其解析为 JavaScript 文本文件;而.json 会解析为 JOSN 文件文件;.node 会尝试解析为编译后的插件文件,并由 dlopen 进行加载。
路径解析
当加载的文件模块使用’/‘前缀时,则表示绝对路径。如,require(’/home/null/index.js’)会加载/home/null/index.js 文件。
而使用’./‘前缀时,表示相对路径。如,在 index.js 中 require(’./circle’)引用时,circle.js 必须在相同的目录下才能加载成功。
当没有’/‘或’./'前缀时,所引用的模块必须是“核心模块”或是 node_modules 中的模块。
如果所加载的模块不存在,require()会抛出一个 code 属性为’MODULE_NOT_FOUND’的错误。
当前模块的目录名。 与 __filename 的 path.dirname() 相同。
console.log(__dirname) // /Users/null/meet-nodejs/module
console.log(require('path').dirname(__filename)) // /Users/null/meet-nodejs/module
console.log(__dirname === require('path').dirname(__filename)) // true
module 在每个模块中表示对当前模块的引用。 而 module.exports 又可以通过全局对象 exports 来引用。module 并不是一个全局对象,而更像一个模块内部对象。
module.children
这个模块引入的所有模块对象
module.exports
module.exports 通过模块系统创建。有时它的工作方式与我们所想的并不一致,有时我们希望模块是一些类的实例。因此,要将导出对象赋值给 module.exports,但是导出所需的对象将分配绑定本地导出变量,这可能不是我们想要的结果。
// a.js
const EventEmitter = require('events')
module.exports = new EventEmitter()
// Do some work, and after some time emit
// the 'ready' event from the module itself.
setTimeout(() => {
module.exports.emit('ready')
}, 1000)
const a = require('./a')
a.on('ready', () => {
console.log('module a is ready')
})
需要注意,分配给 module.exports 的导出值必须能立刻获取到,当使用回调时其不能正常执行。
exports 别名
exports 可以做为 module.exports 的一个引用。和任何变量一样,如果为它分配新值,其旧值将会失效:
function require(...) {
// ...
((module, exports) => {
// Your module code here
exports = some_func; // re-assigns exports, exports is no longer
// a shortcut, and nothing is exported.
module.exports = some_func; // makes your module export 0
})(module, module.exports);
return module;
}
所有触发的事件的对象都是EventEmitter的实例,这些对象都暴露了EventEmitter.on()函数,允许一个或者多个函数绑定到对象触发的命名事件。
EventEmitter 对象触发事件时,所有绑定到该特定事件的函数都会被同步的调用
js中的事件是通过用户点击等操作触发的,node中EventEmitter 则是通过emit去触发的。\
const EventEmitter = require('events'); // 引入事件
class MyEmitter extends EventEmitter {} // 继承事件触发器
const myEmitter = new MyEmitter(); // 创建实例对象
myEmitter.on('event', () => {
console.log('creat Event');
})
myEmitter.emit('event') // 触发事件
myEmitter.emit(),第二个甚至之后的参数 可以将任意一组参数传递给监听器的回调函数,
当调用普通的函数时,this会被绑定到EventEmitter 实例上,但如果是箭头函数的话,this不会绑定在实例上
myEmitter.on('testThis', function(a, b) {
console.log(a, b, this, this === myEmitter);
})
myEmitter.emit('testThis', 'aaaa', 'bbbbb')
打印结果
aaaa bbbbb MyEmitter {
_events: [Object: null prototype] { testThis: [Function] },
_eventsCount: 1,
_maxListeners: undefined } true
箭头函数测试
myEmitter.on('testThis', (a, b) => {
console.log(a, b, this, this === myEmitter);
})
myEmitter.emit('testThis', 'aaaa', 'bbbbb') // aaaa bbbbb {} false
EventEmitter按照注册的顺序同步的执行所有监听器,这样可以确保事件的正确顺序,有助于避免竞争条件和逻辑错误,在适当的时候,监听函数可以使用setImmediate()或者是process.nextTick()切换到异步的操作模式
sync同步的意思
myEmitter.on('noSync', (a, b) => {
setImmediate(() => {
console.log('this is setImmediate');
})
console.log(a, b);
})
myEmitter.emit('noSync', 'aaaaa', 'bbbb');
console.log('end');
返回如下:
aaaaa bbbb
end
this is setImmediate
先测试一下案例
myEmitter.on('setInterval', (num) => {
console.log(num);
})
let num = 0;
setInterval(() => {
myEmitter.emit('setInterval', num);
num += 1;
}, 500);
返回如下:
0
1
2
3
...
如果只用once调用的 ,只调用一次,调用完了之后被注销。
myEmitter.once('setInterval', (num) => {
console.log(num);
})
let num = 0;
setInterval(() => {
myEmitter.emit('setInterval', num);
num += 1;
}, 500);
返回如下:
0
当 EventEmitter 实例中发生错误时,典型的操作是触发 'error' 事件。 这些在 Node.js 中被视为特殊情况。
如果 EventEmitter 没有为 'error' 事件注册至少一个监听器,并且触发 'error' 事件,则会抛出错误,打印堆栈跟踪,然后退出 Node.js 进程。
myEmitter.emit('error', new Error('whoops')) // 没有注册直接触发会导致抛出错误
应该为error添加监听器
myEmitter.on('error', (err) => {
console.log('whoops! there was an error')
}) // err 参数通过emit中传入一个 new Error的类
myEmitter.emit('error', new Error('whoops!'));
const a = () => {
console.log('a');
}
const b = () => {
console.log('b');
}
myEmitter.on('testRemoveAllListeners', a)
myEmitter.on('testRemoveAllListeners', b)
myEmitter.emit('testRemoveAllListeners') // a b
myEmitter.removeListener('testRemoveAllListeners', a);
myEmitter.emit('testRemoveAllListeners')// b 因为a被移除了
removeAllListeners() 移除事件所有的监听器 给绑定的事件名的时候 移除这个事件下所有的监听器,不给参数的时候,移除所有的
const a = () => {
console.log('a');
}
const b = () => {
console.log('b');
}
myEmitter.on('testRemoveAllListeners', a)
myEmitter.on('testRemoveAllListeners', b)
myEmitter.emit('testRemoveAllListeners') // a b
myEmitter.removeAllListeners('testRemoveAllListeners');
myEmitter.emit('testRemoveAllListeners')// 不输出 因为都被移除了
process 对象是一个全局变量, 因为是全局变量,所以无需使用 require()
提供了有关当前 Node.js进程的信息并对其进行控制,作为一个全局变量,我们都知道,进程计算机系统进行资源分配和调度的基本单位,是操作系统结构的基础,是线程的容器。当我们启动一个js文件,实际就是开启了一个服务进程,每个进程都拥有自己的独立空间地址、数据栈,像另一个进程无法访问当前进程的变量、数据结构,只有数据通信后,进程之间才可以数据共享,由于JavaScript是一个单线程语言,所以通过node xxx启动一个文件后,只有一条主线程
返回一个对象,存储当前环境相关的所有信息,一般很少直接用到。
一般我们会在 process.env 上挂载一些变量标识当前的环境。比如最常见的用 process.env.NODE_ENV 区分 development 和 production
在 vue-cli 的源码中也经常会看到 process.env.VUE_CLI_DEBUG 标识当前是不是 DEBUG 模式
我们知道NodeJs是基于事件轮询,在这个过程中,同一时间只会处理一件事情
在这种处理模式下,process.nextTick()就是定义出一个动作,并且让这个动作在下一个事件轮询的时间点上执行
function foo() {
console.error('foo');
}
process.nextTick(foo);
console.error('bar');
// 输出结果为bar、foo
下述方式也能实现同样效果:
setTimeout(foo, 0);
console.log('bar');
// 两者区别在于:
process.nextTick()会在这一次event loop的call stack清空后(下一次event loop开始前)再调用callback
setTimeout()是并不知道什么时候call stack清空的,所以何时调用callback函数是不确定的
属性会返回当前进程的 PID。
console.log('process PID: %d', process.pid)
//process PID: 10086
当前进程对应的父进程
获取当前进程工作目录,
process.exit()方法终止当前进程,此方法可接收一个退出状态的可选参数 code,不传入时,会返回表示成功的状态码 0。
process.on('exit', function(code) {
console.log('进程退出码是:%d', code) // 进程退出码是:886
})
process.exit(886)
在终端通过 Node 执行命令的时候,通过 process.argv 可以获取传入的命令行参数,返回值是一个数组:0、 Node 路径(一般用不到,直接忽略)
1、被执行的 JS 文件路径(一般用不到,直接忽略)
2~n、 真实传入命令的参数
所以,我们只要从 process.argv[2] 开始获取就好了
const args = process.argv.slice(2);
获取当前进程运行的操作系统平台
当前进程已运行时间,例如:pm2 守护进程的 uptime 值
process.on(‘uncaughtException’,cb) 捕获异常信息、 process.on(‘exit’,cb)进程推出监听
process.stdout 标准输出、 process.stdin 标准输入、 process.stderr 标准错误输出
指定进程名称,有的时候需要给进程指定一个名称
http://nodejs.cn/learn/the-nodejs-fs-module
访问文件系统并与文件系统进行交互
无需安装。 作为 Node.js 核心的组成部分,通过引用来使用它:
const fs = require(‘fs’)
方法:
fs.access()
: 检查文件是否存在,以及 Node.js 是否有权限访问。fs.appendFile()
: 追加数据到文件。如果文件不存在,则创建文件。fs.chmod()
: 更改文件(通过传入的文件名指定)的权限。相关方法:fs.lchmod()
、fs.fchmod()
。fs.chown()
: 更改文件(通过传入的文件名指定)的所有者和群组。相关方法:fs.fchown()
、fs.lchown()
。fs.close()
: 关闭文件描述符。fs.copyFile()
: 拷贝文件。fs.createReadStream()
: 创建可读的文件流。fs.createWriteStream()
: 创建可写的文件流。fs.link()
: 新建指向文件的硬链接。fs.mkdir()
: 新建文件夹。fs.mkdtemp()
: 创建临时目录。fs.open()
: 设置文件模式。fs.readdir()
: 读取目录的内容。fs.readFile()
: 读取文件的内容。相关方法:fs.read()
。fs.readlink()
: 读取符号链接的值。fs.realpath()
: 将相对的文件路径指针(.
、..
)解析为完整的路径。fs.rename()
: 重命名文件或文件夹。fs.rmdir()
: 删除文件夹。fs.stat()
: 返回文件(通过传入的文件名指定)的状态。相关方法:fs.fstat()
、fs.lstat()
。fs.symlink()
: 新建文件的符号链接。fs.truncate()
: 将传递的文件名标识的文件截断为指定的长度。相关方法:fs.ftruncate()
。fs.unlink()
: 删除文件或符号链接。fs.unwatchFile()
: 停止监视文件上的更改。fs.utimes()
: 更改文件(通过传入的文件名指定)的时间戳。相关方法:fs.futimes()
。fs.watchFile()
: 开始监视文件上的更改。相关方法:fs.watch()
。fs.writeFile()
: 将数据写入文件。相关方法:fs.write()
。
stat() 第一个参数是文件路径,第二个参数是一个回调函数(err, stats)=> {};
返回的stats常用以下几点属性
- 使用 stats.isFile() 和 stats.isDirectory() 判断文件是否目录或文件。
- 使用 stats.isSymbolicLink() 判断文件是否符号链接。
- 使用 stats.size 获取文件的大小
stat是异步的,也可以同步操作 statSync(),但需要注意的是同步会阻塞进程。
fs.stat('./test.txt', (err, stats) => {
if (err) {
console.log(err);
}
console.log(stats);
console.log(stats.isFile()); // true
console.log(stats.isDirectory()); // false
console.log(stats.size); // 21 文件大小
});
文件描述符
- r 读取模式
- r+ 读写模式 !!!
- rs 同步读取
- rs+ 同步读写
- w 写入模式,如果文件不存在就创建
- wx 类似于w,但是如果文件存在,则写入失败
- w+ 打开文件用于读写,将流定位到文件的开头。如果文件不存在则创建文件 也就是写入的时候会覆盖 !!!
- wx+ 类似w+ 如果文件路径存在 则写入失败
- a 打开文件用于写入,将流定位到文件的末尾。如果文件不存在则创建文件 写入的时候在文件末尾追加 !!!
- ax 同a 文件存在 则写入失败
- a+ 打开文件用于读写,将流定位到文件的末尾。如果文件不存在则创建文件 !!!
- ax+ 同a+ 文件存在 则文件读取追加失败
异步的 fs.readFile() 和同步的 fs.readFileSync()
fs.readFile()
- 第一个参数是文件路径
- 第二个参数是读取文件时用到的编码格式 一般都是’utf-8’.
- 参数三是一个回调函数,有返回成功的信息和失败的信息,如果返回成功则失败信息为null,如果返回失败则成功信息为undefined
fs.readFile('./test.txt', 'utf-8',function(error, successData) {
if (error) { // 读取成功 则error为null.
return console.log('读取文件失败', error);
}
return console.log(successData);
});
fs.writeFile()
http://nodejs.cn/api/fs.html#fswritefilefile-data-options-callback
第一个参数 文件路径
第二个参数,表示写入的内容
第三个参数,表示用什么样的形式写入文件内容
{
encoding: ‘utf-8’, 默认值:
'utf8'
flag: ‘r+’, 文件描述符 ,默认值:
'w'
signal: " ", 允许中止正在进行的写入文件
}
第四个参数,回调函数 回调只有一个参数 写入是否失败。
fs.writeFileSync('./test.txt', '2222',{ encoding: 'utf-8', flag: 'a+' },function(error) {
if (error) { // 写入成功 则error为null. 失败 则为错误对象
return console.log('读取文件失败', error);
}
});
fs.readFile('./test.txt', 'utf-8',function(error, successData) {
if (error) { // 读取成功 则error为null.
return console.log('读取文件失败', error);
}
return console.log(successData, '写入之后调用'); // successData 写入之后调用
});
fs.appendFile()
不需要像writeFile一样设置模式 直接追加 个人理解writeFile中包含appendFile的功能
- 第一个参数 文件路径
- 第二个参数 追加内容
- 第三个参数 回调 结束一个参数 err 判断是否追加成功。
fs.appendFile('./test.txt', '追加', function(err) {
console.log(err);
})
fs.open()
打开文件
- 第一个参数是文件的路径
- 第二个参数是以什么样的形式打开文件,文件描述符
- 第三个参数是回调函数 接收两个参数 err和 fd 其中err是异常信息 fd是文件描述符(linux基础)
fs.open('./test.txt', 'r', function (err, fd) {
if (err) {
return console.log(err, '打开文件失败');
}
console.log(fd, '打开文件成功');
})
fs.assecc(文件路径,(err) => {文件存在或者访问正常的话err为null, 否则存在err})
fs.asseccSync() 同步
access 中文 访问、入口、使用权等
fs.access('./test.txt', (err) => {
console.log(err);
});
创建一个文件夹 mkdir 或者同步的mkdirSync()
fs.mkdir(新的文件夹路径, (err) => {}); // 如果文件夹在改路径已存在就会报错
mkdir 中文 建立一个新的子目录
fs.mkdir('./mkNewDir', (err) => {
console.log(err);
});
读取目录的内容 会读取文件夹的内容(全部的文件和子文件夹),并返回它们的相对路径
fs.readdir()
readdirSync()
readdir 读取目录项,从目录下读取条目
fs.readdir('../node', (err, content) => {
console.log(err, content); // content: [ 'fsLearn.js', 'mkNewDir', 'test.txt', '基础.txt' ]
})
rename() 同步renameSync()
rename(当前路径,新的路径,(err) => {});
fs.rename('./mkNewDir', './testRename', (err) => {
console.log(err);
}) // 执行完成之后mkNewDir文件夹 就变成了testRename。
fs.rmdir() 同步fs.rmdirSync() 删除文件夹
删除的话可以采用替代品fs-extra npm install fs-extra,使用同fs,需要引入fs
引入的时候使用 const fsExtra = require(‘fs-extra’);
fs-extra 中的remove可以替代 rmdir,remove 可以同promise、async/awite一起使用 http://nodejs.cn/learn/working-with-folders-in-nodejs
- extra 中文 额外的
fs.rmdir('./mkNewDir', (err) => {
console.log(err); // 执行完成之后 删除调mkNewDir 文件夹 没有错误的话 err为null
})
系统中的每个文件都有路径
// 引入模块
const path = require('path')
解析和规范化都不会检查路径是否存在。 其只是根据获得的信息来计算路径
获取路径的目录部分。
const path = require('path');
const pathName = './learn/test/zh.txt'
path.dirname(pathName) // ./learn/test
获取文件名部分。指定第二个参数可以获取不带扩展名的文件名,第二个参数为扩展名
const path = require('path');
const pathName = './learn/test/zh.txt'
// 获取当前文件名
console.log(path.basename(pathName)); // zh.txt
// 获取不带扩展名的文件名
console.log(path.basename(pathName, path.extname(pathName))); // zh
获取文件的扩展名。
const path = require('path');
const pathName = './learn/test/zh.txt'
// 获取当前文件扩展名
console.log(path.extname(pathName)); // .txt
连接路径的两个或多个片段
const path = require('path');
const name = 'test'
const test = path.join('/', 'users', name, 'join.txt');
console.log(test); // /users/test/join.txt
获得相对路径的绝对路径计算
// path.resolve()
const path = require('path');
// 传一个参数的时候,以当前文件所在的绝对路径,拼接testResolve
path.resolve('testResolve.js'); // /Users/xiannv/Documents/软件学习资料/LearnExample /node/testResolve.js
// 再给前面加参数的时候,就会再拼起来
path.resolve('test', 'testResolve.js');
// /Users/xiannv/Documents/软件学习资料/LearnExample /node/test/testResolve.js
// 但是如果第一个参数以斜杠开头,则表示它是绝对路径:
path.resolve('/test', 'testResolve.js');// /test/testResolve.js
当包含诸如 .
、..
或双斜杠之类的相对说明符时,其会尝试计算实际的路径:
// path.normalize
path.normalize('/users/joe/..//test.txt') //'/users/test.txt'
如果是绝对路径,则返回 true
path.isAbsolute('/test/absolute') ; // true
path.isAbsolute('./test/absolute'); // false
解析路径片段,并返回信息对象
root: 根路径。
dir: 从根路径开始的文件夹路径。
base: 文件名 + 扩展名
name: 文件名
ext: 文件扩展名
// 传入相对路径
path.parse('./test/parse.js')
// 返回如下
{
root: '',
dir: './test',
base: 'parse.js',
ext: '.js',
name: 'parse'
}
// 传入绝对路径
path.parse('/test/parse.js')
{
root: '/',
dir: '/test',
base: 'parse.js',
ext: '.js',
name: 'parse'
}
接受两个路径作为参数, 基于当前工作目录,返回从第一个路径到第二个路径的相对路径。
path.relative('/Users/joe', '/Users/joe/test.txt'); // test.txt
buffer是什么?
buffer 是内存区域,它表示在V8引擎之外配置的固定大小的内存块,无法调整大小。
可以将buffer看作一个整数数组,每个整数代表一个数据字节
什么情况下使用?
当流处理器接收数据的速度大于其消耗的速度时,此时就会将数据先放入buffer中
Buffer.from(array)
使用 0 – 255 范围内的字节 array 分配新的 Buffer。 该范围之外的数组条目将被截断以符合它
如果 array 不是 Array 或其他适用于 Buffer.from() 变体的类型,则将抛出 TypeError。
// 创建一个包含 [0x62, 0x75, 0x66, 0x66, 0x65, 0x72] 的 Buffer
const buffer = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
Buffer.from(arrayBuffer[, byteOffset[, length\]])
返回一个新建的与给定的 ArrayBuffer 共享同一内存的 Buffer。
arrayBuffer
byteOffset 要暴露的第一个字节的索引 默认值 0
length 长度 默认值: arrayBuffer.byteLength - byteOffset
支持 ArrayBuffer 可以覆盖超出 TypedArray 视图边界的内存范围。 使用 TypedArray 的 buffer 属性创建的新 Buffer 可能会超出 TypedArray 的范围
const arr = new Uint16Array(2);
arr[0] = 5000;
arr[1] = 4000;
和 arr共享内存
const buf = Buffer.from(arr.buffer);
console.log(buf); //
更改原始的 Uint16Array 也会更改缓冲区
arr[1] = 6000;
console.log(buf); //
使用可选的 byteOffset 和length 指定了 arrayBuffer 中将由 Buffer 共享的内存范围
const ab = new ArrayBuffer(10);
const buf1 = Buffer.from(ab, 0, 2);
console.log(buf1.length); // 2
Buffer.from(buffer)
Buffer.from(buffer)
要从中复制数据的现有 Buffer 或 Uint8Array。
const bufferA = Buffer.from('buffer');
const bufferB = Buffer.from(bufferA);
console.log(bufferB.toString());
修改bufferA
bufferA[0] = 0x61;
A修改之后,不会影响bufferB
console.log(bufferA.toString());
console.log(bufferB.toString());
Buffer.from(string[, encoding\])
buffer.from(string[,encoding]) 创建包含string的新 Buffer。 encoding 参数标识将 string 转换为字节时要使用的字符编码
string 要编码的字符串
encoding 可选 string 的编码。 默认值: ‘utf8’
如果 string 不是字符串或其他适用于 Buffer.from() 变体的类型,则将抛出 TypeError
const bufString = Buffer.from('this is a tést');
console.log(buf1.toString()); // this is a tést
alloc ===>> 中文:分配
size buffer所需要的长度 用于预填充新 Buffer 的值。 默认值: 0
encoding如果fill是字符串的话,则这就是它的编码, 默认为’utf-8’
如果 size 大于 buffer.constants.MAX_LENGTH 或小于 0,则抛出 ERR_INVALID_ARG_VALUE。
Buffer.alloc()比Buffer.allocUnsafe()慢,但是可以保证buffer中不包含敏感数据
const bufferAlloc = Buffer.from(5);
console.log(bufferAlloc);
const bufferAlloc = Buffer.from(5, 'a');
console.log(buf); //
size是新的Buffer所需的长度
这种方式创建的Buffer实例的底层内存不会被初始化,新创建的Buffer的内容是未知的,可能包含敏感的数据,使用Buffer.alloc()用来零初始化Buffer实例
如果size不是数值,则会抛出TypeError
const buf = Buffer.from('buffer')
console.log(buf[0]) // 返回结果为第一个字符 b
console.log(buf[1]) // 返回结果为第一个字符 u
console.log(buf.toString())
const buf = Buffer.from('hello')
console.log(buf.length); // 5
const buf = Buffer.from('hello');
for (const item of buf) {
console.log(item)
}
104
101
108
108
111
write, 写入字符串
也可以数组索引方式去修改
const buf = Buffer.from('hello');
buf.write('write');
console.log(buf.toString()); // write 直接被重置
buf.write('four');
console.log(buf.toString()); // foure 前四位被重重
// 测试如果大于长度
buf.write('testSex');
console.log(buf.toString()); // testS 只有5位被写入
buf[0] = 111; // 111是o的UTF-8编码
console.log(buf.toString()); // oestS
copy
必传参数:新的buffer
三个可选参数
新的buffer开始写入的位置
buf内开始复制的偏移量。默认值: 0
buf 内停止复制的偏移量(不包括)。 默认值: buf.length.
const buf = Buffer.from('hello');
let bufCopy = Buffer.alloc(4);
buf.copy(bufCopy)
console.log(buf.toString(), bufCopy.toString()) // hello hell bufCopy复制了buf的内容
// 如果传入三个参数,则
let bufCopy1 = Buffer.alloc(2); // 分配两个字节的位置
buf.copy(bufCopy1, 0, 0, 2)
console.log(bufCopy1.toString()) // he
buf.copy(bufCopy1, 0, 1, 3)
console.log(bufCopy1.toString()) // el
// slice
// 两个参数 第一个是开始位置
// 第二个参数是结束位置
const buf = Buffer.from('Hey!')
buf.slice(0).toString() //Hey!
const slice = buf.slice(0, 2)
console.log(slice.toString()) //He
buf[1] = 111 //o
console.log(slice.toString()) //Ho
流是什么
http://nodejs.cn/learn/nodejs-streams
流是为node.js应用提供动力的基础概念之一
它们是一种以高效的方式处理 读、写文件,网络通信,或者任何类型的端到端的信息交换
程序使用传统的方式读取文件的时候,会将文件从头到尾的写入内存,然后进行处理。但是使用流,可以逐个片段读取并处理,并且无需全部保存在内存中流的优点
内存效率:不需要加载大量的数据到内存中即可进行处理
时间效率:获取到数据之后就可以立刻开始处理数据,这样需要的时间更少,不需要等到整个的数据都读入内存之后才开始使用
使用流编写 从磁盘读取文件,并在与 HTTP 服务器建立新连接时通过 HTTP 提供文件
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
const stream = fs.createReadStream(__dirname, '/test.txt')
stream.pipe(res);
})
server.listen(3000);
是用来获取来源流,并将其通过管道传输给目标流,也可以连接使用
stream.pipe(data1).pipe(data2);
流分为四种
Readable: 可以通过管道读取,但是不能通过管道写入的流(可以接收数据,但是不能发送数据的流),当推送数据到可读流中时,会对其进行缓冲,直到使用者开始读取数据为止。
Writable: 可以通过管道写入,但是不可以通过管道读取的流(可以发送数据,但是不能接收数据)
Duplex: 可以通过管道写入和读取的流
Transform: 类似于双工流,但其输出的是其输入的转换流
const Stream = require('stream') // 引入流
const ReadableStream = new Stream.Readable(); // 创建流
实现一个读的方法
ReadableStream._read = () => {};
也可以直接在创建的时候写入read方法
const ReadableStream = new Stream.Readable({
read() {}
});
完成初始化之后,开始发送数据
ReadableStream.push('hi');
const Stream = require('stream')
const writeStream = new Stream.Writable();
writeStream._write = (chunk, encoding, next) => {
console.log(chunk.toString())
next()
}
初始化结束之后,可以通过以下方式传输可读流:
process.stdin.pipe(writableStream)
从可读流中获取数据,
方法一:使用可写流读取
const Stream = require('stream')
const readableStream = new Stream.Readable({
read() { }
})
const writeStream = new Stream.Writable();
writeStream._write = (chunk, encoding, next) => {
console.log(chunk.toString())
next()
}
readableStream.pipe(writeStream)
readableStream.push('hi'); // hi
readableStream.push('ho'); // ho
方法二:通过readable事件直接消费可读流
const Stream = require('stream') // 引入流
const readableStream = new Stream.Readable(); // 创建流
//实现一个读的方法
readableStream._read = () => {};
// 完成初始化之后,开始发送数据
readableStream.push('hi');
readableStream.on('readable', () => {
console.log(readableStream.read()) //
})
使用流的write() 方法
writeStream.write('write');
const Stream = require('stream')
const readableStream = new Stream.Readable({
read() {}
})
const writableStream = new Stream.Writable()
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString())
next()
}
readableStream.pipe(writableStream)
readableStream.push('hi!')
readableStream.push('ho!')
writableStream.end()
os 操作系统模块。用于从底层的操作系统和程序运行所在的计算机上检索信息并进行交互
const os = require(‘os’);
http://nodejs.cn/api/os.html#os_signal_constants
http://nodejs.cn/learn/the-nodejs-os-module
- os.arch() 返回二进制文件的操作系统。返回标识底层架构的字符串,例如arm、x64、arm64
- 返回结果相当于 process.arch()
os.arch(); // x64
- 返回系统可用的CPU信息
- os.cpus()
os.cpus()
[ { model: 'Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz', // 模型 机器
speed: 2300, // 速度
times:{
user: 14668580, // CPU在用户模式下花费的毫秒数
nice: 0, // CPU在良好模式花费的毫秒数
sys: 9683670, // CPU在系统模式下花费的毫秒数
idle: 53402030, // CPU在空闲模式下花费的毫秒数
irq: 0 // CPU在中断请求模式下花费的毫秒数
},
},
{ model: 'Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz',
speed: 2300,
times: { user: 4355800, nice: 0, sys: 3203210, idle: 70190940, irq: 0 }
},
]
- 返回标识 为其编译node二进制文件的CPU字节序的字符串
- os.endianness()
- 返回BE: 大端序 、 LE :小端序
- endianness 英文 字节顺序
os.endianness() // LE
返回空闲的系统内存量 以字节为单位
os.freemem();
os.freemem() // 119418880
返回当前用户的主目录的路径
os.homedir() // /Users/bianlifeng
os.hostname()
os.hostname() // bogon
返回对平均负载的计算,返回包含1、5、15分钟平均负载的数组
负载是指:cpu的使用率
平均负载是操作系统计算的系统活动量度,并表示为小数。
平均负载是 Unix 特有的概念。 在 Windows 上,返回值始终为 [0, 0, 0]
os.loadavg() // [ 2.923828125, 2.998046875, 4.12744140625 ]
返回系统可用的网络接口的详细信息,返回包含已分配网络地址的网络接口对象。
返回对象的每个key都标识着一个网络接口,关联的值是每个对象描述一个分配的网络地址的对象数组os.networkInterfaces()
os.networkInterfaces()
{
lo0: [{
address: '127.0.0.1', // 分配的IPV4和IPv6地址
netmask: '255.0.0.0', // IPV4和IPv6网络掩码
family: 'IPv4', // 会返回4(IPv4) 或者 6(IPv6)
mac: '00:00:00:00:00:00', // 网络接口的MAC地址
internal: true, // 如果网络接口是不能远程访问的环回或者是类似接口,就为true,否则为false
cidr: '127.0.0.1/8' // 使用 CIDR 表示法的路由前缀分配的 IPv4 或 IPv6 地址。 如果 netmask 无效,则此属性设置为 null
}],
en0: [{
address: 'fe80::5a:6abc:dc2c:aca1',
netmask: 'ffff:ffff:ffff:ffff::',
family: 'IPv6',
mac: '8c:85:90:06:22:5a',
scopeid: 6, // 数字的ipv6范围id, 只有IPV6有
internal: false,
cidr: 'fe80::5a:6abc:dc2c:aca1/64'
}],
}
返回node编译的平台, 相当于process.platform()
返回标识为其编译 Node.js 二进制文件的操作系统平台的字符串。 该值在编译时设置。 可能的值为 ‘aix’、‘darwin’、‘freebsd’、‘linux’、‘openbsd’、‘sunos’、以及 ‘win32’
如果是安卓设备,则返回值为’android’
os.platform() // darwin
标识操作系统版本号的字符串
os.release()
release // 英文 释放
os.release() // 20.5.0
返回操作系统默认的临时文件的目录
os.tmpdir() // /var/folders/zq/6v7d1c3d6z9_lx89wj9n08q40000gp/T
返回系统中总内存的字节数
os.totalmem() // 8589934592
标识操作系统
Linux 上返回 ‘Linux’, macOS上返回‘Darwin’,在window上面返回‘Window_NT’
os.type() // Darwin
以秒为单位返回系统正常运行时间
os.uptime() // 189973
返回包含当前 username、uid、gid、shell 和 homedir 的对象
os.userInfo()
{
uid: 502,
gid: 20,
username: 'bianlifeng',
homedir: '/Users/bianlifeng',
shell: '/bin/zsh'
}
返回标识内核版本的字符串
- v13.11.0, v12.17.0 新增属性
os.version()
返回操作系统常量
os.constants()
http 模块, 提供了一些网络相关的模块
http://nodejs.cn/learn/the-nodejs-http-modulehttp 模块是 Node.js 中非常重要的一个核心模块。通过 http 模块,你可以使用其 http.createServer 方法创建一个 http 服务器,也可以使用其 http.request 方法创建一个 http 客户端。(本文先不说),Node 对 HTTP 协议及相关 API 的封装比较底层,其仅能处理流和消息,对于消息的处理,也仅解析成报文头和报文体,但是不解析实际的报文头和报文体内容。这样不仅解决了 HTTP 原本比较难用的特性,也可以支持更多的 HTTP 应用.
引入:const http = require(‘http’);
const http = require(‘http’);
console.log(http.METHODS)
返回所有的HTTP状态码及其描述
console.log(http.STATUS_CODES)
指向 Agent 对象的全局实例,该实例是 http.Agent 类的实例。
http://nodejs.cn/api/http.html#class-httpagent
console.log(http.globalAgent)
http.createServer() 返回http.server类的新实例
http://nodejs.cn/api/http.html#httpcreateserveroptions-requestlistener
const server = http.createServer((req, res) => {
使用此回调处理每个单独的请求。
})
发送http请求到服务器,并创建http.ClientRequest()类的实例
类似于http.request(),但是会自动设置Http方法为get,并自动调用req.end()
IncomingMessage 对象是由 http.Server 或 http.ClientRequest 创建的,并作为第一参数分别传递给 http.Server 的’request’事件和 http.ClientRequest 的’response’事件。
它也可以用来访问应答的状态、头文件和数据等。 IncomingMessage 对象实现了 Readable Stream 接口,对象中还有一些事件,方法和属性。
在 http.Server 或 http.ClientRequest 中略有不同。
实现 HTTP 服务端功能,要通过 http.createServer 方法创建一个服务端对象 http.Server。
这个方法接收一个可选传入参数 requestListener,该参数是一个函数,传入后将做为 http.Server 的 request 事件监听。不传入时,则需要通过在 http.Server 对象的 request 事件中单独添加。
var http = require('http')
// 创建server对象,并添加request事件监听器
var server = http.createServer(function(req, res) {
res.writeHeader(200, { 'Content-Type': 'text/plain' })
res.end('Hello Nodejs')
})
// 创建server对象,通过server对象的request事件添加事件事件监听器
var server = new http.Server()
server.on('request', function(req, res) {
res.writeHeader(200, { 'Content-Type': 'text/plain' })
res.end('Hello Nodejs')
})
http.Server 对象是一个事件发射器 EventEmitter,会发射:request、connection、close、checkContinue、connect、upgrade、clientError 事件。
其中 request 事件监听函数为 function (request, response) { },该方法有两个参数:request 是一个 http.IncomingMessage 实例,response 是一个 http.ServerResponse 实例。
http.Server 对象中还有一些方法,调用 server.listen 后 http.Server 就可以接收客户端传入连接。
http.ServerResponse 对象用于响应处理客户端请求。
http.ServerResponse 是 HTTP 服务器(http.Server)内部创建的对象,作为第二个参数传递给 'request’事件的监听函数。
http.ServerResponse 实现了 Writable Stream 接口,其对于客户端的响应,本质上是对这个可写流的操作。它还是一个 EventEmitter,包含:close、finish 事件。
创建 http.Server 使用 http.createServer()方法,为了处理客户端请求,需要在服务端监听来自客户的’request’事件。
'request’事件的回调函数中,会返回一个 http.IncomingMessage 实例和一个 http.ServerResponse。
const http = require('http')
/**
* @param {Object} req 是一个http.IncomingMessag实例
* @param {Object} res 是一个http.ServerResponse实例
*/
const server = http.createServer((req, res) => {
console.log(req.headers)
res.end(`Hello Nodejs`)
})
server.listen(3000)
http.ServerResponse 实例是一个可写流,所以可以将一个文件流转接到 res 响应流中。下面示例就是将一张图片流传送到 HTTP 响应中:
const http = require('http')
/**
* @param {Object} req 是一个http.IncomingMessag实例
* @param {Object} res 是一个http.ServerResponse实例
*/
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'image/jpg' })
const r = require('fs').createReadStream('./kobe.jpg')
r.pipe(res)
})
server.listen(3000)
在流传输过程中,为减少传输数据加快传输速度,往往会对流进行压缩。
HTTP 流就是如此,为提高网站响应速度,会在服务端进行压缩,客户端收到数据后再进行相应的解压。
Node.js 中的 Zlib 模块提供了流压缩与解压缩功能,Zlib 模块提供了对 Gzip/Gunzip、Deflate/Inflate、DeflateRaw/InflateRaw 类的绑定,这些类可以实现对可读流/可写流的压缩与解压。
deflate(RFC1951)是一种压缩算法,使用 LZ77 和哈弗曼进行编码。gzip(RFC1952)一种压缩格式,是对 deflate 的简单封装,gzip = gzip 头(10 字节) + deflate 编码的实际内容 + gzip 尾(8 字节)。在 HTTP 传输中,gzip 是一种常用的压缩算法,使用 gzip 压缩的 HTTP 数据流,会在 HTTP 头中使用 Content-Encoding:gzip 进行标识。
HTTP Request Header 中 Accept-Encoding 是浏览器发给服务器,声明浏览器支持的解压类型
Accept-Encoding: gzip, deflate, br
HTTP Response Header 中 Content-Encoding 是服务器告诉浏览器 使用了哪种压缩类型
Content-Encoding: gzip
对 web 性能优化有所了解的同学,相信对 gzip 都不陌生,我们就通过 gzip 来了解 zlib 模块.
const zlib = require('zlib')
const fs = require('fs')
const gzip = zlib.createGzip()
const inp = fs.createReadStream('zlib.txt')
const out = fs.createWriteStream('zlib.txt.gz')
inp.pipe(gzip).pipe(out)
const zlib = require('zlib')
const fs = require('fs')
const gunzip = zlib.createGunzip()
const inp = fs.createReadStream('./un-zlib.txt.gz')
const out = fs.createWriteStream('un-zlib.txt')
inp.pipe(gunzip).pipe(out)
const fs = require('fs')
const http = require('http')
const zlib = require('zlib')
const filepath = './index.html'
const server = http.createServer((req, res) => {
const acceptEncoding = req.headers['accept-encoding']
if (acceptEncoding.includes('gzip')) {
const gzip = zlib.createGzip()
res.writeHead(200, {
'Content-Encoding': 'gzip'
})
fs.createReadStream(filepath)
.pipe(gzip)
.pipe(res)
} else {
fs.createReadStream(filepath).pipe(res)
}
})
server.listen(4396)
DNS(Domain Name System,域名系统),DNS 协议运行在 UDP 协议之上,使用端口号 53。DNS 是因特网上作为域名和 IP 地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的 IP 数串。简单的说,就是把域名(网址)解析成对应的 IP 地址。Node.js 的 dns 模块,提供了 DNS 解析功能。当使用 dns 模块中的 net.connect(80, ‘github.com/webfansplz’)方法 或 http 模块的 http.get({ host: ‘github.com/webfansplz’ })方法时,在其底层会使用 dns 模块中的 dns.lookup 方法进行域名解析。
使用操作系统底层的 DNS 服务进行域名解析时,不需要连接到网络仅使用系统自带 DNS 解析功能。这个功能由 dns.lookup()方法实现。
dns.lookup(hostname[, options], callback):将一个域名(如:‘www.baidu.com’)解析为第一个找到的 A 记录(IPv4)或 AAAA 记录(IPv6)
hostname 表示要解析的域名。
options 可以是一个对象或整数。如果没有提供 options 参数,则 IP v4 和 v6 地址都可以。如果 options 是整数,则必须是 4 或 6。如果 options 是对象时,会包含以下两个可选参数:
callback 回调函数,参数包含(err, address, family)。出错时,参数 err 是 Error 对象。address 参数表示 IP v4 或 v6 地址。family 参数是 4 或 6,表示 address 协议版本。
const dns = require('dns')
dns.lookup(`www.github.com`, (err, address, family) => {
if (err) throw err
console.log('地址: %j 地址族: IPv%s', address, family) // 地址: "13.229.188.59" 地址族: IPv4
})
在 dns 模块中,除 dns.lookup()方法外都是使用 DNS 服务器进行域名解析,解析时需要连接到网络。
dns.resolve(hostname[, rrtype], callback):将一个域名(如 ‘www.baidu.com’)解析为一个 rrtype 指定类型的数组
hostname 表示要解析的域名。
rrtype 有以下可用值:
rrtype | records 包含 | 结果的类型 | 快捷方法 |
---|---|---|---|
‘A’ | IPv4 地址 (默认) | string | dns.resolve4() |
‘AAAA’ | IPv6 地址 | string | dns.resolve6() |
‘ANY’ | 任何记录 | Object | dns.resolveAny() |
‘CNAME’ | 规范名称记录 | string | dns.resolveCname() |
‘MX’ | 邮件交换记录 | Object | dns.resolveMx() |
‘NAPTR’ | 名称权限指针记录 | Object | dns.resolveNaptr() |
‘NS’ | 名称服务器记录 | string | dns.resolveNs() |
‘PTR’ | 指针记录 | string | dns.resolvePtr() |
‘SOA’ | 开始授权记录 | Object | dns.resolveSoa() |
‘SRV’ | 服务记录 | Object | dns.resolveSrv() |
‘TXT’ | 文本记录 | string[] | dns.resolveTxt() |
callback 回调函数,参数包含(err, addresses)。出错时,参数 err 是 Error 对象。addresses 根据记录类型的不同返回值也不同。
const dns = require('dns')
dns.resolve('www.baidu.com', 'A', (err, addresses) => {
if (err) throw err
console.log(`IP地址 : ${JSON.stringify(addresses)}`) // IP地址 : ["163.177.151.110","163.177.151.109"]
})
// or
dns.resolve4('www.baidu.com', (err, addresses) => {
if (err) throw err
console.log(`IP地址 : ${JSON.stringify(addresses)}`) // IP地址 : ["163.177.151.110","163.177.151.109"]
})
使用 getnameinfo 方法将传入的地址和端口解析为域名和服务
dns.reverse(ip, callback)
ip 表示要反向解析的 IP 地址。
callback 回调函数,参数包含(err, domains)。出错时,参数 err 是 Error 对象。domains 解析后的域名数组。
dns.reverse('8.8.8.8', (err, domains) => {
if (err) throw err
console.log(domains) // [ 'dns.google' ]
})
dns.lookupService(address, port, callback)
address 表示要解析的 IP 地址字符串。
port 表示要解析的端口号。
callback 回调函数,参数包含(err, hostname, service)。出错时,参数 err 是 Error 对象。
dns.lookupService('127.0.0.1', 80, function(err, hostname, service) {
if (err) throw err
console.log('主机名:%s,服务类型:%s', hostname, service) // 主机名:localhost,服务类型:http
})
忽略一些文件,使它不上传到git上面
规则:
- 前面加/:表示项目根目录
- 后面加/:代表的是目录
- 前面加!:表示取反,也就是不忽略当前文件或者文件夹
- *:表示任意字符
- ?:表示匹配任意字符
- **:表示匹配多级目录
在包发布时用于排除某些文件或目录。
比如我下载了一个名为
cli-ux
的npm包,这是一组常见的 CLI UX 工具函数。该项目的根目录下有一个名为/test
的文件夹,里面放的是测试脚本。为了使这个npm包更小,我们可以不要这些文件。我可以创建一个 npmignore 文件里面包含/test
,这样在运行npm publish
打包时,可以避免将该文件夹打包到项目中。
说eslint之前先说一下git hooks
Git hooks是什么?
- 有时我们想要在git操作时候进行一些定制化操作,比如在git commit时候检查一下提交内容是否合规、git push时候检查一下资源文件大小等等,这些功能需要我们可以在git命令执行前后进行拦截,git hooks提供了这样的能力
每个通过git管理的项目,在.git/hooks/这个隐藏文件夹中,会提供一些默认的git hooks文件,比如pre-commit.sample pre-push.sample等。当我们执行git命令时,git会执行相应命令的相关文件,
- pre-commit在我们新增一个commit前
- prepare-commit-msg在我们编辑一个commit的消息前调用
- commit-msg在我们编辑完一个commit的消息后调用
- pre-push在我们执行一次push操作前调用
由于git默认并不会提供具体的hook操作,所以这些文件都是
.sample
类型的文件,不会自动执行,所以我们需要自己定义操作。(也可以用pre-commit插件https://pre-commit.com/),安装的时候使用–save-dev,就会安装在devdepen
package.json
先安装npm install eslint -- global,然后使用eslint --init初始化eslint,然后安装pre-commit,在scripts里面新建一个命令lint,去eslint检验,然后在pre-commit里面调用lint,就可以在commit之前校验eslit,当然也可以在其他时机使用,也可以使用pre-push插件
{
"name": "node",
"version": "1.0.0",
"description": "common JS node 模块规范 每一个文件就是一个模块,有自己的作用域",
"main": "index.js",
"scripts": {
"lint": "eslint ."
},
"pre-commit": [
"lint"
],
"devDependencies": {
"pre-commit": "^1.2.2"
},
"author": "",
"license": "ISC"
}
静态资源服务器本质上就是http 客户端发起http请求,服务器给http进行相应。
Supervisor 插件 每次修改不需要重新启动node 借助Supervisor
实现静态资源服务器 运行当前进程node 获取到当前文件夹下所有目录 通过访问对应的文件,获得对应文件的内容或者是文件夹列表
// 简易版
const http = require('http');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs')
const port = 3000;
const hostname = '127.0.0.1';
const nowdir = process.cwd(); // 获取当前进程工作目录
const server = http.createServer((req, res) => {
console.log();
const filePath = path.join(nowdir, req.url);
fs.stat(filePath, (err, stats) => {
if (err) {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end(`${filePath} is not have`);
return;
}
// 如果是文件的话
if (stats.isFile()) {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
// 读取文件 通过流的形式一点点吐给客户端
fs.createReadStream(filePath).pipe(res);
// 这一步读取 也可以用readfile去进行读取 但是这个是所有的读取完了之后才会传给客户端,不建议使用
// fs.readFile(filePath, (err, data) => {
// res.end(data);
// })
} else if (stats.isDirectory()){
fs.readdir(filePath, (err, files) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(files.join(','))
})
}
})
})
server.listen(port, () => {
const str = `http://${hostname}:${port}/`;
console.log(`服务器运行在 ${chalk.green(str)}`)
})
// 不使用回调函数,使用异步的方式
const http = require('http');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs')
const promisify = require('util').promisify;
const stat = promisify(fs.stat);
const readdir = promisify(fs.readdir)
const port = 3000;
const hostname = '127.0.0.1';
const nowdir = process.cwd(); // 获取当前进程工作目录
const handleRes = async (req, res) => {
const filePath = path.join(nowdir, req.url);
try {
const stats = await stat(filePath);
if (stats.isFile()) {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
// 读取文件 通过流的形式一点点吐给客户端
fs.createReadStream(filePath).pipe(res);
// 这一步读取 也可以用readfile去进行读取 但是这个是所有的读取完了之后才会传给客户端,不建议使用
// fs.readFile(filePath, (err, data) => {
// res.end(data);
// })
} else if (stats.isDirectory()) {
const files = await readdir(filePath);
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(files.join('\n'))
}
} catch (err) {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end(`${filePath} is not have`);
}
}
const server = http.createServer((req, res) => {
handleRes(req, res)
})
server.listen(port, () => {
const str = `http://${hostname}:${port}/`;
console.log(`服务器运行在 ${chalk.green(str)}`)
})
EJS
http://ejs.co/
EJS是CanJS默认的模板语言,它提供了与Observes的实时绑定的使用。EJS非常易于使用,在模板中写入想要的HTML,以及一些表示动态行为的魔法标签即可。JES不支持block功能。