初识 nodejs

来源:互联网 发布:网络有初心 编辑:程序博客网 时间:2024/05/15 17:08

初识 nodejs

nodejs 是运行在非浏览器端的 JavaScript 平台,更多的用于但不限于构建服务器端的应用。相较于其它很多语言,它具有非阻塞和异步的特点。

学习 nodejs 可以让我们了解全栈的 JavaScript 生态。熟悉核心 JavaScript 和 前端编程的人可以相对更轻松的学习后端编程,因为没有语言语法的限制,这就如同老英和老美沟通无太大阻碍是一个道理。

好的,我也是学习 nodejs 的新手,这一篇就是目前我对 nodejs 认识。

nodejs 运行 JavaScript 代码

REPL

REPL 是 read-eval-process-loop 的简称,其实就是 nodejs 的命令行,大多数能够在浏览器 console 执行的核心 JavaScript 代码都能在这执行,当然,浏览器端可以操作 DOM API, REPL 可以操作 nodejs 的大多数 API 。

这就让 nodejs 获得了类似 PYTHON 等其它脚本语言的类似体验,我们能在命令行中测试代码。

node file.js

有了 nodejs ,编写的一些与宿主 API 无关的程序时,不再需要将 js 文件 src 到 html 里了,直接命令行通过 node file.js 来运行文件,非常方便。同时,命令行编程的整个生态的优势得以发挥。

nodejs 模块

讲真,我平常用到 nodejs 最多的是 REPL, 除了前面提到了命令行的优势外,nodejs 很大范围支持最新的 ECMAScript 标准, 我使用它来学习验证 JavaScript 以及 nodejs 的基础知识。除了 REPL, 最吸引我的是 nodejs 的模块。

require

在 nodejs 里面,模块就是一个 js 文件,我们可以通过 require 函数来获取模块。

// 自定义的模块 module.jsexports.add = function(a, b) {  return a + b;}// 测试文件 test.js 通过 require 将 module.js 加载到里面let add = require('./module').add;console.log(add(1, 2));// shellnode test.js  // 3

module.js 里,exports 默认被赋值 module.exports, module.exports 初始值是一个空对像,当 module.js 被 test.js require 时,add 变量获得的就是 exports 对象。require 函数的参数可以是模块名,也可以是路径,这里用到的是相对路径,.js 后缀可以省掉,’./’ 不能省掉,如果省掉的话 nodejs 会去核心模块或根据规则去全局路径和局部路径寻找这个模块。

这里值得注意的是,exports 不能重新赋值,只能改值,因为最终被导出的值是 module.exports , exports 只是 module.exports 的别名, 上例改如下:

// 自定义的模块 module.jsexports = function(a, b) {  return a + b;}// 测试文件 test.js 通过 require 将 module.js 加载到里面let add = require('./module');console.log(add(1, 2));// shellnode test.js  // err, 一些说明 add 不是函数的报错

因为最终模块被导出的是 module.exports 的值,它初始值是空对象,module.js 并未改变它,因此 test.js 的 add 获得的是一个空对像。

上面的 module.js 是自定义的模块,nodejs 提供了很多原生模块,比如文件模块:

// module.jsconst fs = require('fs');fs.readFile('./module.js', 'utf8', (err, data) => {  if (err) {    console.error(err);  } else {    console.log(data);  }});// shellnode module.js// 输出将是 module.js 的文本内容

从此例可以看出,nodejs 的很多默认操作以及推荐的操作都是异步的,虽然它也提供的很多同步的版本。

我们还可以 require 第三方模块,将在后面介绍 npm 的时候提到。

nodejs 执行模块的本质

nodejs 会将模块包裹到如下的函数中:

