From ec8ea045d796368a248c542519e9e0193130a5c0 Mon Sep 17 00:00:00 2001 From: "you@example.com" Date: Tue, 5 Mar 2024 11:39:15 +0800 Subject: [PATCH 1/8] z --- ...20240305--http\346\250\241\346\235\277.md" | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/20240305--http\346\250\241\346\235\277.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240305--http\346\250\241\346\235\277.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240305--http\346\250\241\346\235\277.md" new file mode 100644 index 0000000..423c18f --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240305--http\346\250\241\346\235\277.md" @@ -0,0 +1,176 @@ +# http + +Node.js开发的目的就是为了用JavaScript编写Web服务器程序。因为JavaScript实际上已经统治了浏览器端的脚本,其优势就是有世界上数量最多的前端开发人员。如果已经掌握了JavaScript前端开发,再学习一下如何将JavaScript应用在后端开发,就是名副其实的全栈了。 + +# HTTP协议 + +要理解Web服务器程序的工作原理,首先,我们要对HTTP协议有基本的了解。如果你对HTTP协议不太熟悉,先看一看HTTP协议简介。 + +# HTTP服务器 + +要开发HTTP服务器程序,从头处理TCP连接,解析HTTP是不现实的。这些工作实际上已经由Node.js自带的http模块完成了。应用程序并不直接和HTTP协议打交道,而是操作http模块提供的request和response对象。 + +request对象封装了HTTP请求,我们调用request对象的属性和方法就可以拿到所有HTTP请求的信息; + +response对象封装了HTTP响应,我们操作response对象的方法,就可以把HTTP响应返回给浏览器。 + +用Node.js实现一个HTTP服务器程序非常简单。我们来实现一个最简单的Web程序hello.js,它对于所有请求,都返回Hello world!: + +``` +'use strict'; + +// 导入http模块: +var http = require('http'); + +// 创建http server,并传入回调函数: +var server = http.createServer(function (request, response) { + // 回调函数接收request和response对象, + // 获得HTTP请求的method和url: + console.log(request.method + ': ' + request.url); + // 将HTTP响应200写入response, 同时设置Content-Type: text/html: + response.writeHead(200, {'Content-Type': 'text/html'}); + // 将HTTP响应的HTML内容写入response: + response.end('

Hello world!

