1. 前言
在接触到前端的一大堆框架的时候,越发觉得JavaScript 个语言的通用。在接下来的将学习和了解nodejs 。可以查看官网文档,并且这里也是以官网的文档为主要学习资料。值得注意的是,有很多开发者在nodejs 上做了二次开发,也就是为了让创建服务端程序更加简单而进行了很多封装,正如官网所列出的值得学习的部分列表。这里根据其介绍我比较感兴趣如下几个项目,并考虑在后期继续学习:
- Express:提供了最简单而强大的方式来创建
Web 服务器。它的极简主义方法、没有偏见、专注于服务器的核心功能,是其成功的关键。 - Gatsby:基于
React 、由 GraphQL 驱动的静态网站生成器,具有非常丰富的插件和启动器生态系统。 - Socket.io: 构建网络应用的实时通信引擎。
1.1 Node.js 与浏览器的区别
- 从编码角度看,构建运行于浏览器中的应用程序与构建
Node.js 应用程序完全不同。 - 从交互角度看,在浏览器中,大多数时候做的是与
DOM 或其??他 Web 平台 API (例如 Cookies )进行交互;而这些概念在Node.js 中是不存在的。 Node.js 使用 CommonJS 模块系统,而在浏览器中,则还正在实现 ES 模块标准。
JavaScript 引擎(比如: Google Chrome 提供V8 成为了现在为大量的服务器端代码(使用 JavaScript 编写)提供支持的引擎。)独立于托管它的浏览器。 此关键的特性推动了 Node.js 的兴起。
2. 简单入门
首先配置一下项目,执行:
mkdir NodeJsLearning
cd NodeJsLearning
然后初始化一下项目:
npm init
使用npx license mit 下载对应的license 协议文件,使用npx gitignore node 下载.gitignore 模版文件,然后创建一个index.js 文件,就可以开始写代码了。
比如下面这个极简服务器代码index.js :
const http = require('http')
const hostname = '127.0.0.1'
const port = 3000
const server = http.createServer((req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('Hello World\n')
})
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`)
})
直接使用命令node index.js 即可运行。
2.1 Node.js事件循环
Node.js JavaScript 代码运行在单个线程上。 每次只处理一件事。这个限制实际上非常有用,因为它大大简化了编程方式,而不必担心并发问题。只需要注意如何编写代码,并避免任何可能阻塞线程的事情,例如同步的网络调用或无限的循环。通常,在大多数浏览器中,每个浏览器选项卡都有一个事件循环,以使每个进程都隔离开,并避免使用无限的循环或繁重的处理来阻止整个浏览器的网页。
JavaScript 中几乎所有的 I/O 基元都是非阻塞的。 网络请求、文件系统操作等。 被阻塞是个异常,这就是 JavaScript 如此之多基于回调(最近越来越多基于 promise 和 async/await )的原因。
调用堆栈是一个 LIFO 队列(后进先出)。事件循环不断地检查调用堆栈,以查看是否需要运行任何函数。当执行时,它会将找到的所有函数调用添加到调用堆栈中,并按顺序执行每个函数。
比如下面的案例:
const bar = () => console.log('bar')
const baz = () => console.log('baz')
const foo = () => {
console.log('foo')
setTimeout(bar, 0)
baz()
}
foo()
执行结果:
foo
baz
bar
在foo 函数中,首先打印foo ,然后执行setTimeout ,将bar 作为参数传入,并传入 0 作为定时器指示它尽快运行。 然后调用 baz() 。也就是说这里可以看作:在扫描到setTimeout 函数后,将其压入栈中,然后继续压入baz 这个函数,故而如果其下还有各种函数,最终setTimeout 函数将最后出栈,即最后执行。但其实并没有这么简单,我们这里继续对上面案例做一个简单的修改:
const bar = () => console.log('bar');
const baz = () => console.log('baz');
const baz1 = () => console.log('baz1');
const baz2 = () => console.log('baz2');
const foo = () => {
console.log('foo');
setTimeout(bar, 0);
baz();
setTimeout(baz1, 2);
baz2();
};
foo();
结果:
foo
baz
baz2
bar
baz1
很显然这里就不是上面猜测的规则。在Node.js 中引入了消息队列,对于setTimeout() 方法,当定时器到期时(在此示例中会立即到期,因为将超时值设为 0 ),则回调函数会被放入“消息队列”中。在消息队列中,用户触发的事件(如单击或键盘事件、或获取响应)也会在此排队,然后代码才有机会对其作出反应。
事件循环会赋予调用堆栈优先级,它首先处理在调用堆栈中找到的所有东西,一旦其中没有任何东西,便开始处理消息队列中的东西。
同时,在Node.js 中还引入了作业队列,这种方式会尽快地执行异步函数的结果,而不是放在调用堆栈的末尾。Promise 使用了该队列。对于作业队列中的任务,会在当前函数之后被立即执行。
所以上面的栈和队列示意图可以表示为:
当然上图中的普通函数队列只是方便理解的说法,在官网中并没有看到。不妨再来一个案例:
const bar = () => console.log('bar');
const baz = () => console.log('baz');
const baz1 = () => console.log('baz1');
const baz2 = () => console.log('baz2');
const foo = () => {
console.log('foo');
baz();
setTimeout(bar, 0);
new Promise((resolve, reject) => resolve('promise')).then(resolve =>
console.log(resolve),
);
setTimeout(baz1, 2);
new Promise((resolve, reject) => resolve('promise2')).then(resolve =>
console.log(resolve),
);
baz2();
};
foo();
最终的结果为:
foo
baz
baz2
promise
promise2
bar
baz1
这里可以看出,其输出结果和上面的示意图中的保持一致。
2.2 JavaScript 异步编程与回调
JavaScript 默认情况下是同步的,并且是单线程的。 这意味着代码无法创建新的线程并且不能并行运行。浏览器通过提供一组可以处理这种功能的 API 来提供了一种异步的实现方式。在Node.js 中引入了非阻塞的 I/O 环境,以将该概念扩展到文件访问、网络调用等。提到Node.js 中的异步,不得不提及Promise ,比如下面的用法:
var http = require('http');
const fs = require('fs');
const readImage = fileName => {
return new Promise((resolve, reject) => {
fs.readFile(fileName, (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
};
const server = http.createServer(function (req, res) {
readImage('D:\\a.png')
.then(data => {
res.writeHead(200, {'Content-Type': 'image/png'});
res.end(data);
})
.catch(err => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(JSON.stringify(err));
});
});
server.listen(3000, 'localhost', () => {
console.log(`Server running at http://localhost:3000/`);
});
结果为如果a.png 真实存在,那么会显示这个图片,否则就会返回错误信息:
2.3 读取Json文件案例
这里对于Json 文件的读取其实有两种方式:
- 直接使用
require 进行文件导入; - 按照上一个案例的写法,使用文件读取;
2.3.1 直接导入json文件
const http = require('http')
const datas = require('./users.json')
const server = http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/json' })
res.end(JSON.stringify(datas))
})
server.listen(3001, 'localhost', () => {
console.log(`Server running at http://localhost:3001/`)
})
2.3.2 使用fs进行文件读取
const http = require('http')
const fs = require('fs')
const server = http.createServer(function (req, response) {
response.writeHead(200, { 'Content-Type': 'text/json' })
const data = fs.readFileSync('./users.json', 'utf-8')
response.end(JSON.stringify(JSON.parse(data)))
})
server.listen(3001, 'localhost', () => {
console.log(`Server running at http://localhost:3001/`)
})
2.3.3 请求处理json
这里需要安装node-fetch ,执行:
npm install node-fetch
主要用来测试。
需要在配置文件中添加: 然后测试代码为:
import http from 'http'
import fetch from 'node-fetch'
const server = http.createServer(function (req, res) {
const status = (response) => {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
}
return Promise.reject(new Error(response.statusText))
}
const json = (response) => response.json()
fetch('http://localhost:3001/')
.then(status)
.then(json)
.then((data) => {
console.log('请求成功获得 JSON 响应', data)
})
.catch((error) => {
console.log('请求失败', error)
})
})
server.listen(3000, 'localhost', () => {
console.log(`Server running at http://localhost:3000/`)
})
结果: 今天就到这里,更多参考官方文档:http://nodejs.cn/learn/understanding-javascript-promises
|