有助于理解前端工具的 node 知识

缘起

平时写惯了业务代码以后,若是想要了解下 webpack 或者 vue-cli,好像是件很难上手的事情🙁 。拿 webpack 来讲,咱们可能会对配置熟悉点,但经常一段时间事后又忘了,感受看起来不是很好懂。其实相似这种打包工具、构建工具咱们最好应该先去学习一下 node 的一些基础知识,而后再回过头来看这些工具,就会有柳暗花明又一村的感受,由于这些工具是用 node 写出来的🤯。
想一想咱们是否是时常看到过这种东西:const path = require('path');。假设你学过前端框架但没学过 node,你看到这句话的时候就会一头雾水,好像知道它是弄路径的,但具体这是哪里来的,经常使用来作什么就不得而知了,我起初看的感受就是这样🤨。
后来才知道这实际上是 node 的内置模块,由于这些构建工具或打包工具是用 node 来执行的,只要咱们有装 node,它里面的内置模块就能直接引用,不用另外安装。因此强烈建议你们要是想了解这类工具最好先学习一下 node,否则会老是懵逼的🧐。
言归正传,本篇就来简要讲述一下 node 的一些经常使用内置模块。html

node 初识

node 是什么

首先 node 不是一门后台语言而是一个环境,一个可以让 js 运行在服务器的环境,这个环境就比如是服务器上的浏览器(虽然不是很恰当),但正是由于有了它才使得 js 变成了一门后台语言。前端

node 遵循的规范

其次 node 遵循的是 CommonJs 规范,什么意思?其实就是规定了导入导出的方式😬,就向下面这样:vue

require('./module')
module.exports = {
    a: 1,
}
exports.a = 1;
复制代码

这就是 node 的规范,用 require 导入、用 module.exports 导出。那 node 为何不支持 ESM(就是用 import 导入、用 export 导出)规范呢,由于它出现的比较早,仅此而已,而后一时半会儿还改不过来,之后应就会支持了。另外,咱们时常在 webpack 里看到 require() 字样却没有看见 import() 就是由于 webpack 是要用 node 来执行的,而 node 目前只支持 require()
这里顺带来一张各类规范图(这种东西容易忘,看成历史看看就行🙄),以下: node

require 寻找依赖

require() 里面的参数有两种写法,一种带路径一种不带路径。就像下面这样:webpack

require('./module'); // 带相对路径
require('/module'); // 带绝对路径
require('module'); // 不带路径
复制代码

这种不带路径的 require('module') 引入方式,多是内置模块,也多是第三方模块,内置模块优先查找,没有的话就是第三方模块了,它会先从当前目录的 node_modules 里面查找,没有的话就到父目录下的 node_modules 里面去找,如此向上追溯,直到根目录下的 node_modules 目录,要是尚未的话就会到全局里面去找,大概是这么一个搜索过程。
另一种带路径的方式,就会沿着路径去找,若是没有找到则会尝试将当前目录做一个包来加载。此外,使用绝对路径的速度查找最快,固然了,node 也对路径查找作了缓存机制。web

node 模块包装

node 在解析每一个模块(js 文件)时,会对每一个模块进行包装,就是在代码外面加一个闭包,而且向里传递五个参数,这样就保证了每一个模块之间的独立,就像下面这样:vue-cli

(function(exports, require, module, __filename, __dirname) {
    // module: 表示当前模块
    // __filename: 当前模块的带有完整绝对路径的文件名
    // __dirname: 当前模块的完整绝对路径
    module.exports = exports = this = {};
    // 咱们的代码就在这里...
    return module.exports;
})()
复制代码

想一想咱们平时是否是常在 webpack 里面看到 __dirname 这种东西,咱们既没有引入也没有声明它,为何可以直接使用呢,就是由于这个缘由😮。数据库

node 的应用场景

通常来讲,node 主要应用于如下几个方面:后端

  • 自动化构建等工具
  • 中间层
  • 小项目

第一点对于前端同窗来讲应该是重中之重了,什么工程化、自动构建工具就是用 node 写出来的,它是前端的一大分水岭之一,是块难啃的骨头,因此咱们必须拿下,否则瓶颈很快就到了。若是你能熟练应用 node 的各类模块(系统模块 + 第三方模块),那么恭喜你,你又比别人牛逼了一截😎。数组

node 的优势

  • 适合前端大大们
  • 基于事件驱动和无阻塞的I/O(适合处理并发请求)
  • 性能较好(别人作过性能分析)

node 内置模块

ok,废话了这么多,我们赶忙来看看一些常见的 node 基础模块吧。相信掌握这些对你学习 webpack 和 vue-cli 等工具是有很大帮助的✊ 。

http 模块