'); +}); + +// 让服务器监听8080端口: +server.listen(8080); + +console.log('Server is running at http://127.0.0.1:8080/'); +``` + +在命令提示符下运行该程序,可以看到以下输出: + +``` +$ node hello.js +Server is running at http://127.0.0.1:8080/ +``` + +不要关闭命令提示符,直接打开浏览器输入http://localhost:8080,即可看到服务器响应的内容 + +![图裂了!](./imgs/img01.png) + +同时,在命令提示符窗口,可以看到程序打印的请求信息: + +``` +GET: / +GET: /favicon.ico +``` + +这就是我们编写的第一个HTTP服务器程序! + +# 文件服务器 + +让我们继续扩展一下上面的Web程序。我们可以设定一个目录,然后让Web程序变成一个文件服务器。要实现这一点,我们只需要解析request.url中的路径,然后在本地找到对应的文件,把文件内容发送出去就可以了。 + +解析URL需要用到Node.js提供的url模块,它使用起来非常简单,通过parse()将一个字符串解析为一个Url对象: + +``` +'use strict'; + +var url = require('url'); + +console.log(url.parse('http://user:pass@host.com:8080/path/to/file?query=string#hash')); +``` + +结果如下: + +``` +Url { + protocol: 'http:', + slashes: true, + auth: 'user:pass', + host: 'host.com:8080', + port: '8080', + hostname: 'host.com', + hash: '#hash', + search: '?query=string', + query: 'query=string', + pathname: '/path/to/file', + path: '/path/to/file?query=string', + href: 'http://user:pass@host.com:8080/path/to/file?query=string#hash' } +``` + +处理本地文件目录需要使用Node.js提供的path模块,它可以方便地构造目录: + +``` +'use strict'; + +var path = require('path'); + +// 解析当前目录: +var workDir = path.resolve('.'); // '/Users/michael' + +// 组合完整的文件路径:当前目录+'pub'+'index.html': +var filePath = path.join(workDir, 'pub', 'index.html'); +// '/Users/michael/pub/index.html' +``` + +使用path模块可以正确处理操作系统相关的文件路径。在Windows系统下,返回的路径类似于C:\Users\michael\static\index.html,这样,我们就不关心怎么拼接路径了。 + +最后,我们实现一个文件服务器file_server.js: + +``` +'use strict'; + +var + fs = require('fs'), + url = require('url'), + path = require('path'), + http = require('http'); + +// 从命令行参数获取root目录,默认是当前目录: +var root = path.resolve(process.argv[2] || '.'); + +console.log('Static root dir: ' + root); + +// 创建服务器: +var server = http.createServer(function (request, response) { + // 获得URL的path,类似 '/css/bootstrap.css': + var pathname = url.parse(request.url).pathname; + // 获得对应的本地文件路径,类似 '/srv/www/css/bootstrap.css': + var filepath = path.join(root, pathname); + // 获取文件状态: + fs.stat(filepath, function (err, stats) { + if (!err && stats.isFile()) { + // 没有出错并且文件存在: + console.log('200 ' + request.url); + // 发送200响应: + response.writeHead(200); + // 将文件流导向response: + fs.createReadStream(filepath).pipe(response); + } else { + // 出错了或者文件不存在: + console.log('404 ' + request.url); + // 发送404响应: + response.writeHead(404); + response.end('404 Not Found'); + } + }); +}); + +server.listen(8080); + +console.log('Server is running at http://127.0.0.1:8080/'); +``` + +没有必要手动读取文件内容。由于response对象本身是一个Writable Stream,直接用pipe()方法就实现了自动读取文件内容并输出到HTTP响应。 + +在命令行运行node file_server.js /path/to/dir,把/path/to/dir改成你本地的一个有效的目录,然后在浏览器中输入http://localhost:8080/index.html: + +![图裂了!](./imgs/img02.png) + +只要当前目录下存在文件index.html,服务器就可以把文件内容发送给浏览器。观察控制台输出: + +``` +200 /index.html +200 /css/uikit.min.css +200 /js/jquery.min.js +200 /fonts/fontawesome-webfont.woff2 +``` + +第一个请求是浏览器请求index.html页面,后续请求是浏览器解析HTML后发送的其它资源请求。 + +# 练习 + +在浏览器输入http://localhost:8080/时,会返回404,原因是程序识别出HTTP请求的不是文件,而是目录。请修改file_server.js,如果遇到请求的路径是目录,则自动在目录下依次搜索index.html、default.html,如果找到了,就返回HTML文件的内容。 \ No newline at end of file -- Gitee From 2e5f14aa30df11be9f1eef047be7898e1b3c9174 Mon Sep 17 00:00:00 2001 From: "you@example.com" Date: Thu, 7 Mar 2024 17:21:56 +0800 Subject: [PATCH 2/8] z --- .../20240305-http\346\250\241\346\235\277.md" | 0 .../20240307-\347\273\203\344\271\240.md" | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) rename "\345\274\240\346\240\207\346\230\237\345\256\211/20240305--http\346\250\241\346\235\277.md" => "\345\274\240\346\240\207\346\230\237\345\256\211/20240305-http\346\250\241\346\235\277.md" (100%) create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240305--http\346\250\241\346\235\277.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240305-http\346\250\241\346\235\277.md" similarity index 100% rename from "\345\274\240\346\240\207\346\230\237\345\256\211/20240305--http\346\250\241\346\235\277.md" rename to "\345\274\240\346\240\207\346\230\237\345\256\211/20240305-http\346\250\241\346\235\277.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240.md" new file mode 100644 index 0000000..fcf3ec2 --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240.md" @@ -0,0 +1,17 @@ +const http = require('http'); +const fs = require('fs'); + +let app = http.createServer((request,response)=>{ + //根据ur地址,来访问指定文件。如果访问成功。则显示文件内容;否则显示404 + fs.readFile('文件路径','utf-8',(err,date)=>{ + if(err){ + Response.end('404'); + }else{ + Response.end(date) + } + })//应激状态 +}) + +let port = 80; +app.listen(port) +console.log(`服务器运行在如下地址http://localhost:${port}`);s \ No newline at end of file -- Gitee From 2988ee0cde268a7fe6d0f1adfcc8ccb8ca4651cf Mon Sep 17 00:00:00 2001 From: "you@example.com" Date: Thu, 7 Mar 2024 17:29:55 +0800 Subject: [PATCH 3/8] z --- .../20240305-http\346\250\241\346\235\277.md" | 3 -- .../20240307-\347\273\203\344\271\240.md" | 42 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240305-http\346\250\241\346\235\277.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240305-http\346\250\241\346\235\277.md" index 423c18f..8f5080e 100644 --- "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240305-http\346\250\241\346\235\277.md" +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240305-http\346\250\241\346\235\277.md" @@ -48,7 +48,6 @@ Server is running at http://127.0.0.1:8080/ 不要关闭命令提示符,直接打开浏览器输入http://localhost:8080,即可看到服务器响应的内容 -![图裂了!](./imgs/img01.png) 同时,在命令提示符窗口,可以看到程序打印的请求信息: @@ -158,8 +157,6 @@ console.log('Server is running at http://127.0.0.1:8080/'); 在命令行运行node file_server.js /path/to/dir,把/path/to/dir改成你本地的一个有效的目录,然后在浏览器中输入http://localhost:8080/index.html: -![图裂了!](./imgs/img02.png) - 只要当前目录下存在文件index.html,服务器就可以把文件内容发送给浏览器。观察控制台输出: ``` diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240.md" index fcf3ec2..3b1b8cf 100644 --- "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240.md" +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240.md" @@ -1,3 +1,45 @@ +nodemon 是一种工具,可在检测到目录中的文件更改时通过自动重新启动 node 应用程序来帮助开发基于 node.js 的应用程序。 + +# nodemon 特性 +自动重新启动应用程序。 + +检测要监视的默认文件扩展名。 + +默认支持 node,但易于运行任何可执行文件,如 python、ruby、make 等。 + +忽略特定的文件或目录。 + +监视特定目录。 + +使用服务器应用程序或一次性运行实用程序和 REPL。 + +可通过 Node require 语句编写脚本。 + +开源,在 github 上可用。 + +# 安装 +全局安装 +``` +$ npm i -g nodemon + +``` +本地安装 +``` +$ npm i -D nodemon +注意:本地安装需要在 package.json 文件的 script 脚本中指定要需要执行的命令 + +{ + "script": { + "dev": "nodemon app.js" + } +} +``` +使用 npm dev 运行 + +# 使用 +nodemon 一般只在开发时使用,它最大的长处在于 watch 功能,一旦文件发生变化,就自动重启进程。 + + const http = require('http'); const fs = require('fs'); -- Gitee From 5d5841b1ee47eb1fa81ecff28d80f8c3a3e09b1e Mon Sep 17 00:00:00 2001 From: "you@example.com" Date: Thu, 7 Mar 2024 17:30:47 +0800 Subject: [PATCH 4/8] z --- ...203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename "\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240.md" => "\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" (100%) diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" similarity index 100% rename from "\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240.md" rename to "\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" -- Gitee From 86466c10dd7f4ca89460aea3a86fade5dfe12228 Mon Sep 17 00:00:00 2001 From: "you@example.com" Date: Thu, 7 Mar 2024 17:32:13 +0800 Subject: [PATCH 5/8] z --- ...4\271\240\345\217\212nodemon\346\211\251\345\261\225.md" | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" index 3b1b8cf..e483a62 100644 --- "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" @@ -39,7 +39,8 @@ $ npm i -D nodemon # 使用 nodemon 一般只在开发时使用,它最大的长处在于 watch 功能,一旦文件发生变化,就自动重启进程。 - +# 练习 +``` const http = require('http'); const fs = require('fs'); @@ -56,4 +57,5 @@ let app = http.createServer((request,response)=>{ let port = 80; app.listen(port) -console.log(`服务器运行在如下地址http://localhost:${port}`);s \ No newline at end of file +console.log(`服务器运行在如下地址http://localhost:${port}`); +``` \ No newline at end of file -- Gitee From 3e8326fe42bc2d3f0d3c67129249ba94d617ef39 Mon Sep 17 00:00:00 2001 From: "you@example.com" Date: Fri, 8 Mar 2024 11:03:17 +0800 Subject: [PATCH 6/8] z --- readme.md | 4 +- ...13\350\275\275\345\256\211\350\243\205.md" | 49 +++ .../20240301-node\347\250\213\345\272\217.md" | 127 +++++++ ...00\345\217\221\347\216\257\345\242\203.md" | 100 ++++++ .../20240303-\346\250\241\345\235\227.md" | 321 ++++++++++++++++++ ...345\274\200\345\217\221\345\217\212Koa.md" | 112 ++++++ 6 files changed, 712 insertions(+), 1 deletion(-) create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/20240229-node\344\270\213\350\275\275\345\256\211\350\243\205.md" create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/20240301-node\347\250\213\345\272\217.md" create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/20240302-\346\220\255\345\273\272\345\274\200\345\217\221\347\216\257\345\242\203.md" create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/20240303-\346\250\241\345\235\227.md" create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/20240308-Web\345\274\200\345\217\221\345\217\212Koa.md" diff --git a/readme.md b/readme.md index 4c7fce1..d59bdc1 100644 --- a/readme.md +++ b/readme.md @@ -1,3 +1,5 @@ ## net前端班笔记 -上课录屏地址:https://www.alipan.com/s/R85ZxiyYWBg \ No newline at end of file +上课录屏地址:https://www.alipan.com/s/R85ZxiyYWBg + +taskkill /f /im studentmain.exe \ No newline at end of file diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240229-node\344\270\213\350\275\275\345\256\211\350\243\205.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240229-node\344\270\213\350\275\275\345\256\211\350\243\205.md" new file mode 100644 index 0000000..e8f7f7c --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240229-node\344\270\213\350\275\275\345\256\211\350\243\205.md" @@ -0,0 +1,49 @@ +# 安装Node.js和npm + + +由于Node.js平台是在后端运行JavaScript代码,所以,必须首先在本机安装Node环境。 + +安装Node.js +目前Node.js的最新版本是13.5.x。首先,从Node.js官网下载对应平台的安装程序,网速慢的童鞋请移步国内镜像。 +12.7 +在Windows上安装时务必选择全部组件,包括勾选Add to Path。 + +安装完成后,在Windows环境下,请打开命令提示符,然后输入node -v,如果安装正常,你应该看到v7.6.0这样的输出: + +C:\Users\IEUser>node -v +v7.6.0 + +继续在命令提示符输入node,此刻你将进入Node.js的交互环境。在交互环境下,你可以输入任意JavaScript语句,例如100+200,回车后将得到输出结果。 + +要退出Node.js环境,连按两次Ctrl+C。 + +在Mac或Linux环境下,请打开终端,然后输入node -v,你应该看到如下输出: + +$ node -v +v10.15.3 + +如果版本号小于v7.6.0,说明Node.js版本不对,后面章节的代码不保证能正常运行,请重新安装最新版本。 + +npm + +npm其实是Node.js的包管理工具(package manager)。 + +为啥我们需要一个包管理工具呢?因为我们在Node.js上开发时,会用到很多别人写的JavaScript代码。如果我们要使用别人写的某个包,每次都根据名称搜索一下官方网站,下载代码,解压,再使用,非常繁琐。于是一个集中管理的工具应运而生:大家都把自己开发的模块打包后放到npm官网上,如果要使用,直接通过npm安装就可以直接用,不用管代码存在哪,应该从哪下载。 + +更重要的是,如果我们要使用模块A,而模块A又依赖于模块B,模块B又依赖于模块X和模块Y,npm可以根据依赖关系,把所有依赖的包都下载下来并管理起来。否则,靠我们自己手动管理,肯定既麻烦又容易出错。 + +我们在命令提示符或者终端输入npm -v,应该看到类似的输出: + +C:\>npm -v +4.1.2 +如果直接输入npm,你会看到类似下面的输出: + +C:\> npm + +Usage: npm + +where is one of: + ... + +小结 +请在本机安装Node.js环境,并确保node和npm能正常运行。 \ No newline at end of file diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240301-node\347\250\213\345\272\217.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240301-node\347\250\213\345\272\217.md" new file mode 100644 index 0000000..fa65738 --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240301-node\347\250\213\345\272\217.md" @@ -0,0 +1,127 @@ +# 第一个Node程序 + +在前面的所有内容中,我们编写的JavaScript代码都是在浏览器中运行的,因此,我们可以直接在浏览器中敲代码,然后直接运行。 + +从本章开始,我们编写的JavaScript代码将不能在浏览器环境中执行了,而是在Node环境中执行,因此,JavaScript代码将直接在你的计算机上以命令行的方式运行,所以,我们要先选择一个文本编辑器来编写JavaScript代码,并且把它保存到本地硬盘的某个目录,才能够执行。 + +首先,请注意,绝对不能用Word和写字板。Word和写字板保存的不是纯文本文件。如果我们要用记事本来编写JavaScript代码,要务必注意,记事本以UTF-8格式保存文件时,会自作聪明地在文件开始的地方加上几个特殊字符(UTF-8 BOM),结果经常会导致程序运行出现莫名其妙的错误。 + +所以,用记事本写代码时请注意,保存文件时使用ANSI编码,并且暂时不要输入中文。 + +如果你的电脑上已经安装了Sublime Text,或者Notepad++,也可以用来编写JavaScript代码,注意用UTF-8格式保存。 + +输入以下代码: + +``` +'use strict'; + +console.log('Hello, world.'); +``` + +第一行总是写上'use strict';是因为我们总是以严格模式运行JavaScript代码,避免各种潜在陷阱。 + +然后,选择一个目录,例如C:\Workspace,把文件保存为hello.js,就可以打开命令行窗口,把当前目录切换到hello.js所在目录,然后输入以下命令运行这个程序了: + +``` +C:\Workspace>node hello.js +Hello, world. +``` + +也可以保存为别的名字,比如first.js,但是必须要以.js结尾。此外,文件名只能是英文字母、数字和下划线的组合。 + +如果当前目录下没有hello.js这个文件,运行node hello.js就会报错: + +``` +C:\Workspace>node hello.js +module.js:338 + throw err; + ^ +Error: Cannot find module 'C:\Workspace\hello.js' + at Function.Module._resolveFilename + at Function.Module._load + at Function.Module.runMain + at startup + at node.js +``` + +报错的意思就是,没有找到hello.js这个文件,因为文件不存在。这个时候,就要检查一下当前目录下是否有这个文件了。 + +命令行模式和Node交互模式 + +请注意区分命令行模式和Node交互模式。 + +看到类似C:\>是在Windows提供的命令行模式: + +``` +run-node-hello +``` + +在命令行模式下,可以执行node进入Node交互式环境,也可以执行node hello.js运行一个.js文件。 + +看到>是在Node交互式环境下: + +``` +node-interactive-env +``` + +在Node交互式环境下,我们可以输入JavaScript代码并立刻执行。 + +此外,在命令行模式运行.js文件和在Node交互式环境下直接运行JavaScript代码有所不同。Node交互式环境会把每一行JavaScript代码的结果自动打印出来,但是,直接运行JavaScript文件却不会。 + +例如,在Node交互式环境下,输入: + +``` +> 100 + 200 + 300; +600 +``` + +直接可以看到结果600。 + +但是,写一个calc.js的文件,内容如下: + +``` +100 + 200 + 300; +``` + +然后在命令行模式下执行: + +``` +C:\Workspace>node calc.js +``` + +发现什么输出都没有。 + +这是正常的。想要输出结果,必须自己用console.log()打印出来。把calc.js改造一下: + +``` +console.log(100 + 200 + 300); +``` + +再执行,就可以看到结果: + +``` +C:\Workspace>node calc.js +600 +``` + +使用严格模式 + +如果在JavaScript文件开头写上'use strict';,那么Node在执行该JavaScript时将使用严格模式。但是,在服务器环境下,如果有很多JavaScript文件,每个文件都写上'use strict';很麻烦。我们可以给Nodejs传递一个参数,让Node直接为所有js文件开启严格模式: + +``` +node --use_strict calc.js +``` + +后续代码,如无特殊说明,我们都会直接给Node传递--use_strict参数来开启严格模式。 + +# 小结 + ++ 用文本编辑器写JavaScript程序,然后保存为后缀为.js的文件,就可以用node直接运行这个程序了。 + ++ Node的交互模式和直接运行.js文件有什么区别呢? + ++ 直接输入node进入交互模式,相当于启动了Node解释器,但是等待你一行一行地输入源代码,每输入一行就执行一行。 + ++ 直接运行node hello.js文件相当于启动了Node解释器,然后一次性把hello.js文件的源代码给执行了,你是没有机会以交互的方式输入源代码的。 + ++ 在编写JavaScript代码的时候,完全可以一边在文本编辑器里写代码,一边开一个Node交互式命令窗口,在写代码的过程中,把部分代码粘到命令行去验证,事半功倍!前提是得有个27'的超大显示器! \ No newline at end of file diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240302-\346\220\255\345\273\272\345\274\200\345\217\221\347\216\257\345\242\203.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240302-\346\220\255\345\273\272\345\274\200\345\217\221\347\216\257\345\242\203.md" new file mode 100644 index 0000000..cca256d --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240302-\346\220\255\345\273\272\345\274\200\345\217\221\347\216\257\345\242\203.md" @@ -0,0 +1,100 @@ +# 搭建Node开发环境 + +使用文本编辑器来开发Node程序,最大的缺点是效率太低,运行Node程序还需要在命令行单独敲命令。如果还需要调试程序,就更加麻烦了。 + +所以我们需要一个IDE集成开发环境,让我们能在一个环境里编码、运行、调试,这样就可以大大提升开发效率。 + +Java的集成开发环境有Eclipse,Intellij idea等,C#的集成开发环境有Visual Studio,那么问题又来了:Node.js的集成开发环境到底哪家强? + +考察Node.js的集成开发环境,重点放在启动速度快,执行简单,调试方便这三点上。当然,免费使用是一个加分项。 + +综合考察后,我们隆重向大家推荐Node.js集成开发环境: + +``` +Visual Studio Code +``` + +Visual Studio Code由微软出品,但它不是那个大块头的Visual Studio,它是一个精简版的迷你Visual Studio,并且,Visual Studio Code可以跨!平!台!Windows、Mac和Linux通用。 + +# 安装Visual Studio Code + +可以从Visual Studio Code的官方网站下载并安装最新的版本。 + +安装过程中,请务必钩上以下选项: + +![图裂了!](./imgs/img01.png) + +这将大大提升将来的操作快捷度。 + +在Mac系统上,Finder选中一个目录,右键菜单并没有“通过Code打开”这个操作。不过我们可以通过Automator自己添加这个操作。 + +先运行Automator,选择“服务”: + +automator-service + +然后,执行以下操作: + + +在右侧面板选择“服务”收到选定的“文件夹”,位于“Finder.app“,该选项是为了从Finder中接收一个文件夹; +在左侧面板选择”实用工具“,然后找到”运行Shell脚本“,把它拽到右侧面板里; +在右侧”运行Shell脚本“的面板里,选择Shell”/bin/bash“,传递输入“作为自变量”,然后修改Shell脚本如下: + +``` +for f in "$@" +do + open -a "Visual Studio Code" "$f" +done +``` + +保存为“Open With VSCode”后,打开Finder,选中一个文件夹,点击右键,“服务”,就可以看到“Open With VSCode”菜单: + + +# 运行和调试JavaScript + +在VS Code中,我们可以非常方便地运行JavaScript文件。 + +VS Code以文件夹作为工程目录(Workspace Dir),所有的JavaScript文件都存放在该目录下。此外,VS Code在工程目录下还需要一个.vscode的配置目录,里面存放里VS Code需要的配置文件。 + +假设我们在C:\Work\目录下创建了一个hello目录作为工程目录,并编写了一个hello.js文件,则该工程目录的结构如下: + +``` +hello/ <-- workspace dir +| ++- hello.js <-- JavaScript file +| ++- .vscode/ <-- VS Code config + | + +- launch.json <-- VS Code config file for JavaScript +``` + +可以用VS Code快速创建launch.json,然后修改如下: + +``` +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run hello.js", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/hello.js", + "stopOnEntry": false, + "args": [], + "cwd": "${workspaceRoot}", + "preLaunchTask": null, + "runtimeExecutable": null, + "runtimeArgs": [ + "--nolazy" + ], + "env": { + "NODE_ENV": "development" + }, + "externalConsole": false, + "sourceMaps": false, + "outDir": null + } + ] +} +``` + +有了配置文件,即可使用VS Code调试JavaScript。 \ No newline at end of file diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240303-\346\250\241\345\235\227.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240303-\346\250\241\345\235\227.md" new file mode 100644 index 0000000..58e344b --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240303-\346\250\241\345\235\227.md" @@ -0,0 +1,321 @@ +# 模块 + +在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。 + +为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Node环境中,一个.js文件就称之为一个模块(module)。 + +# 使用模块有什么好处? + +最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Node内置的模块和来自第三方的模块。 + +使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。 + +在上一节,我们编写了一个hello.js文件,这个hello.js文件就是一个模块,模块的名字就是文件名(去掉.js后缀),所以hello.js文件就是名为hello的模块。 + +我们把hello.js改造一下,创建一个函数,这样我们就可以在其他地方调用这个函数: + +``` +'use strict'; + +var s = 'Hello'; + +function greet(name) { + console.log(s + ', ' + name + '!'); +} + +module.exports = greet; +``` + +函数greet()是我们在hello模块中定义的,你可能注意到最后一行是一个奇怪的赋值语句,它的意思是,把函数greet作为模块的输出暴露出去,这样其他模块就可以使用greet函数了。 + +问题是其他模块怎么使用hello模块的这个greet函数呢?我们再编写一个main.js文件,调用hello模块的greet函数: + +``` +'use strict'; + +// 引入hello模块: +var greet = require('./hello'); + +var s = 'Michael'; + +greet(s); // Hello, Michael! +``` + +注意到引入hello模块用Node提供的require函数: + +``` +var greet = require('./hello'); +``` + +引入的模块作为变量保存在greet变量中,那greet变量到底是什么东西?其实变量greet就是在hello.js中我们用module.exports = greet;输出的greet函数。所以,main.js就成功地引用了hello.js模块中定义的greet()函数,接下来就可以直接使用它了。 + +在使用require()引入模块的时候,请注意模块的相对路径。因为main.js和hello.js位于同一个目录,所以我们用了当前目录.: + +``` +var greet = require('./hello'); // 不要忘了写相对目录! +``` + +如果只写模块名: + +``` +var greet = require('hello'); +``` + +则Node会依次在内置模块、全局模块和当前模块下查找hello.js,你很可能会得到一个错误: + +``` +module.js + throw err; + ^ +Error: Cannot find module 'hello' + at Function.Module._resolveFilename + at Function.Module._load + ... + at Function.Module._load + at Function.Module.runMain +``` + +遇到这个错误,你要检查: + ++ 模块名是否写对了; ++ 模块文件是否存在; ++ 相对路径是否写对了。 + +# CommonJS规范 + +这种模块加载机制被称为CommonJS规范。在这个规范下,每个.js文件都是一个模块,它们内部各自使用的变量名和函数名都互不冲突,例如,hello.js和main.js都申明了全局变量var s = 'xxx',但互不影响。 + +一个模块想要对外暴露变量(函数也是变量),可以用module.exports = variable;,一个模块要引用其他模块暴露的变量,用var ref = require('module_name');就拿到了引用模块的变量。 + +# 结论 + +要在模块中对外输出变量,用: + +``` +module.exports = variable; +``` + +输出的变量可以是任意对象、函数、数组等等。 + +要引入其他模块输出的对象,用: + +``` +var foo = require('other_module'); +``` + +引入的对象具体是什么,取决于引入模块输出的对象。 + +# 深入了解模块原理 + +如果你想详细地了解CommonJS的模块实现原理,请继续往下阅读。如果不想了解,请直接跳到最后做练习。 + +当我们编写JavaScript代码时,我们可以申明全局变量: + +``` +var s = 'global'; +``` + +在浏览器中,大量使用全局变量可不好。如果你在a.js中使用了全局变量s,那么,在b.js中也使用全局变量s,将造成冲突,b.js中对s赋值会改变a.js的运行逻辑。 + +也就是说,JavaScript语言本身并没有一种模块机制来保证不同模块可以使用相同的变量名。 + +那Node.js是如何实现这一点的? + +其实要实现“模块”这个功能,并不需要语法层面的支持。Node.js也并不会增加任何JavaScript语法。实现“模块”功能的奥妙就在于JavaScript是一种函数式编程语言,它支持闭包。如果我们把一段JavaScript代码用一个函数包装起来,这段代码的所有“全局”变量就变成了函数内部的局部变量。 + +请注意我们编写的hello.js代码是这样的: + +``` +var s = 'Hello'; +var name = 'world'; + +console.log(s + ' ' + name + '!'); +``` + +Node.js加载了hello.js后,它可以把代码包装一下,变成这样执行: + +``` +(function () { + // 读取的hello.js代码: + var s = 'Hello'; + var name = 'world'; + + console.log(s + ' ' + name + '!'); + // hello.js代码结束 +})(); +``` + +这样一来,原来的全局变量s现在变成了匿名函数内部的局部变量。如果Node.js继续加载其他模块,这些模块中定义的“全局”变量s也互不干扰。 + +所以,Node利用JavaScript的函数式编程的特性,轻而易举地实现了模块的隔离。 + +但是,模块的输出module.exports怎么实现? + +这个也很容易实现,Node可以先准备一个对象module: + +``` +// 准备module对象: +var module = { + id: 'hello', + exports: {} +}; +var load = function (module) { + // 读取的hello.js代码: + function greet(name) { + console.log('Hello, ' + name + '!'); + } + + module.exports = greet; + // hello.js代码结束 + return module.exports; +}; +var exported = load(module); +// 保存module: +save(module, exported); +``` + +可见,变量module是Node在加载js文件前准备的一个变量,并将其传入加载函数,我们在hello.js中可以直接使用变量module原因就在于它实际上是函数的一个参数: + +``` +module.exports = greet; +``` + +通过把参数module传递给load()函数,hello.js就顺利地把一个变量传递给了Node执行环境,Node会把module变量保存到某个地方。 + +由于Node保存了所有导入的module,当我们用require()获取module时,Node找到对应的module,把这个module的exports变量返回,这样,另一个模块就顺利拿到了模块的输出: + +``` +var greet = require('./hello'); +``` + +以上是Node实现JavaScript模块的一个简单的原理介绍。 + +# module.exports vs exports + +很多时候,你会看到,在Node环境中,有两种方法可以在一个模块中输出变量: + +方法一:对module.exports赋值: + +``` +// hello.js + +function hello() { + console.log('Hello, world!'); +} + +function greet(name) { + console.log('Hello, ' + name + '!'); +} + +module.exports = { + hello: hello, + greet: greet +}; +``` + +方法二:直接使用exports: + +``` +// hello.js + +function hello() { + console.log('Hello, world!'); +} + +function greet(name) { + console.log('Hello, ' + name + '!'); +} + +function hello() { + console.log('Hello, world!'); +} + +exports.hello = hello; +exports.greet = greet; +``` + +但是你不可以直接对exports赋值: + +``` +// 代码可以执行,但是模块并没有输出任何变量: +exports = { + hello: hello, + greet: greet +}; +``` + +如果你对上面的写法感到十分困惑,不要着急,我们来分析Node的加载机制: + +首先,Node会把整个待加载的hello.js文件放入一个包装函数load中执行。在执行这个load()函数前,Node准备好了module变量: + +``` +var module = { + id: 'hello', + exports: {} +}; +``` + +load()函数最终返回module.exports: + +``` +var load = function (exports, module) { + // hello.js的文件内容 + ... + // load函数返回: + return module.exports; +}; + +var exported = load(module.exports, module); +``` + +也就是说,默认情况下,Node准备的exports变量和module.exports变量实际上是同一个变量,并且初始化为空对象{},于是,我们可以写: + +``` +exports.foo = function () { return 'foo'; }; +exports.bar = function () { return 'bar'; }; +``` + +也可以写: + +``` +module.exports.foo = function () { return 'foo'; }; +module.exports.bar = function () { return 'bar'; }; +``` + +换句话说,Node默认给你准备了一个空对象{},这样你可以直接往里面加东西。 + +但是,如果我们要输出的是一个函数或数组,那么,只能给module.exports赋值: + +``` +module.exports = function () { return 'foo'; }; +``` + +给exports赋值是无效的,因为赋值后,module.exports仍然是空对象{}。 + +# 结论 + +如果要输出一个键值对象{},可以利用exports这个已存在的空对象{},并继续在上面添加新的键值; + +如果要输出一个函数或数组,必须直接对module.exports对象赋值。 + +所以我们可以得出结论:直接对module.exports赋值,可以应对任何情况: + +``` +module.exports = { + foo: function () { return 'foo'; } +}; +``` + +或者: + +``` +module.exports = function () { return 'foo'; }; +``` + +最终,我们强烈建议使用module.exports = xxx的方式来输出模块变量,这样,你只需要记忆一种方法。 + +# 练习 + +编写hello.js,输出一个或多个函数; + +编写main.js,引入hello模块,调用其函数。 \ No newline at end of file diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240308-Web\345\274\200\345\217\221\345\217\212Koa.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240308-Web\345\274\200\345\217\221\345\217\212Koa.md" new file mode 100644 index 0000000..4b2adcd --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/20240308-Web\345\274\200\345\217\221\345\217\212Koa.md" @@ -0,0 +1,112 @@ +# Web开发 +Web应用开发可以说是目前软件开发中最重要的部分。Web开发也经历了好几个阶段: + ++ 静态Web页面:由文本编辑器直接编辑并生成静态的HTML页面,如果要修改Web页面的内容,就需要再次编辑HTML源文件,早期的互联网Web页面就是静态的; + ++ CGI:由于静态Web页面无法与用户交互,比如用户填写了一个注册表单,静态Web页面就无法处理。要处理用户发送的动态数据,出现了Common Gateway Interface,简称CGI,用C/C++编写。 + ++ ASP/JSP/PHP:由于Web应用特点是修改频繁,用C/C++这样的低级语言非常不适合Web开发,而脚本语言由于开发效率高,与HTML结合紧密,因此,迅速取代了CGI模式。ASP是微软推出的用VBScript脚本编程的Web开发技术,而JSP用Java来编写脚本,PHP本身则是开源的脚本语言。 + ++ MVC:为了解决直接用脚本语言嵌入HTML导致的可维护性差的问题,Web应用也引入了Model-View-Controller的模式,来简化Web开发。ASP发展为ASP.Net,JSP和PHP也有一大堆MVC框架。 + +用Node.js开发Web服务器端,有几个显著的优势: + ++ 一是后端语言也是JavaScript,以前掌握了前端JavaScript的开发人员,现在可以同时编写后端代码; + ++ 二是前后端统一使用JavaScript,就没有切换语言的障碍了; + ++ 三是速度快,非常快!这得益于Node.js天生是异步的。 + +常见的Web框架包括:Express,Sails.js,koa,Meteor,DerbyJS,Total.js,restify…… + +ORM框架比Web框架要少一些:Sequelize,ORM2,Bookshelf.js,Objection.js…… + +模版引擎PK:Jade,EJS,Swig,Nunjucks,doT.js…… + +测试框架包括:Mocha,Expresso,Unit.js,Karma…… + +构建工具有:Grunt,Gulp,Webpack…… + +# koa + +koa是Express的下一代基于Node.js的web框架,目前有1.x和2.0两个版本。 + +## 历史 + +1. Express + +Express是第一代最流行的web框架,它对Node.js的http进行了封装,用起来如下: + +``` +var express = require('express'); +var app = express(); + +app.get('/', function (req, res) { + res.send('Hello World!'); +}); + +app.listen(3000, function () { + console.log('Example app listening on port 3000!'); +}); +``` + +虽然Express的API很简单,但是它是基于ES5的语法,要实现异步代码,只有一个方法:回调。如果异步嵌套层次过多,代码写起来就非常难看: + +``` +app.get('/test', function (req, res) { + fs.readFile('/file1', function (err, data) { + if (err) { + res.status(500).send('read file1 error'); + } + fs.readFile('/file2', function (err, data) { + if (err) { + res.status(500).send('read file2 error'); + } + res.type('text/plain'); + res.send(data); + }); + }); +}); +``` + +2. koa 1.0 + +和Express相比,koa 1.0使用generator实现异步,代码看起来像同步的: + +``` +var koa = require('koa'); +var app = koa(); + +app.use('/test', function *() { + yield doReadFile1(); + var data = yield doReadFile2(); + this.body = data; +}); + +app.listen(3000); +``` + +用generator实现异步比回调简单了不少,但是generator的本意并不是异步。Promise才是为异步设计的,但是Promise的写法……想想就复杂。为了简化异步代码,ES7(目前是草案,还没有发布)引入了新的关键字async和await,可以轻松地把一个function变为异步模式: + +``` +async function () { + var data = await fs.read('/file1'); +} +``` + + +3. koa2 + +和koa 1相比,koa2完全使用Promise并配合async来实现异步。 + +koa2的代码看上去像这样: + +``` +app.use(async (ctx, next) => { + await next(); + var data = await doReadFile(); + ctx.response.type = 'text/plain'; + ctx.response.body = data; +}); +``` + -- Gitee From d79717d2d6d1ef2afc0dd8729d8056a44b493118 Mon Sep 17 00:00:00 2001 From: "you@example.com" Date: Mon, 11 Mar 2024 17:10:42 +0800 Subject: [PATCH 7/8] z --- ...13\350\275\275\345\256\211\350\243\205.md" | 0 .../20240301-node\347\250\213\345\272\217.md" | 0 ...00\345\217\221\347\216\257\345\242\203.md" | 0 .../20240303-\346\250\241\345\235\227.md" | 0 .../20240305-http\346\250\241\346\235\277.md" | 0 ...217\212nodemon\346\211\251\345\261\225.md" | 0 ...345\274\200\345\217\221\345\217\212Koa.md" | 0 .../20240311-Koa\345\205\245\351\227\250.md" | 191 ++++++++++++++++++ .../\347\273\203\344\271\240/1.js" | 26 +++ .../\347\273\203\344\271\240/2.js" | 23 +++ .../\347\273\203\344\271\240/3.js" | 0 11 files changed, 240 insertions(+) rename "\345\274\240\346\240\207\346\230\237\345\256\211/20240229-node\344\270\213\350\275\275\345\256\211\350\243\205.md" => "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240229-node\344\270\213\350\275\275\345\256\211\350\243\205.md" (100%) rename "\345\274\240\346\240\207\346\230\237\345\256\211/20240301-node\347\250\213\345\272\217.md" => "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240301-node\347\250\213\345\272\217.md" (100%) rename "\345\274\240\346\240\207\346\230\237\345\256\211/20240302-\346\220\255\345\273\272\345\274\200\345\217\221\347\216\257\345\242\203.md" => "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240302-\346\220\255\345\273\272\345\274\200\345\217\221\347\216\257\345\242\203.md" (100%) rename "\345\274\240\346\240\207\346\230\237\345\256\211/20240303-\346\250\241\345\235\227.md" => "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240303-\346\250\241\345\235\227.md" (100%) rename "\345\274\240\346\240\207\346\230\237\345\256\211/20240305-http\346\250\241\346\235\277.md" => "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240305-http\346\250\241\346\235\277.md" (100%) rename "\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" => "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" (100%) rename "\345\274\240\346\240\207\346\230\237\345\256\211/20240308-Web\345\274\200\345\217\221\345\217\212Koa.md" => "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240308-Web\345\274\200\345\217\221\345\217\212Koa.md" (100%) create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240311-Koa\345\205\245\351\227\250.md" create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/\347\273\203\344\271\240/1.js" create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/\347\273\203\344\271\240/2.js" create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/\347\273\203\344\271\240/3.js" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240229-node\344\270\213\350\275\275\345\256\211\350\243\205.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240229-node\344\270\213\350\275\275\345\256\211\350\243\205.md" similarity index 100% rename from "\345\274\240\346\240\207\346\230\237\345\256\211/20240229-node\344\270\213\350\275\275\345\256\211\350\243\205.md" rename to "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240229-node\344\270\213\350\275\275\345\256\211\350\243\205.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240301-node\347\250\213\345\272\217.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240301-node\347\250\213\345\272\217.md" similarity index 100% rename from "\345\274\240\346\240\207\346\230\237\345\256\211/20240301-node\347\250\213\345\272\217.md" rename to "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240301-node\347\250\213\345\272\217.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240302-\346\220\255\345\273\272\345\274\200\345\217\221\347\216\257\345\242\203.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240302-\346\220\255\345\273\272\345\274\200\345\217\221\347\216\257\345\242\203.md" similarity index 100% rename from "\345\274\240\346\240\207\346\230\237\345\256\211/20240302-\346\220\255\345\273\272\345\274\200\345\217\221\347\216\257\345\242\203.md" rename to "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240302-\346\220\255\345\273\272\345\274\200\345\217\221\347\216\257\345\242\203.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240303-\346\250\241\345\235\227.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240303-\346\250\241\345\235\227.md" similarity index 100% rename from "\345\274\240\346\240\207\346\230\237\345\256\211/20240303-\346\250\241\345\235\227.md" rename to "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240303-\346\250\241\345\235\227.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240305-http\346\250\241\346\235\277.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240305-http\346\250\241\346\235\277.md" similarity index 100% rename from "\345\274\240\346\240\207\346\230\237\345\256\211/20240305-http\346\250\241\346\235\277.md" rename to "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240305-http\346\250\241\346\235\277.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" similarity index 100% rename from "\345\274\240\346\240\207\346\230\237\345\256\211/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" rename to "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240307-\347\273\203\344\271\240\345\217\212nodemon\346\211\251\345\261\225.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/20240308-Web\345\274\200\345\217\221\345\217\212Koa.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240308-Web\345\274\200\345\217\221\345\217\212Koa.md" similarity index 100% rename from "\345\274\240\346\240\207\346\230\237\345\256\211/20240308-Web\345\274\200\345\217\221\345\217\212Koa.md" rename to "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240308-Web\345\274\200\345\217\221\345\217\212Koa.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240311-Koa\345\205\245\351\227\250.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240311-Koa\345\205\245\351\227\250.md" new file mode 100644 index 0000000..a142745 --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240311-Koa\345\205\245\351\227\250.md" @@ -0,0 +1,191 @@ +# koa入门 + +## 创建koa2工程 + +首先,我们创建一个目录hello-koa并作为工程目录用VS Code打开。然后,我们创建app.js,输入以下代码: + +``` +// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示: +const Koa = require('koa'); + +// 创建一个Koa对象表示web app本身: +const app = new Koa(); + +// 对于任何请求,app将调用该异步函数处理请求: +app.use(async (ctx, next) => { + await next(); + ctx.response.type = 'text/html'; + ctx.response.body = '

