赞
踩
很多人学习node.js,学习完还是一知半解,感觉学会了但是没有完全学会,当在项目中要用到时,还是会有一种无从下手的感觉。本文主要是通过几个实际应用的例子来给初学者讲解node.js在实际项目中的应用,用尽量简单的代码先做出些东西来提高初学者学习的信心。如果要深入学习,还是要以官网为主,也可以找一些书辅助,比如《node.js开发指南》、《了不起的Node.js》、《Node.js实战》、《深入浅出Node.js》等等,然后广泛涉猎各种项目。
本文不讲基础知识,以实战为主,对于没有学过node.js的同学可以先花半个小时或一个小时浏览一下官网入门教程(本文第三节有一点小建议),对于本文有什么错误的地方,欢迎大家指正。
本文中所有代码git地址:https://gitee.com/liaofeiyu/node-study ,复制或者戳这里
nodejs是什么?官网是这么写的: Node.js 是一个开源和跨平台的 JavaScript 运行时环境。也就是说nodejs就只是一个运行JavaScript 的环境,提供了一些内部的方法,它能够做什么,完全看使用者发挥。
我们常常会用nodejs做以下几种事请:
看了那么多的文字了,先写点代码放松一下吧。
node处理excel使用node-xlsx来解析会比较方便,使用nodemon可以不用每次更改js都重新 node xx.js
安装
npm install node-xlsx
npm install nodemon -g
例子比较简单,直接贴代码
const xlsx = require('node-xlsx') const fs=require('fs'); let file = 'demo.xlsx' // __dirname是当前目录,node全局变量之一 let path = `${__dirname}/input/${file}`; // excel处理 const xlsxToXlsx = (path) => { //表格解析 let sheetList = xlsx.parse(path); //对数据进行处理 sheetList.forEach((sheet) => { sheet.data.forEach((row, index) => { // 第一行是标题 不处理 if(index == 0){ row[3] = '新的列' return; } // 加一列 row[3] = row[2] + row[1] }) }) // xlsx将对象转成二进制流 let buffer = xlsx.build(sheetList); // 写入 fs.writeFile(path.replace(/input/, 'output').replace(/\./, '修改版.'), buffer, (err) => { if (err) { console.log(err); } }); } xlsxToXlsx();
const xlsx = require('node-xlsx') const fs=require('fs'); let file = 'demo.xlsx' let path = `${__dirname}/input/${file}`; // excel转json const xlsxToJson = (path) =>{ //表格解析 let sheetList = xlsx.parse(path); // 拿到第一个sheet数据 let sheet = sheetList[0].data let ret = []; sheet.forEach((row, index) => { // 第一行是标题 不处理 if(index == 0){ return; } // 已经知道每一列是什么了,直接按索引拿 ret.push({ city: row[0], code: row[1], name: row[2] }) }) // 转成字符串 let str = JSON.stringify(ret); console.log(str) // 写入 fs.writeFile(path.replace(/input/, 'output').replace(/\.xlsx/, '.json'), str, (err) => { if (err) { console.log(err); } }) } xlsxToJson(path);
厚颜无耻的推荐一下本人第一篇博客:几种前端模拟数据使用方案
注意:仅限于第一次浏览需要关注的点,有学过基础的就跳过吧
1、运行与退出
node xx,js 运行
ctrl+c退出
代码里面用processs.exit(0)退出
2、参数相关—记住下面的代码就行
// 读取环境变量
process.env.NODE_ENV
// 命令行输入传参
node my.js --name=joe --param2=abcdefg
// 代码中取参数,可以自己打印出来看看为什么是slice(2)
const args = require('minimist')(process.argv.slice(2))
args['name'] // joe
args['param2'] // abcdefg
3、consle
// 调试
consle.log();
// 记录错误日志----服务器开发用
console.error()
// 查看代码执行调用了哪些函数----定位错误很有用
console.trace()
// 代码执行时间----看代码性能
time()
timeEnd()
4、从命令行接收输入----vue-cli中会讲到。
5、REPL ----在命令行输入node,然后可以在命令工具中写js,一般人不这么干
6、npm安装相关,安装到哪、npm依赖的使用、npm版本----npx很有用
7、pacakge.json----需要细看的配置项
9、事件循环----看不懂也不影响你使用node,水平不够看懂了也没啥用。先学着应付面试,慢慢学,以后总会懂的。
process.nextTick()、setImmediate()同样可以先不懂
引出的小知识:在定时器和promis中,非语法类的报错并不会影响你其它代码执行,node中尽量用try catch。
10、定时器、promise、async、await----这是你js就该会的
11、事件触发----类比vue的$emit,但是它这个是全局的,算是eventBus
12、http相关----搭建服务器用,直接看实战吧
13、文件系统----api文档超长,直接实战中翻文档
14、操作系统模块----作者用得不多,基本上使用的插件都做了封装
举个最简单的小栗子:node判断是mac还是window,区分路径的斜杠跟反斜杠。
15、buffer跟流----让文件传输更快,提升文件处理性能用的
16、typescript----官网的示例都是js,不看它,学会了typescipt再来看
17、WebAssembly----几乎用不上,不看它
本节涉及到http、文件系统、热更新(热更新就是改了js代码,浏览器会自动刷新)实现、express简单实现(真的很简单)、vue代理的原理(vue用的是http-proxy)
代码的目录结构
话不多说,上代码,代码写了别人看不懂,废话再多也没用
ps:为了同一个模块集中放一块,代码用了var,按顺序一段段复制成一个文件就能跑起来
1、启动服务器(这里用http是为了对应官网学习,用express会比较舒服,用koa也可以,但是得自己加依赖)
const fs=require('fs');
const http = require('http')
const Url = require('url')
const port = 3000;
const basePath = __dirname;
// 这里返回指的是访问localhost:3000
const server = http.createServer();
// 启动监听
server.listen(port, () => {
console.log("启动成功")
})
2、监听请求
server.on('request', (req, res) => { // http接数据很麻烦,还是用express封装好的爽 let data = ''; req.on('data', chunk => { data += chunk; }) // 为了代码简单,直接接收完数据在处理 req.on('end', async () => { // 把数据挂在body上 req.body = data; // vue的代理实现 if(await getProxy(req, res)){ return; }; // 作为前端服务器,返回静态文件 if(responseFile(req, res)){ return; } // 作为后端服务器,只接收get post express.run(req, res); }) });
3、返回静态文件
// 返回文件 var resFile = (res, path, type, contentType) => { let realPath = `${basePath}/html/${path}${type}` fs.readFile(realPath, (err, data) => { if (err) { response404(res); } res.writeHead(200, {'Content-Type': contentType}) res.end(data); }); } // demo只支持返回js 跟 index.html var responseFile = (req, res)=>{ const { path } = Url.parse( req.url ); if( !path || path === '/' ){ resFile(res, 'index', '.html', 'text/html;charset=UTF-8') return true; } let jsIndex = path.indexOf('.js'); if(jsIndex > -1){ resFile(res, path.substring(0, jsIndex), '.js', 'application/javascript;charset=UTF-8'); return true; } return false; } // 简单返回404 var response404 = (res) =>{ res.writeHead(404, {'Content-Type': 'text/plain'}); res.end('404'); }
4、返回get、post请求
// 自制个express,实际上express也就比我多了亿点点细节 var express = { postUrl: {}, getUrl: {}, run(req,res){ // 作为后端服务器返回get,post const method = req.method.toLowerCase(); const { path } = Url.parse( req.url ); // 加多个json返回方法,简化版express.json() res.json = (data)=>{ res.setHeader('Content-Type', 'text/html;charset=UTF-8'); res.end(JSON.stringify(data)) } // 判断是get还是post,路径在不在列表里,在列表里就调回调函数 if(method === 'get' && this.getUrl[path]){ this.getUrl[path](req, res); return; } if(method === 'post' && this.postUrl[path]){ this.postUrl[path](req, res); return; } // 没有就返回404 response404(res); }, // get post都只是把callback存起来, post(url, callBack){ this.postUrl[url] = callBack; }, get(url, callBack){ this.getUrl[url] = callBack; } } // 定义两个接口 express.post('/setData', (req, res)=>{ res.json({code:200, msg:'success'}) }) express.get('/getData', (req, res)=>{ res.json({code:200,data:{count:1},msg:'success'}) })
5、简单的代理实现
// 假如是vue devServer传进来的 var proxyRouter = { '/api': { target: 'http://localhost:3000', pathRewrite: path => { return path.replace('/api', ''); }, } } // 实现代理 var getProxy = (serverReq, serverRes)=>{ const url = Url.parse( serverReq.url ); const path = url.path; // 斜杆是index.html if(path === '/'){ return; } // 判断是否在代理列表中 let currentRoute = ''; for(let route in proxyRouter){ if( path.indexOf(route) === 0 ){ currentRoute = route; break; } } if(!currentRoute){ return false; } // 解析proxyRouter let target = Url.parse( proxyRouter[currentRoute].target ); // pathRewrite的作用 let targetPath = proxyRouter[currentRoute].pathRewrite(url.path) // 真正的请求地址及配置 const options = { hostname: target.hostname, port: target.port, path: targetPath, method: serverReq.method, }; // 创建请求到真正的地址 var request = http.request(options, (res) => { res.setEncoding('utf8'); let data = ''; res.on('data', (chunk) => { data += chunk; }); // 请求完把接收到的数据返回到前端 res.on('end', () => { serverRes.setHeader('Content-Type', 'text/html;charset=UTF-8'); serverRes.end(data); }); }); // 请求数据发送到真正的地址 request.write(serverReq.body); request.end(); return true; }
这里有一篇代理的介绍,感兴趣可以戳一戳前端代理浅析
6、热更新,用到了socket.io(官网推荐,必是精品)
const { Server } = require("socket.io");
const io = new Server(server);
io.on('connection', (socket) => {
console.log('a user connected');
});
// 简单监听文件夹,文件夹
fs.watch('./html', { encoding: 'utf-8' }, (eventType, filename) => {
console.log(filename);
io.emit('message',123)
});
7、前端index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <style> #app { text-align: center; color: #fff; line-height: 400px; width: 400px; height: 400px; border-radius: 200px; background: greenyellow; } </style> </head> <body> <div id="app">生活必须带点绿</div> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script src=./demo.js></script> <script src=./404.js></script> <script type="importmap"> { "imports": { "socket.io-client": "https://cdn.socket.io/4.4.1/socket.io.esm.min.js" } } </script> <script type="module"> import { io } from "socket.io-client"; const socket = io('ws://localhost:3000'); socket.on('message',function(evt){ location.reload(); }) </script> <script> console.log('onload') </script> </body> </html>
8、前端demo.js
// get示例
axios.get('http://localhost:3000/getData').then(function(data){
console.log(data)
})
// post示例
axios.post('http://localhost:3000/setData').then(function(data){
console.log(data)
})
// 代理示例,代理完访问的还是/setData
axios({url:'http://localhost:3000/api/setData',method:'post',data: {value: 1}}).then(function(data){
console.log(data)
})
vue-cli的原理就是给你下载了一个工程到本地,然后添加了很多指令跟参数让你可以定制一个你需要的脚手架,直接上代码做一个简易版。运行需要先下载代码,戳这里
这里就不讲cli怎么发布了,篇幅已经很长了。讲一下本地怎么跑起来。
ps:基本上都是用的node插件,代码是作者原本项目中实现的cli,简化后重新改了注释,简单看看代码原理吧,毕竟你去看vuecli源码连注释都没几个。
#!/usr/bin/env node // r是作者名字首字母,没别的意思 process.title = 'r-cli'; // 指令模块,可以生成一个可以在cmd执行的指令 const program = require('commander'); // node总是离不开文件读写 const fs = require('fs'); // 文件读写总是离不开用path const path = require('path'); // node跟命令窗口交互的插件 const inquirer = require('inquirer'); // 插件作用:在代码中实现在cmd中执行命令的效果 const execa = require('execa'); // chalk是一个node在命令窗口按颜色输出文字的插件 const chalk = require('chalk'); // node在命令窗口显示loading的工具 const ora = require('ora'); const spinner = ora(); // 在cmd中执行 r-cli -v 查看版本 program.version(require('../package').version, '-v, --version') .usage('<command> [options]') // r-cli init 直接开始创建 program .command('init') .description('init a project in current folder') .action(function() { initProjectInCurrentFolder(); }); // 读取命令参数到program中 program.parse(process.argv); // 根据项目名name,项目路径path生成项目 async function makeProjectWithProjectConfig({ name, path }) { // 根据类型,名称和路径生成项目 await makeProject({ name, path }); // 询问是不是要执行 npm install const installAnswers = await inquirer.prompt([{ type: 'list', name: 'install', message: 'install project dependency packages ?', choices: [{ name: 'yes', value: 'yes' }, { name: 'no', value: 'no' }] }]); // cmd中输入了yes if (installAnswers.install === 'yes') { // 执行npm i await execa('npm', ['i', '--prefix', path],{ stdio: 'inherit' }); } else { // 返回执行命令的描述 showMakeProjectResult({ name, path, isInstall: installAnswers.install === 'yes' }); return ; } // 询问是不是要执行npm run dev const startAnswers = await inquirer.prompt([{ type: 'list', name: 'start', message: 'start project now?', choices: [{ name: 'yes', value: 'yes' }, { name: 'no', value: 'no' }] }]); if (startAnswers.start === 'yes') { await execa('npm', ['run', 'dev', '--prefix', path],{ stdio: 'inherit' }); return ; } else { // 返回执行命令的描述 showMakeProjectResult({ name, path, isInstall: installAnswers.install === 'yes' }) } } // 返回执行命令的描述 function showMakeProjectResult({ name, path, isInstall }) { console.log(chalk.green(` Success! Created ${name} at ${path}. We suggest that you begin by typing: cd ${path} ${isInstall ? '': 'npm install'} npm run dev Happy developing!` )); } // 获取文件夹名后开始创建项目 async function initProjectInCurrentFolder() { // 获取当前文件夹的名称作为项目名称 let pathArr = process.cwd().split(/\\|\//); let projectName = pathArr.pop(); makeProjectWithProjectConfig({ name: projectName, path: process.cwd() }); } // 根据输入的参数,生成对应的项目 async function makeProject(projectArgs) { const { path } = projectArgs; // path 为clone 代码后,项目所在的目录 await downloadTemplate(path); await resetProjectWithArgs(projectArgs); } // 更改packagejson的内容 async function resetProjectWithArgs(projectArgs) { // 更改package.json的name const packageJsonFilePath = path.resolve(projectArgs.path, 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonFilePath)); packageJson.name = projectArgs.name; fs.writeFileSync(packageJsonFilePath, JSON.stringify(packageJson, null, ' ')); } // 执行git clone下载项目 function downloadTemplate(path) { const successSymbol = chalk.green('✔'); const failSymbol = chalk.red('x'); return new Promise( resolve => { spinner.text = `start to clone vue project template from https://gitee.com/liaofeiyu`; spinner.start(); // 这里是使用git下载,所以得先安装有git execa('git', ['clone', 'https://gitee.com/liaofeiyu/node-study.git', path]).then(async () => { spinner.stopAndPersist({ symbol: successSymbol, text: `clone vue project template successfully` }); resolve(); }).catch( error => { if (error.failed) { spinner.stopAndPersist({ symbol: failSymbol, text: chalk.red(error.stderr) }); } }); }); }
通过几个实战例子,你可能已经发现了。除了文件系统(fs、path)外,我们很少会直接用node.js提供的基础功能,就像我们很少会直接用原生js来开发项目。比如http用express,websocket用socket.io,与命令行交互用inquirer,想要console好看点用了chalk,与命令窗口指令交互用commander等等。
生态是无穷无尽的,总有一些工具被创造出来,懂得越多就会越发觉得自己知识匮乏。
然而,总有一些不变的东西,那就是js,不管用了什么插件,代码逻辑的编写都是用的js基础。学好js,发挥自己的主动性与创造力,你就是下一个vue作者、react作者。
ps:作者是尽量把代码写简单了,实际项目中需要加上很多细节,比如使用ts或者做一些类型判断,用try catch来打印错误日志等。写的过程中经常越写越嗨越写越多,最后删删改改还是写了这么多,就酱吧。
pps:如果觉得本文对你有帮助,帮忙点个赞,让作者知道自己写的东西还有一点点价值。如果写的有什么不对的地方,也欢迎留言指正。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。