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

栏目: JavaScript · 发布时间: 5年前

内容简介:平时写惯了业务代码之后,如果想要了解下 webpack 或者 vue-cli,好像是件很难上手的事情 。拿 webpack 来说,我们可能会对配置熟悉点,但常常一段时间过后又忘了,感觉看起来不是很好懂。其实类似这种打包工具、构建工具我们最好应该先去学习一下 node 的一些基础知识,然后再回过头来看这些工具,就会有柳暗花明又一村的感觉,因为这些工具是用 node 写出来的 。想想我们是不是时常看到过这种东西:后来才知道这其实是 node 的内置模块,因为这些构建工具或打包工具是用 node 来执行的,只要我

平时写惯了业务代码之后,如果想要了解下 webpack 或者 vue-cli,好像是件很难上手的事情 。拿 webpack 来说,我们可能会对配置熟悉点,但常常一段时间过后又忘了,感觉看起来不是很好懂。其实类似这种打包工具、构建 工具 我们最好应该先去学习一下 node 的一些基础知识,然后再回过头来看这些工具,就会有柳暗花明又一村的感觉,因为这些工具是用 node 写出来的 。

想想我们是不是时常看到过这种东西: const path = require('path'); 。假设你学过前端框架但没学过 node,你看到这句话的时候就会一头雾水,好像知道它是弄路径的,但具体这是哪里来的,常用来做什么就不得而知了,我起初看的感觉就是这样 。

后来才知道这其实是 node 的内置模块,因为这些构建工具或打包工具是用 node 来执行的,只要我们有装 node,它里面的内置模块就能直接引用,不用另外安装。所以强烈建议大家要是想了解这类工具最好先学习一下 node,不然会总是懵逼的 。

言归正传,本篇就来简要讲述一下 node 的一些常用内置模块。

node 初识

node 是什么

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

node 遵循的规范

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

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() 里面的参数有两种写法,一种带路径一种不带路径。就像下面这样:

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

这种不带路径的 require('module') 引入方式,可能是内置模块,也可能是第三方模块,内置模块优先查找,没有的话就是第三方模块了,它会先从当前目录的 node_modules 里面查找,没有的话就到父目录下的 node_modules 里面去找,如此向上追溯,直到根目录下的 node_modules 目录,要是还没有的话就会到全局里面去找,大概是这么一个搜索过程。

另外一种带路径的方式,就会沿着路径去找,如果没有找到则会尝试将当前目录作一个包来加载。此外,使用绝对路径的速度查找最快,当然了,node 也对路径查找做了缓存机制。

node 模块包装

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

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

想想我们平时是不是常在 webpack 里面看到 __dirname 这种东西,我们既没有引入也没有声明它,为什么能够直接使用呢,就是因为这个原因:open_mouth:。

node 的应用场景

一般来说,node 主要应用于以下几个方面:

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

第一点对于前端同学来说应该是重中之重了,什么工程化、自动构建工具就是用 node 写出来的,它是前端的一大分水岭之一,是块难啃的骨头,所以我们必须拿下,不然瓶颈很快就到了。如果你能熟练应用 node 的各种模块(系统模块 + 第三方模块),那么恭喜你,你又比别人牛逼了一截:sunglasses:。

node 的优点

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

node 内置模块

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

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 的效果就像下面这样:

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

另外,和 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 运行一下代码就能看到如下结果:

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

上图是断言失败的例子,如果断言正确的话,则不会有任何提示,程序会继续默默往下执行。所以断言的作用就是先判断条件是否正确(有点像 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('成功');
})
复制代码

流式操作,就是一直读取,它是个连续的过程,如果一边快一边慢,或者一边出错没衔接上也没关系,它会自动处理,不用我们自己去调整其中的误差,是个优秀的模块没错了:+1:。另外,我们没有直接使用 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:ok_hand:,以上就是本章要讲的一些 node 知识(比较基础,大家凑合看看)。当然除此之外,还有 util、Buffer、Event、crypto 和 process 等其他内置模块,这里就不一一赘述了,希望大家能够多动手多敲两下代码多实践,毕竟纸上得来终觉浅嘛:muscle:。如果你能用好 node 的各种模块,那么转后端也就拥有了无限可能性:yum:(其实前端的坑大的超乎你想像:sob:)。

最后的最后,安利一下自己的文章,勿喷,哈哈!

1、基于 vue-cli3 打造属于自己的 UI 库

2、仿 vue-cli 搭建属于自己的脚手架

3、this.$toast() 了解一下?

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java多线程编程实战指南(设计模式篇)

Java多线程编程实战指南(设计模式篇)

黄文海 / 电子工业出版社 / 2015-10 / 59.00

随着CPU 多核时代的到来,多线程编程在充分利用计算资源、提高软件服务质量方面扮演了越来越重要的角色。而 解决多线程编程中频繁出现的普遍问题可以借鉴设计模式所提供的现成解决方案。然而,多线程编程相关的设计模式书籍多采用C++作为描述语言,且书中所举的例子多与应用开发人员的实际工作相去甚远。《Java多线程编程实战指南(设计模式篇)》采用Java(JDK1.6)语言和UML 为描述语言,并结合作者多......一起来看看 《Java多线程编程实战指南(设计模式篇)》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具