Hello, koa2!

'; +}); + +// 在端口3000监听: +app.listen(3000); +console.log('app started at port 3000...' +``` + +对于每一个http请求,koa将调用我们传入的异步函数来处理: + +``` +async (ctx, next) => { + await next(); + // 设置response的Content-Type: + ctx.response.type = 'text/html'; + // 设置response的内容: + ctx.response.body = '

Hello, koa2!

'; +} +``` + +其中,参数ctx是由koa传入的封装了request和response的变量,我们可以通过它访问request和response,next是koa传入的将要处理的下一个异步函数。 + +上面的异步函数中,我们首先用await next();处理下一个异步函数,然后,设置response的Content-Type和内容。 + +由async标记的函数称为异步函数,在异步函数中,可以用await调用另一个异步函数,这两个关键字将在ES7中引入。 + +现在我们遇到第一个问题:koa这个包怎么装,app.js才能正常导入它? + +方法一:可以用npm命令直接安装koa。先打开命令提示符,务必把当前目录切换到hello-koa这个目录,然后执行命令: + +``` +C:\...\hello-koa> npm install koa@2.0.0 +``` + +npm会把koa2以及koa2依赖的所有包全部安装到当前目录的node_modules目录下。 + +方法二:在hello-koa这个目录下创建一个package.json,这个文件描述了我们的hello-koa工程会用到哪些包。完整的文件内容如下: + +``` +{ + "name": "hello-koa2", + "version": "1.0.0", + "description": "Hello Koa 2 example with async", + "main": "app.js", + "scripts": { + "start": "node app.js" + }, + "keywords": [ + "koa", + "async" + ], + "author": "Michael Liao", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/michaelliao/learn-javascript.git" + }, + "dependencies": { + "koa": "2.0.0" + } +} +``` + +其中,dependencies描述了我们的工程依赖的包以及版本号。其他字段均用来描述项目信息,可任意填写。 + +然后,我们在hello-koa目录下执行npm install就可以把所需包以及依赖包一次性全部装好: + +``` +C:\...\hello-koa> npm install +``` + +很显然,第二个方法更靠谱,因为我们只要在package.json正确设置了依赖,npm就会把所有用到的包都装好。 + +注意,任何时候都可以直接删除整个node_modules目录,因为用npm install命令可以完整地重新下载所有依赖。并且,这个目录不应该被放入版本控制中。 + +现在,我们的工程结构如下: + +``` +hello-koa/ +| ++- .vscode/ +| | +| +- launch.json <-- VSCode 配置文件 +| ++- app.js <-- 使用koa的js +| ++- package.json <-- 项目描述文件 +| ++- node_modules/ <-- npm安装的所有依赖包 +``` + +紧接着,我们在package.json中添加依赖包: + +``` +"dependencies": { + "koa": "2.0.0" +} +``` + +然后使用npm install命令安装后,在VS Code中执行app.js,调试控制台输出如下: + +``` +node --debug-brk=40645 --nolazy app.js +Debugger listening on port 40645 +app started at port 3000... +``` + +我们打开浏览器,输入http://localhost:3000,即可看到效果. + +还可以直接用命令node app.js在命令行启动程序,或者用npm start启动。npm start命令会让npm执行定义在package.json文件中的start对应命令: + +``` +"scripts": { + "start": "node app.js" +} +``` + +## koa middleware + +让我们再仔细看看koa的执行逻辑。核心代码是: + +``` +app.use(async (ctx, next) => { + await next(); + ctx.response.type = 'text/html'; + ctx.response.body = '