这是 node 最最基础的功能了,咱们用 node http.js 运行一下下面的文件就能开启一个服务器,在浏览器中输入 http://localhost:8888 便可访问,http.js 具体内容以下:

// http.js
const http = require('http');
http.createServer((req, res) => { // 开启一个服务
  console.log('请求来了'); // 若是你打开 http://localhost:8888,控制台就会打印此消息
  res.write('hello'); // 返回给页面的值,也就是页面会显示 hello
  res.end(); // 必须有结束的标识,不然页面会一直处于加载状态
}).listen(8888); // 端口号
复制代码

fs 文件系统

因为 js 一开始是用来开发给浏览器用的,因此它的能力就局限于浏览器,不能直接对客户端的本地文件进行操做,这样作的目的是为了保证客户端的信息安全,固然了,经过一些手段也能够操做客户端内容(就像 <input type='file'>),可是须要用户手动操做才行。
可是当 js 做为后台语言时,就能够直接对服务器上的资源文件进行 I/O 操做了。这也是 node 中尤其重要的模块之一(操做文件的能力),这在自动化构建和工程化中是很经常使用的。它的主要职责就是读写文件,或者移动复制删除等。fs 就比如对数据库进行增删改查同样,不一样的是它操做的是文件。下面咱们来具体看看代码用例:

const fs = require('fs');

// 写入文件:fs.writeFile(path, fileData, cb);
fs.writeFile('./text.txt', 'hello xr!', err => {
  if (err) {
    console.log('写入失败', err);
  } else {
    console.log('写入成功');
  }
});

// 读取文件:fs.readFile(path, cb);
fs.readFile('./text.txt', (err, fileData) => {
  if (err) {
    console.log('读取失败', err);
  } else {
    console.log('读取成功', fileData.toString()); // fileData 是二进制文件,非媒体文件能够用 toString 转换一下
  }
});
复制代码

须要注意的是 readFile 里面的 fileData 是原始的二进制文件🤨(em...就是计算机才看的懂的文件格式),对于非媒体类型(如纯文本)的文件能够用 toString() 转换一下,媒体类型的文件之后则会以流的方式进行读取,要是强行用 toString() 转换的话会丢失掉原始信息,因此不能乱转。二进制和 toString 的效果就像下面这样:

另外,和 fs.readFile(异步) 和 fs.writeFile(异步)相对应的还有 fs.readFileSync(同步)和 fs.writeFileSync(同步),fs 的大多方法也都有同步异步两个版本,具体取决于业务选择,通常都用异步,不知道用啥的话也用异步。

path 路径

这个模块想必你们应该都并不陌生,🧐瞟过 webpack 的都应该看过这个东东。很显然,path 就是来处理路径相关东西的,咱们直接看下面的常见用例就可以体会到:

const path = require('path');

let str = '/root/a/b/index.html';
console.log(path.dirname(str)); // 路径
// /root/a/b
console.log(path.extname(str)); // 后缀名
// .html
console.log(path.basename(str)); // 文件名
// index.html

// path.resolve() 路径解析,简单来讲就是拼凑路径,最终返回一个绝对路径
let pathOne = path.resolve('rooot/a/b', '../c', 'd', '..', 'e');

// 通常用来打印绝对路径,就像下面这样,其中 __dirname 指的就是当前目录
let pathTwo = path.resolve(__dirname, 'build'); // 这个用法很常见,你应该在 webpack 中有见过

console.log(pathOne, pathTwo, __dirname);
// pathOne => /Users/lgq/Desktop/node/rooot/a/c/e
// pathTwo => /Users/lgq/Desktop/node/build
// __dirname => /Users/lgq/Desktop/node
复制代码

嗯,下次看到 path 这个东西就不会迷茫了。

url 模块

很显然这是个用来处理网址相关东西的,也是咱们必需要掌握的,主要用来获取地址路径和参数的,就像下面这样:

const url = require('url');

let site = 'http://www.xr.com/a/b/index.html?a=1&b=2';
let { pathname, query } = url.parse(site, true); // url.parse() 解析网址,true 的意思是把参数解析成对象

console.log(pathname, query);
// /a/b/index.html { a: '1', b: '2' }
复制代码

querystring 查询字符串

这个主要是用来把形如这样的字符串 a=1&b=2&c=3(&和=能够换成别的)解析成 { a: '1', b: '2', c: '3' } 对象,反过来也能够把对象拼接成字符串,上面的 url 参数也能够用 querystring 来解析,具体演示以下:

const querystring = require('querystring');

let query = 'a=1&b=2&c=3'; // 形如这样的字符串就能被解析
let obj = querystring.parse(query);
console.log(obj, obj.a); // { a: '1', b: '2', c: '3' } '1'

