Nodejs 基础介绍
给公司同事分享自己对于 Nodejs 的理解。
大纲
为什么说 Nodejs 是改变前端地位的发展呢。在 Nodejs 出现前,前端开发的工作范围被局限在浏览器环境,我们所有的代码,都只能运行在浏览器里面。
而 Nodejs 的出现打破了这个限制。
Nodejs 是什么
而 Nodejs 简单来说是让 js 代码能够直接在操作系统上运行的一个软件。
之前我们想测试一段 js 代码执行的效果,只能先新建一个html
文件,在文件内的<script>
标签内写我们的代码,再使用浏览器打开这个html
文件。
但是安装 Nodejs 后,我们可以直接新建一个js
文件,使用 Nodejs 来执行这个js
文件!
比如我对splice
方法记不太清,想要验证下:
1 | // spliceTest.js |
然后可以直接使用node
命令来运行一个js
文件:
1 | node spliceTest.js |
然后就会在当前命令行窗口看到我们打印的结果!
或者可以直接输入node
进入交互模式,在该模式下输入的代码都会作为js
代码执行。
输入
.exit
退出交互模式
如果熟悉 python 可能看到这里很眼熟,python 同样可以直接使用python xxx.py
来执行一个py
文件,也可以输入python
进入交互模式直接写python
代码。
Nodejs 和 python 我个人是认为可以算作同一类型的东西,同样是一个软件,能够解析各自的语法,实现很类似的功能。
Nodejs 和 JavaScript 有什么关系
之前一直都是说 “JavaScript”,其实并不是很严谨,严格来说 JavaScript 是 “ECMAScript 在浏览器环境的实现”,与之对应可以说,“Nodejs 是 ECMAScript 在服务器环境的实现”。
那ECMAScript
又是什么东西?
其实ECMAScript
就是语法,也可以说是标准。forEach()
方法为什么能够遍历一个数组?这就是由ECMAScript
规定的。
在浏览器环境的实现是什么意思呢?
写过前端代码的都知道,在浏览器中有两个很重要的对象,DOM 和 BOM,实际上前端大部分工作,都是在操作 DOM,用ECMAScript
语法操作 DOM。
1 | var con = document.getElementById('container'); |
这么一段代码要从两个方面来看。
第一,语法,为什么是var
而不是其他的关键字声明一个变量,这就是ECMAScript
规定的。
第二,DOM,document
是什么?这个在ECMAScript
里面并没有,所以不是语法的东西,那就是浏览器给我们的。
与之对应,在服务器环境的实现就是指用ECMAScript
语法,来操作服务器给我们的东西。而服务器给了我们什么呢?
很多很多的东西,fs、http、event 等等,具体的可以查看 node 文档。
下面代码是fs
的作用。
1 | // demo.txt |
1 | // index.js |
require
方法不存在ECMAScript
语法标准内,所以可以知道这是Nodejs
给我们的东西。
使用node index.js
可以看到输出
1 | hello nodejs |
构建工具(实战
终于讲到能够实际使用的东西了。举个栗子,在做前端页面时,header 和 footer 往往是相同的,我们有index.html
、login.html
、register.html
、detail.html
等等文件,这些文件都需要 header 和 footer,在每个html
文件内复制一份吗,当然可以。
“改一下 header 的结构”。??!!!修改好一份,再一个一个替换其他页面,或者使用编辑器的批量替换功能。
但是还有更简单的方式,就是“构建工具”。我可以开发出一个工具,输入一行代码,就自动帮我把header.html
和footer.html
的内容插入到index.html
这些文件的指定位置。
目录结构
是的,使用 js,更严谨的说是 nodejs。目录结构是这样的:
1 | tool.js |
1 | // template/header.html |
1 | // template/footer.html |
1 | // template/index.html |
可以看到,我在index.html
文件内写了{header}
和{footer}
,这是用来告诉我们的工具,把header.html
文件内容放到{header}
,把footer.html
文件内容放到{footer}
。
获取 index.html 文件内容
OK,那开始我们工具的 js 代码。
1 | // tool.js |
OK,这是第一步,可以使用node
命令看看有什么结果。
1 | node tool.js |
正常情况下,会输出index.html
的内容。知道了文件内容,就可以知道哪里要插入其他文件。
我们约定,
{xxx}
形式的文本会被解析,xxx 是文件名,只支持英文字母。我们会把xxx.html
文件的内容替换{xxx}
。
提取插入的文件名
按照约定,也可以说是规则吧,我们从index.html
文件内使用正则表达式分析出要插入什么文件。
1 | // tool.js |
/\{[a-z]+\}/g
是一个正则表达式,能够匹配到{abcd}
这种形式的字符串。这些是属于语法,也就是ECMAScript
的范畴。暂时不用去了解具体原理,知道这样写可以提取出我们想要的内容就可以。
正常情况会打印
1 | [ '{header}', '{footer}' ] |
获取指定文件名的内容
OK,既然知道了文件名,那就可以和上面一样使用fs.readFile
方法获取到文件内容。
1 | // tool.js |
/[a-z]+/g
会提取出所有的连续的英文字母。所以对{header}
可以得到header
,对{footer}
可以得到footer
。
再次打印看看,可以看到输出了header.html
和footer.html
文件内容。
文件内容替换占位符
这是最后一步,既然获取到了文件内容,就可以把文件内容替换掉我们自己约定的{xxx}
就好了。
不过在这之前需要修改一个地方,我们总共调用了两次fs.readFile
方法是吧,所以我们有了两个res
变量,这就导致了我们第二个覆盖了第一个,所以我们要把第一个保存了我们index.html
文件内容的变量res
起另外一个名字。
在第二步前面,我们这样做:
1 | // tool.js |
OK,修改好之后继续我们的替换。
1 | fs.readFile('./template/'+fileName+'.html', 'utf8', function (err, res) { |
然后打印看看,可以看到打印了两次,第一次是将{header}
替换掉了,第二次是将footer
替换掉了。
生成文件
虽然替换成功了,但是在template
文件夹内的文件并没有发生改变,因为我们并没有将我们得到的结果写入到文件中,所以我们最后的步骤就是写入文件。最终代码:
1 | // tool.js |
我们运行该文件,会打印两次写入成功
,打开tool.js
文件所在文件夹,会看到一个index.html
文件,内容正是我们想要的。
我们修改template/footer.html
文件内容,再次运行node tool.js
,再看看和tool.js
同目录下(非template
目录)的index.html
文件内容是不是也改变了?
实际使用中发现有时候生成的
index.html
文件内容不对,这是由于异步操作导致的,下次具体来讲什么是异步。
模块与模块化
在写构建工具的时候,我们所有的操作,都是基于 fs 这个对象。
1 | var fs = require('fs'); |
require
是 nodejs 自带的全局函数,用来“导入”模块。可以理解成在 nodejs 这个软件的目录下,有一个fs.js
文件,这个文件定义了一个fs
对象,这个对象上有很多操作文件的方法。我们使用require
可以把这个文件的内容添加到当前的文件,就可以使用另一个文件的变量了。
还是举个栗子。
1 | // sum.js |
我写了这样一个文件,里面定义了一个add
函数,作用是返回传过来的第一个和第二个参数的和。
1 | // index.js |
使用node index.js
运行,可以看到打印了8
。
这个栗子是想说明,基于 fs 模块,我们是可以获取到其他文件的内容,所以可以把代码根据功能来进行划分,算法归到一个文件夹,加法一个 js 文件,减法一个 js 文件,要用什么算法就导入什么算法,要对算法进行修改只要找到算法这个文件夹,找到对应的 js 文件进行修改就好了。
所以模块可以方便代码的管理与维护。
当然我们不可能像这个例子中这样获取另一个文件的内容,node 不仅提供了require
导入,也提供了module.exports
导出,所以上面的例子使用真正的“模块化机制”应该这样写:
1 | // sum.js |
1 | // index.js |
很方便的获取另外一个文件定义好的方法。
打包
之前说到,require
是 nodejs 提供的函数,那是不是意味着在浏览器里面就不能用了,“是的,没错”。
还是拿上面的例子,我不能在index.html
文件内写script
标签引入index.js
文件,浏览器会报错,因为根本不存在require()
这个函数。
所以我们需要自定义一个require()
函数,定义了require()
函数浏览器就不会报错了。
当然自己写的话还要处理module.exports
和依赖关系,所以使用别人写好的工具,比如webpack
。
使用 webpack 打包
webpack 就和我们之前自己写的构建工具一样,在命令行里面输入一行代码,就可以得到新的文件。
先全局安装webpack
:
1 | npm i -g webpack |
然后使用
1 | webpack ./index.js common.js |
这行代码表示,我要打包index.js
文件,生成一个common.js
文件。运行后在当前目录就会生成common.js
文件,在index.html
中引入这个新生成的common.js
文件,可以在浏览器控制台看到输出了8
!
感兴趣可以看看这个
common.js
是定义了一个怎么样的require()
函数。
这样就好了,我们可以把项目的代码进行组织,最后生成一个文件。
比如我们开发时使用的接口和正式上线的接口不一样,有两种做法
- 在
html
文件引入多个 js 文件。 - 将多个 js 文件打包,
html
只引入一个生成的 js 文件。
很明显引入多个 js 文件会影响网页访问速度,所以最好还是打包成一个文件,而且按照功能来组织文件也是很方便维护的,轮播图功能在一个文件、获取数据接口在一个文件、无限加载功能在一个文件。
当然如果每次修改代码都要手动执行一次webpack ./index.js common.js
肯定也不行,所以这也是已经有解决办法了,只要再增加另一个模块webapck-dev-server
即可。实现修改代码实时看到效果。
npm
从上面的例子中,我们又发现了一个新东西
1 | npm i -g webpack |
这表示从 npmjs.com 下载了一个模块,这个模块就是很多 js 文件组成的,而且这个模块给出了一个webpack
命令可以在命令行窗口中使用。
这就属于命令行工具的开发了,我们之前的构建工具也是可以做成像这样有自己的专属命令的形式。
感兴趣可以查看 Node.js 命令行程序开发教程
总结
nodejs 给了前端开发人员更多的能力,不再局限在浏览器中,我们可以写工具、写后端程序、写服务器运维脚本、爬虫甚至移动端应用等等。可以说,只要想做,用 js,或者说用 ECMAScript 都能够做到。