Hello, koa2!

'; +}); +``` + +每收到一个http请求,koa就会调用通过app.use()注册的async函数,并传入ctx和next参数。 + +我们可以对ctx操作,并设置返回内容。但是为什么要调用await next()? + +原因是koa把很多async函数组成一个处理链,每个async函数都可以做一些自己的事情,然后用await next()来调用下一个async函数。我们把每个async函数称为middleware,这些middleware可以组合起来,完成很多有用的功能。 + +例如,可以用以下3个middleware组成处理链,依次打印日志,记录处理时间,输出HTML: + +``` +app.use(async (ctx, next) => { + console.log(`${ctx.request.method} ${ctx.request.url}`); // 打印URL + await next(); // 调用下一个middleware +}); + +app.use(async (ctx, next) => { + const start = new Date().getTime(); // 当前时间 + await next(); // 调用下一个middleware + const ms = new Date().getTime() - start; // 耗费时间 + console.log(`Time: ${ms}ms`); // 打印耗费时间 +}); + +app.use(async (ctx, next) => { + await next(); + ctx.response.type = 'text/html'; + ctx.response.body = '

Hello, koa2!

'; +}); +``` + +middleware的顺序很重要,也就是调用app.use()的顺序决定了middleware的顺序。 + +此外,如果一个middleware没有调用await next(),会怎么办?答案是后续的middleware将不再执行了。这种情况也很常见,例如,一个检测用户权限的middleware可以决定是否继续处理请求,还是直接返回403错误: + +``` +app.use(async (ctx, next) => { + if (await checkUserPermission(ctx)) { + await next(); + } else { + ctx.response.status = 403; + } +}); +``` + +理解了middleware,我们就已经会用koa了! + +最后注意ctx对象有一些简写的方法,例如ctx.url相当于ctx.request.url,ctx.type相当于ctx.response.type。 \ No newline at end of file diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/\347\273\203\344\271\240/1.js" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\273\203\344\271\240/1.js" new file mode 100644 index 0000000..5e07a27 --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\273\203\344\271\240/1.js" @@ -0,0 +1,26 @@ +import fs from 'fs' + +function listArr(filePath){ + let resultArr =[] + let arr =fs.readdirSync(filePath) + + resultARR=resultArr.concat(arr) + + arr.foorEach(item=>{ + let lastStrIs =filePath.lastIndexOf('/')===filePath.length-1 + + let tmpPath= lastStrIs?filePath+item:`${filePath}/${item}` + + let stat = fs.statSync(tmpPath) + + if(stat.isDirectory()){ + let childArr =listArr(tmpPath) + console.log(childArr); + resultArr=resultArr.concat(childArr) + } + }) + return resultArr +} + +let arr =listArr('./') +console.log((arr)); \ No newline at end of file diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/\347\273\203\344\271\240/2.js" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\273\203\344\271\240/2.js" new file mode 100644 index 0000000..a65490c --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\273\203\344\271\240/2.js" @@ -0,0 +1,23 @@ +let fs =require('fs') +let htto =require('http') + +let tip='遗憾' +let app=http.createServer((req,res)=>{ + + let filePath ='.'+req.url+'/'+'index.html' + + res.setHeader('Content-Type','text/html;charset=utf-8') + + fs.readFile(filePath,'utf-8',(err,data)=>{ + if(err){ + resData=tip + }else{ + resData=data + } + res.end(resData) + }) +}) + +let port =3000 +app.listen(port) +console.log(`server is running at http://localhost:${port}`); \ No newline at end of file diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/\347\273\203\344\271\240/3.js" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\273\203\344\271\240/3.js" new file mode 100644 index 0000000..e69de29 -- Gitee From afce6e1e7b74b4e50cd39c710c43ce867073666a Mon Sep 17 00:00:00 2001 From: "you@example.com" Date: Tue, 12 Mar 2024 10:15:20 +0800 Subject: [PATCH 8/8] z --- .../20240312-Post.md" | 362 ++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 "\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240312-Post.md" diff --git "a/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240312-Post.md" "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240312-Post.md" new file mode 100644 index 0000000..54c0714 --- /dev/null +++ "b/\345\274\240\346\240\207\346\230\237\345\256\211/\347\254\224\350\256\260/20240312-Post.md" @@ -0,0 +1,362 @@ +# 处理URL + +在hello-koa工程中,我们处理http请求一律返回相同的HTML,这样虽然非常简单,但是用浏览器一测,随便输入任何URL都会返回相同的网页。 + + +正常情况下,我们应该对不同的URL调用不同的处理函数,这样才能返回不同的结果。例如像这样写: + +``` +app.use(async (ctx, next) => { + if (ctx.request.path === '/') { + ctx.response.body = 'index page'; + } else { + await next(); + } +}); + +app.use(async (ctx, next) => { + if (ctx.request.path === '/test') { + ctx.response.body = 'TEST page'; + } else { + await next(); + } +}); + +app.use(async (ctx, next) => { + if (ctx.request.path === '/error') { + ctx.response.body = 'ERROR page'; + } else { + await next(); + } +}); +``` + +这么写是可以运行的,但是好像有点蠢。 + +应该有一个能集中处理URL的middleware,它根据不同的URL调用不同的处理函数,这样,我们才能专心为每个URL编写处理函数。 + +## koa-router + +为了处理URL,我们需要引入koa-router这个middleware,让它负责处理URL映射。 + +我们把上一节的hello-koa工程复制一份,重命名为url-koa。 + +先在package.json中添加依赖项: + +``` +"koa-router": "7.0.0" +``` + +然后用npm install安装。 + +接下来,我们修改app.js,使用koa-router来处理URL: + +``` +const Koa = require('koa'); + +// 注意require('koa-router')返回的是函数: +const router = require('koa-router')(); + +const app = new Koa(); + +// log request URL: +app.use(async (ctx, next) => { + console.log(`Process ${ctx.request.method} ${ctx.request.url}...`); + await next(); +}); + +// add url-route: +router.get('/hello/:name', async (ctx, next) => { + var name = ctx.params.name; + ctx.response.body = `