query = 'a=1&b=2&c=3&a=3'; // 若是参数重复,其所对应的值会变成数组
obj = querystring.parse(query);
console.log(obj); // { a: [ '1', '3' ], b: '2', c: '3' }

// 相反的咱们能够用 querystring.stringify() 把对象拼接成字符串
query = querystring.stringify(obj);
console.log(query); // a=1&a=3&b=2&c=3
复制代码

assert 断言

这个咱们直接看下面代码就知道它的做用了:

// assert.js
const assert = require('assert');

// assert(条件,错误消息),条件这部分会返回一个布尔值
assert(2 < 1, '断言失败');
复制代码

node assert.js 运行一下代码就能看到以下结果:

上图是断言失败的例子,若是断言正确的话,则不会有任何提示,程序会继续默默往下执行。因此断言的做用就是先判断条件是否正确(有点像 if),若是条件返回值为 false 则阻止程序运行,并抛出一个错误,若是返回值为 true 则继续执行,通常用于函数中间和参数判断。
另外,这里再介绍两种 equal 用法(assert 里面有好多种 equal,这里举例其中的两种):

// assert.js
const assert = require('assert');

const obj1 = { a: { b: 1 } };
const obj2 = { a: { b: 1 } };
const obj3 = { a: { b: '1' } };

// assert.deepEqual(变量,预期值,错误信息) 变量 == 预期值
// assert.deepStrictEqual(变量,预期值,错误信息) 变量 === 预期值
// 一样也是错误的时候抛出信息,正确的时候继续默默执行
assert.deepEqual(obj1, obj2, '不等哦'); // true
assert.deepEqual(obj1, obj3, '不等哦'); // true
assert.deepStrictEqual(obj1, obj2, '不等哦'); // true
assert.deepStrictEqual(obj1, obj3, '不等哦'); // false,这个会抛出错误信息
复制代码

stream 流

stream 又叫作流,你们或多或少应该有听过这个概念,那具体是什么意思呢?在这里,你能够把它当作是前面说过的 fs.readFilefs.writeFile 的升级版。
咱们要知道 readFilewriteFile 的工做流程 是先把整个文件读取到内存中,而后再一次写入,这种方式对于稍大的文件就不适用了,由于这样容易致使内存不足,因此更好的方式是什么呢?就是边读边写啦,业界常说成管道流,就像水流通过水管同样,进水多少,出水就多少,这个水管就是占用的资源(内存),就那么大,这咱们样就能合理利用内存分配啦,而不是一口气吃成个胖子,有吃撑的风险(就是内存爆了🤐)。

const fs = require('fs');

// 读取流:fs.createReadStream();
// 写入流:fs.createWriteStream();
let rs = fs.createReadStream('a.txt'); // 要读取的文件
let ws = fs.createWriteStream('a2.txt'); // 输出的文件

rs.pipe(ws); // 用 pipe 将 rs 和 ws 衔接起来,将读取流的数据传到输出流(就是这么简单的一句话就能搞定)

rs.on('error', err => {
  console.log(err);
});
ws.on('finish', () => {
  console.log('成功');
})
复制代码

流式操做,就是一直读取,它是个连续的过程,若是一边快一边慢,或者一边出错没衔接上也不要紧,它会自动处理,不用咱们本身去调整其中的偏差,是个优秀的模块没错了👍。另外,咱们没有直接使用 stream 模块,是由于 fs 模块引用了它并对其作了封装,因此用 fs 便可。

zlib 压缩

这个用法简单,做用也明了,直接看下面的代码就能理解:

const fs = require('fs');
const zlib = require('zlib');

let rs = fs.createReadStream('tree.jpg');
let gz = zlib.createGzip();
let ws = fs.createWriteStream('tree.jpg.gz');

rs.pipe(gz).pipe(ws);  // 原始文件 => 压缩 => 写入

rs.on('error', err => {
  console.log(err);
});
ws.on('finish', () => {
  console.log('成功');
})
复制代码

小结

ok👌,以上就是本章要讲的一些 node 知识(比较基础,你们凑合看看)。固然除此以外,还有 util、Buffer、Event、crypto 和 process 等其余内置模块,这里就不一一赘述了,但愿你们可以多动手多敲两下代码多实践,毕竟纸上得来终觉浅嘛💪。若是你能用好 node 的各类模块,那么转后端也就拥有了无限可能性😋(其实前端的坑大的超乎你想像😭)。
最后的最后,安利一下本身的文章,勿喷,哈哈!
一、基于 vue-cli3 打造属于本身的 UI 库
二、仿 vue-cli 搭建属于本身的脚手架
三、this.$toast() 了解一下?

相关文章
相关标签/搜索