(function(exports, require, module, __filename, __dirname) {  // all module.js code 被包裹到了这里})

当这个模块被执行或 require 时,这个函数就被执行了,很大程度上,我们可以说模块就是一个 IIFE (imediatelly invoked function expression), 模块因此具有了闭包的特性。

这里有几点需要注意:
1. require, module 是全局函数,我们可以直接在代码中使用它们,而不必 require 某个 API
2. exports 在模块执行前被赋值 module.exports, 模块导出值是 module.exports. exports 的作用域为这个模块文件。
3. __filename,__dirname 同 exports 是模块内局部变量,模块执行时,它们分别被赋值为模块文件名的绝对路径和模块所在文件夹的绝对路径的字符串形式。这也是为什么 exports, __filename 以及 __dirname 不是全局变量,也没有定义,也没有被 require 却能在代码中直接使用的原因。
4. 由于模块可以看成闭包或 IIFE , 引用模块的程序有时不仅仅获得模块导出的值,模块的所有 I/O 都可能与引用程序发生关系,例如:

// module.jsmodule.exports = 'exports just a string';console.log('这条语句会在引用程序执行时输出');// test.jslet str = require('./module');console.log(str);// shellnode test.js// 这条语句会在引用程序执行时输出// exports just a string

又如:

// module.jsiconst EventEmitter = require('events');let myEmitter = new EventEmitter();setTimeout(function() {  myEmitter.emit('someEvent', "someData");}, 1000)exports.myEmitter = myEmitter;// test.jslet myEmitter = require('./module').myEmitter;myEmitter.on('someEvent', function(data) {  console.log(data);});// shellnode test.js// 会看到引用程序跟模块通信了

npm

对模块有了一些初步认识后,我们可以自己写模块了。在我们造轮子前,很大的可能是这个轮子已经在路上跑了很久了。nodejs 有包管理工具 npm (node packages manager), 全世界的开发者都将各种模块开源到 npm register 上面,其它的开发者可以 install 这些模块。

我们安装 nodejs 时附带安装了 npm, npm 官网有很好的文档附带 youtube 视频,通过这些我们可以很快的使用第三方的模块,并且将我们自己的模块发布到 npm 上。

npm install -g how-to-npm, 这是个安装全局模块的例子,同时这个模块是一个教程,全局模块可以当成 shell 的命令来运行。

通过模块以及 npm, 我们可以很方便的组织我们的程序。

nodejs 的特性

前面提到过, nodejs 比较突出的特性是非阻塞和异步性。

我目前的理解,非阻塞包含在了异步性里面。

异步性

异步(asynchronous), 指的是对事件的监听执行。

nodejs 的这个特性对于 JavaScript 的前端来讲一点都不新鲜了。nodejs 很好的延续了这一风格。

非阻塞

nodejs 主要作为服务器端应用,它的异步性直接成就了它的非阻塞能力,提高了多用户多请求时的响应能力。

非阻塞(non-blocking), 就是将请求-响应周期中的非本服务器程序主要承担的时间异步出去。

请求-响应周期就是客户端发送请求给服务器,服务器根据请求进行相关操作,这个操作可以是读盘读取文件或数据处理后返回给客户端,也可以是访问网络另一台数据库,也可以是直接服务器程序返回某个简单的响应, 等等。

这里我大致将这个时间分为三类,一类是服务器接受请求后服务器程序逻辑处理时间,这个逻辑处理可能还包括控制读盘、其它网络请求等; 二类是等待读盘完成时间、等待网络数据接受时间等其它本服务器的等待时间; 三类是为处理客服端请求所需获取的数据得到之后服务器程序数据处理时间以及最后将用户所需文档或数据返回这类操作的时间。

阻塞,就是这三类时间也就是整个请求-响应周期完成以前,服务器不会处理下一用户的请求。非阻塞,就是将这里的第二类时间异步出去,在这个需要等待的时间被异步出去之后,可以处理下一个用户的请求,直到有第三类时间需要处理。(这里可以联想 JavaScript 的单线程和执行环境的概念,服务器主程序就是一个主执行环境,遇到第三类时间的回调处理时不能再响应下一个用户的请求了).

第一类时间和第三类时间根据服务器程序的复杂度和处理数据的大小等因素而定,但同前提下,根据上面的解释,异步非阻塞的 nodejs 比 同步阻塞的其它语言在同时间内能够更快且更多的处理用户请求,这直接导致采用 nodejs 的服务器硬件数量及成本的下降。根据一些书籍和文章的资料表明,这都是得到实战的验证了的。

nodejs 服务器

以上记录的是一些我初学 nodejs 的理解,并不完整,更好更准确的文档在官方网站。并且,我认为目前这些认知还挺初级的, 到全面理解 nodejs 的本质还有很长的路要走,目前光是看完官方文档就很有难度。

不过,我能使用 raw nodejs 写能路由的简单服务器了,虽然写简单的服务器不需要理解本文之前的任何一点…

// 最简单的无路由服务器const http = require('http');http.createServer((req, res) => {  res.writeHead(200, {'content-type': 'text/html'});  res.write('<meta charset="utf-8">');  // 主要是支持中文加了这么一句  res.end('<p>你访问的这个网站的作者能力有限,写的服务器只能支撑这一个页面的这一行文字</p>');}).listen(3000);

小结

学习 nodejs, 路漫漫其修远兮。

ps:
模块的被执行时,类似于

(function(/* 这里省略前面提到的参数 */) {  // module.js 的代码}).call(module.exports, /* 省掉实参 */);

这是为什么在模块里, this === module.exports, 而不是 this === global