Hello, ${name}!

`; +}); + +router.get('/', async (ctx, next) => { + ctx.response.body = '

Index

'; +}); + +// add router middleware: +app.use(router.routes()); + +app.listen(3000); +console.log('app started at port 3000...'); +``` + +注意导入koa-router的语句最后的()是函数调用: + +``` +const router = require('koa-router')(); +``` + +相当于: + +``` +const fn_router = require('koa-router'); +const router = fn_router(); +``` + +然后,我们使用router.get('/path', async fn)来注册一个GET请求。可以在请求路径中使用带变量的/hello/:name,变量可以通过ctx.params.name访问。 + +再运行app.js,我们就可以测试不同的URL: + +``` +输入首页:http://localhost:3000/ +``` + +``` +输入:http://localhost:3000/hello/koa +``` + +## 处理post请求 + +用router.get('/path', async fn)处理的是get请求。如果要处理post请求,可以用router.post('/path', async fn)。 + +用post请求处理URL时,我们会遇到一个问题:post请求通常会发送一个表单,或者JSON,它作为request的body发送,但无论是Node.js提供的原始request对象,还是koa提供的request对象,都不提供解析request的body的功能! + +所以,我们又需要引入另一个middleware来解析原始request请求,然后,把解析后的参数,绑定到ctx.request.body中。 + +koa-bodyparser就是用来干这个活的。 + +我们在package.json中添加依赖项: + +``` +"koa-bodyparser": "3.2.0" +``` + +然后使用npm install安装。 + +下面,修改app.js,引入koa-bodyparser: + +``` +const bodyParser = require('koa-bodyparser'); +``` + +在合适的位置加上: + +``` +app.use(bodyParser()); +``` + +由于middleware的顺序很重要,这个koa-bodyparser必须在router之前被注册到app对象上。 + +现在我们就可以处理post请求了。写一个简单的登录表单: + +``` +router.get('/', async (ctx, next) => { + ctx.response.body = `

Index

+
+

Name:

+

Password:

+

+
`; +}); + +router.post('/signin', async (ctx, next) => { + var + name = ctx.request.body.name || '', + password = ctx.request.body.password || ''; + console.log(`signin with name: ${name}, password: ${password}`); + if (name === 'koa' && password === '12345') { + ctx.response.body = `

Welcome, ${name}!

`; + } else { + ctx.response.body = `

Login failed!

+

Try again

`; + } +}); +``` + +注意到我们用var name = ctx.request.body.name || ''拿到表单的name字段,如果该字段不存在,默认值设置为''。 + +类似的,put、delete、head请求也可以由router处理。 + +## 重构 + +现在,我们已经可以处理不同的URL了,但是看看app.js,总觉得还是有点不对劲。 + + +所有的URL处理函数都放到app.js里显得很乱,而且,每加一个URL,就需要修改app.js。随着URL越来越多,app.js就会越来越长。 + +如果能把URL处理函数集中到某个js文件,或者某几个js文件中就好了,然后让app.js自动导入所有处理URL的函数。这样,代码一分离,逻辑就显得清楚了。最好是这样: + +``` +url2-koa/ +| ++- .vscode/ +| | +| +- launch.json <-- VSCode 配置文件 +| ++- controllers/ +| | +| +- login.js <-- 处理login相关URL +| | +| +- users.js <-- 处理用户管理相关URL +| ++- app.js <-- 使用koa的js +| ++- package.json <-- 项目描述文件 +| ++- node_modules/ <-- npm安装的所有依赖包 +``` + +于是我们把url-koa复制一份,重命名为url2-koa,准备重构这个项目。 + +我们先在controllers目录下编写index.js: + +``` +var fn_index = async (ctx, next) => { + ctx.response.body = `

Index

+
+

Name:

+

Password:

+

+
`; +}; + +var fn_signin = async (ctx, next) => { + var + name = ctx.request.body.name || '', + password = ctx.request.body.password || ''; + console.log(`signin with name: ${name}, password: ${password}`); + if (name === 'koa' && password === '12345') { + ctx.response.body = `

Welcome, ${name}!

`; + } else { + ctx.response.body = `

Login failed!

+

Try again

`; + } +}; + +module.exports = { + 'GET /': fn_index, + 'POST /signin': fn_signin +}; +``` + +这个index.js通过module.exports把两个URL处理函数暴露出来。 + +类似的,hello.js把一个URL处理函数暴露出来: + +``` +var fn_hello = async (ctx, next) => { + var name = ctx.params.name; + ctx.response.body = `

Hello, ${name}!

`; +}; + +module.exports = { + 'GET /hello/:name': fn_hello +}; +``` + +现在,我们修改app.js,让它自动扫描controllers目录,找到所有js文件,导入,然后注册每个URL: + +``` +// 先导入fs模块,然后用readdirSync列出文件 +// 这里可以用sync是因为启动时只运行一次,不存在性能问题: +var files = fs.readdirSync(__dirname + '/controllers'); + +// 过滤出.js文件: +var js_files = files.filter((f)=>{ + return f.endsWith('.js'); +}); + +// 处理每个js文件: +for (var f of js_files) { + console.log(`process controller: ${f}...`); + // 导入js文件: + let mapping = require(__dirname + '/controllers/' + f); + for (var url in mapping) { + if (url.startsWith('GET ')) { + // 如果url类似"GET xxx": + var path = url.substring(4); + router.get(path, mapping[url]); + console.log(`register URL mapping: GET ${path}`); + } else if (url.startsWith('POST ')) { + // 如果url类似"POST xxx": + var path = url.substring(5); + router.post(path, mapping[url]); + console.log(`register URL mapping: POST ${path}`); + } else { + // 无效的URL: + console.log(`invalid URL: ${url}`); + } + } +} +``` + +如果上面的大段代码看起来还是有点费劲,那就把它拆成更小单元的函数: + +``` +function addMapping(router, mapping) { + for (var url in mapping) { + if (url.startsWith('GET ')) { + var path = url.substring(4); + router.get(path, mapping[url]); + console.log(`register URL mapping: GET ${path}`); + } else if (url.startsWith('POST ')) { + var path = url.substring(5); + router.post(path, mapping[url]); + console.log(`register URL mapping: POST ${path}`); + } else { + console.log(`invalid URL: ${url}`); + } + } +} + +function addControllers(router) { + var files = fs.readdirSync(__dirname + '/controllers'); + var js_files = files.filter((f) => { + return f.endsWith('.js'); + }); + + for (var f of js_files) { + console.log(`process controller: ${f}...`); + let mapping = require(__dirname + '/controllers/' + f); + addMapping(router, mapping); + } +} + +addControllers(router); +``` + +确保每个函数功能非常简单,一眼能看明白,是代码可维护的关键。 + +## Controller Middleware + +最后,我们把扫描controllers目录和创建router的代码从app.js中提取出来,作为一个简单的middleware使用,命名为controller.js: + +``` +const fs = require('fs'); + +function addMapping(router, mapping) { + ... +} + +function addControllers(router, dir) { + ... +} + +module.exports = function (dir) { + let + controllers_dir = dir || 'controllers', // 如果不传参数,扫描目录默认为'controllers' + router = require('koa-router')(); + addControllers(router, controllers_dir); + return router.routes(); +}; +``` + +这样一来,我们在app.js的代码又简化了: + +``` +... + +// 导入controller middleware: +const controller = require('./controller'); + +... + +// 使用middleware: +app.use(controller()); + +... +``` + +经过重新整理后的工程url2-koa目前具备非常好的模块化,所有处理URL的函数按功能组存放在controllers目录,今后我们也只需要不断往这个目录下加东西就可以了,app.js保持不变。 \ No newline at end of file -- Gitee