# Node 学习笔记 **Repository Path**: mr-nianj/node--learning-notes ## Basic Information - **Project Name**: Node 学习笔记 - **Description**: Node学习笔记备份 原视频链接:https://www.bilibili.com/video/BV1Ns411N7HU - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2 - **Created**: 2022-06-14 - **Last Updated**: 2024-06-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README [视频链接](https://www.bilibili.com/video/BV1Ns411N7HU) [相关笔记](https://gitee.com/coderhjhh/nodejs-/tree/master/%E4%B8%8A%E8%AF%BE%E7%AC%94%E8%AE%B0) # 1. 概念 1. Node是一个js运行时环境,只有ECMAJavaScript语法,没有Dom和Bom对象,但是增加了http 文件操作的属性 2. Node.js 可以做什么 - web服务器后台 - 命令行工具(npm[node]、git[c语言]、hexo[node]、...) - 游戏服务器 3. 常见的node命令行工具 - webpack - npm - gulp等 4. 资源 - 《深入浅出Node.js》(偏底层原理) - 《Node.js权威指南》 - 《Node入门》 (42p,入门) - 《javaScript标准参考教程》 - 《编写可维护的javaScript》 5. 学习目标 - B/S编程模型,**Brwser-Server** - 模块化编程,**RequireJS、SeaJS等** - Node常用Api - 异步编程,**回调函数** - Express框架 - EcmaScript 6 # 2. 安装Node环境 1. [Node.js官网](https://nodejs.org/en/)(lts--稳定版,current--开发版) 2. 安装完成,确认安装成功 ``node --version``或者``node -v`` 3. 注意在安装过程中,勾选配置环境变量path选项 4. 测试环境,node js文件执行 # 3. node基础语法 ## 3.1 node中的js与浏览器中的js区别 1. node中的js没有dom和bom对象 2. Node.js 使用 CommonJS模块系统,而在浏览器中,则还正在实现 ES模块标准。这意味着在 Node.js中使用 require(),而在浏览器中则使用 import。 3. 在 Node.js 中,可以控制运行环境。 ## 3.2 核心模块 1. Node为JavaScript提供了许多服务器级别的API,这些API大多被包装到了一个具名的核心模块中了。 如:文件操作的fs模块、http服务的http模块、path路径操作模块、os操作系统信息模块... 2. 加载模块,使用`require('模块名')` 3. node可以创建服务器 服务器的基本功能 - [x] 提供服务(数据) - [x] 接收请求 - [x] 发送请求 - [x] 处理请求 - [x] 发送响应 ### 3.2.1 加载fs模块实例: ```javascript var fs = require('fs');//加载fs模块 fs.writeFile('写入文件路径','写入内容',function(error){ if(error){ console.log("写入失败"); } console.log("写入成功"); }) fs.readFile('读取文件路径', 'utf-8',function(error,data){ /* fs.readFile()形参中的 第二个参数为可选项,告诉它把读取到的文件直接按照utf8编码格式进行编接码 也可以通过 data.toString()实现 fs.readFile()形参中的 第三个参数(回调函数)有两个参数 第一个参数 读取成功为null,读取失败为错误对象 第二个参数 读取成功为内容,读取失败为undifind(默认为二进制数据,通过.toString 转化) 补充:res.end() 支持两种数据类型,二进制数据和字符串,可以直接传入读取数据,不用转换 */ if(error){ console.log("读取失败"); } console.log(data); }) ``` ### 3.2.2 加载http核心模块 ```javascript var http = require('http'); //2. 使用http.createSeaver()方法创建一个web服务器对象,返回一个Server实例 var Server = http.createSeaver(); //3. 设置服务器request请求方法,执行回调函数 Server.on('request',function(){ console.log("收到请求! 请求路径为:" + req.url); res.end('hello nodejs!'); }) //4. 绑定端口号,启动服务器 Server.listen(3000,function(){ console.log("服务器启动成功,端口号 3000"); }); ``` ### 3.2.3 加载读取文件目录模块 ```javascript var http = require('http'); var fs = require('fs'); var Server = http.createSeaver(); var wwwDir = 'C:/Users/Mrnianj/Desktop/Nodejs/www'; Server.on('request',function(){ var url = req.url; if (url === '/') { //读取文件列表,第一个参数为被读取url,第二个参数为读取到的数据(数组形式) fs.readdir(wwwDir + '/index.html', function (err, files) { if (err) { res.setHeader('Content-Type', 'text/plain;charset=utf-8'); res.end('404 Not Found!'); } else { res.setHeader('Content-Type', 'text/html;charset=utf-8'); res.end(files); } }) } }) Server.listen(3000,function(){ console.log("running:3000"); }); ``` ## 3.3 加载模块&模块通信 1. require(url)作用:加载文件模块并执行里面的代码;获取被加载文件模块导出的接口对象(模块通信) 2. 加载模块类型 - 具名的核心模块,有自己特殊的名称标识,例如fs、path、http等 - 用户自定义模块(相对路径必须加 ./,可以省略后缀名'.js'),示例:require('./foo.js');/ require('./foo'); - 第三方模块 3. require 加载只能是执行其中的代码,文件与文件之间由于是模块作用域,不会有变量命名污染的问题 4. 在node中,没有全局作用域,只有*模块作用域*(每一个模块都是一个函数作用域),实现模块间数据通信,要借助exports() - 在每个文件模块中都提供了一个方法 exports,默认为空对象,可以将所有需要被外部访问的成员手动挂载到这个 exports接口对象中 - 也就是说:如果想要进行模块通信,需要将数据挂载到 exports对象中,采用.的形式,动态创建函数(理解:参照对象原型) ```javascript //a.js exports.foo = "hello"; function add(x, y) { return x + y; }; exports.add = function (x, y) { return x + y; }; ``` ``` //b.js调用a.js函数 var exports = require('a.js'); console.log(exports.add(1, 2)); ``` ## 3.4 ip地址和端口号 1. ip地址---定位计算机(网卡通过设备唯一的ip地址来定位) - req.socket.remoteAddress 可以手动获取浏览器网络请求地址 2. 端口号---定位具体的应用程序,所有需要联网通信的 软件都必须具有端口号(端口号范围 0~65536 之间) - req.socket.remotePort 可以手动获取浏览器网络请求端口号 - 可以开启多个服务,但端口号不能冲突 3. 补充:url —— 统一资源管理定位符(一个url最终对应一个资源) ## 3.5.request 请求事件对象处理函数 此函数需要接受两个参数: - Request 请求对象,请求对象可以用来获取请求信息 - Reponse 响应对象,响应对象可以用来给客户端发送响应消息 ## 3.6 响应内容类型 Content-Type 1. Content-Type用于定义网络文件的类型和网页的编码,一般服务端发送的数据,默认是utf-8编码 2. 浏览器在获取响应后,如果不声明编码方式,会按照当前操作系统默认编码进行解析 ```javascript var http = require('http'); http.createServer(function (req, res) { //指定服务器为数据类型 普通文本,编码 utf-8(默认) res.setHeader('Content-Type', 'text/html; charset=utf-8'); }) ``` 3. 参考资料:[HTTP Content-type 对照表](https://tool.oschina.net/commons),[菜鸟教程 HTTP content-type](https://www.runoob.com/http/http-content-type.html) 4. 图片资源不需要指定编码 ,因为编码一般指的是:字符编码 ## 3.7 exports 和 module.exports 的区别 1. 每个模块中都有一个 module对象,module对象中有一个 exports 对象 2. 我们可以把需要导出的成员都挂载到 module.exports接口对象中,也就是:`moudle.exports.xxx = xxx` 的方式 3. 但是每次都 `moudle.exports.xxx = xxx` 很麻烦,点儿的太多了,所以 Node 为了你方便,同时在每一个模块中都提供了一个成员叫:`exports` 4. `exports === module.exports` 结果为 `true`,所以对于:`moudle.exports.xxx = xxx` 的方式 完全可以:`expots.xxx = xxx` 5. 当一个模块需要导出单个成员的时候,这个时候必须使用:`module.exports = xxx` 的方式,不要使用 `exports = xxx` 不管用 6. 因为每个模块最终向外 `return` 的是 `module.exports`, 而 `exports` 只是 `module.exports` 的一个引用 7. 所以即便你为 `exports = xx` 重新赋值,也不会影响 `module.exports` 8. 但是有一种赋值方式比较特殊:`exports = module.exports` 这个用来重新建立引用关系的 9. 之所以让大家明白这个道理,是希望可以更灵活的去用它 # 4. Node中使用art-template(了解) ## 4.1 配置art-template 1. 配置: ``` npm install --save art-template ``` - 该命令在哪执行会将包下载到哪里,默认下载到node_modules目录中(不要改也不支持改),同时会生成一个package-lock.json文件 ``` C:\Users\Mrnianj\Desktop\test>npm install art-template ``` 2. 在需要的文件模块中引包,加载模块(参数为下载包的名字) ``` var template = require('art-template'); ``` 3. 查文档,使用模板引擎的API ## 4.2 使用art-template 1. 模板引擎不关心内容,只关心自己认识的模板标记语法,`{{}}` 语法称之为mustache语法,或八字胡语法 2. 具体语法参考:[art-template 介绍](https://aui.github.io/art-template/zh-cn/docs/index.html) 3. 关于each与for each - each,是jQuery支持的语法,语法:$.each(数组,function),只是支持jQuery元素 - 伪数组是对象,对象的原型链(Object.prototype)中没有forEach - forEach,是ES5支持的一个数组遍历函数,语法:arr.forEach((item,index)=>{}),示例:`[1,3,2,'12',31].forEach((item,index)=>{console.log(index+'--->'+item)})` - {{each}},模板引擎提供的遍历方法,只能在模板引擎中使用 - 补充,在同一个作用域内,ES6规范中模板字符串\`\`内可以使用`${变量名}`的方式来访问变量 - 补充,node中的readFile()方法读取到的二进制数据可以使用`.toString()`方法转为字符串在进行渲染 ## 4.3 服务端渲染与浏览器渲染 1. 服务端渲染 利于网站SEO优化,可以被爬虫爬取到 2. 浏览器渲染 多使用ajax,加载速度快,用户体验好 3. 补充:浏览器收到html响应内容后,开始从上而下开始解析,在解析中发现link、img、script、iframe、video、audio等带有src属性或者href(link)属性标签(具有外链资源)的时候,浏览器会自动对这些资源发起新的请求。在实际开发中会将这些资源统一放到public目录下,方便统一处理 4. 服务端渲染 读取index.html --> 模板渲染 --> 响应数据 ``` var 渲染结果 = template.reader(模板字符串,{解析替换对象}); res.end(渲染结果); ``` ## 4.4 Node解析请求路径中的查询字符串 1. Node中提供了专门的方法,语法:url.parse('url',[true|false]) 2. url.parse方法将参数路径解析为一个操作对象;第二个参数为 true,表示将查询字符串(query属性)转为一个对象 3. 常用于浏览器get请求数据解析 url.parse解析 ``` > url.parse('http://127.0.0.1:3000/login.html?name=lisi&pwd=1234') Url { protocol: 'http:', slashes: true, auth: null, host: '127.0.0.1:3000', port: '3000', hostname: '127.0.0.1', hash: null, search: '?name=lisi&pwd=1234', query: 'name=lisi&pwd=1234', pathname: '/login.html', path: '/login.html?name=lisi&pwd=1234', href: 'http://127.0.0.1:3000/login.html?name=lisi&pwd=1234' } ``` url.parse简单应用 ```javascript let fs = require('fs'); let http = require('http'); http.createServer(function(req,res){ // var url = req.url //url.parse方法将路径解析为一个操作对象,第二个参数为 true,表示将查询字符串转为一个对象(通过query属性来访问) var parseObj = url.parse(req.url, true); // 单独获取不包含查询字符串的路径部分(不包含?后面的内容) var pathname = parseObj.pathname; if (pathname.indexOf('/public/') === 0) {//请求路径以/public/开头则开放资源 //开放整个 public文件夹 fs.readFile('.' + pathname, function (err, data) { console.log(pathname);// "/public/lib/bootstrap/bootstrap.css" if (err) { return res.end('404 Not Found.') } res.end(data) }) } }).listen(3000,function(){ console.log('running...'); }) ``` ## 4.5 Node服务器实现页面重定向 1. 状态码设置为302 临时重定向(statusCode) - 语法:```res.statusCode = 302;``` - 补充 301 和 302 状态码区别,*301 永久重定向*,浏览器会记住;*302 临时重定向* 2. 在响应头中通过 Location告诉客户端往哪重定向(setHeader) - res.setHeader('Location','/'); 3. 客户端发现收到服务器响应状态码是302,会自动去响应头中找Location - res.end();//无响应数据 # 5. 第三方模块加载 ## 5.1 模块基础 1. 文件作用域 2. 通信规则 - 加载 require - 导出 3. CommonJS 模块规范 在Node中的JavaScript有一个很重要的概念:模块系统。 - 模块作用域 - 使用`require`方法加载模块 - 使用exprts接口对象导出模块中的成员 ## 5.2 第三方模块 1. art-template 2. 必须通过 `npm`下载使用 3. 不可能和核心模块名冲突 ## 5.3 加载 require 1. 语法:`var 自定义变量名 = require('模块');` 2. 两个作用: - 执行被加载模块中代码; - 得到被加载模块 exprts导出的接口对象 ## 5.4 导出 exports - Node中是模块作用域,默认文件中的所有成员只在当前文件模块中有效 - 对于希望可以被其它模块中访问的成员,需要将这些成员挂载到`exprots`接口对象中 导出单个成员(必须在对象中) ```javascript exports.a =123; exports.b ='hello node'; exports.c =function(x,y){ return x+y; }; ``` 导出多个成员(拿到的就是函数、字符串) ```javascript module.exports = 'hello'; //后者会覆盖前者 module.exports = function(x){ return x/10; }; ``` 也可以这样来导出多个成员 ```javascript module.exports = { add:function(){ return x+y; }, str:'hello' }; ``` ## 5.5 exports 和 module.exports 的区别 1. 每个模块中都有一个 module 对象,module 对象中有一个 exports 对象,一般把需要导出的成员都挂载到 module.exports 接口对象中 ```javascript var module = { exports:{} } return module.exports; ``` 2. 导出的时候,采取`moudle.exports.xxx = xxx` 的方式导出 3. 但是每次都 `moudle.exports.xxx = xxx` 很麻烦,点儿的太多了 4. 所以 Node 为了你方便,同时在每一个模块中都提供了一个成员叫:`exports`, ```javascript console.log(exports === module.exports); //true moudle.exports.foo = 'bar'; // 等价于 expots.foo = 'bar'; ``` 5. 当一个模块需要导出单个成员的时候,这个时候必须使用:`module.exports = xxx` 的方式, 因为每个模块最终向外 return的是 `module.exports`, 而 exports 只是 module.exports的一个引用 所以即便你为 exports = xx 重新赋值,也不会影响 module.exports 但是有一种赋值方式比较特殊:`exports = module.exports` 这个用来重新建立引用关系的 ```javascript //exports 是 module.exports的引用,保存了内存地址指向 module.exports // 导出单个对象必须采用 moudle.exports的方式 moudle.exports.foo = 'bar'; ``` ## 5.6 require 方法加载规则 - 优先从缓存加载,已经加载过的模块文件不会执行,但可以获取 exports挂载对象 - 其次加载核心模块/第三方模块(非路径形式的模块标识),以第三方模块 art-template为例 1. 先找到当前文件夹的所处目录中的 node_modules目录 2. node_modules\art-template\package.json 文件中的main属性 3. main属性中就记录了art-template的入口模块 4. 如果package.json文件不存在或者main 指定的入口模块失效,node会自动寻找该目录下的index.js(默认备选项),再找不到会去上一级目录中的 node_modules目录中查找,没有继续向上一级目录查找,直到磁盘根目录,报找不到错误。 5. 实际开发中,只有一个 node_modules目录,一般放到项目根目录中,确保所有模块可以访问得到。 - 最后是路径形式的模块(自定义模块) 1. ./ 2. ../ 3. / (磁盘根目录,等价于C:/ ,但是几乎不用) 4. 补充:.js后缀名可以省略 ## 5.7 package.json 包描述文件 1. 一个项目都要有 package.json文件。此文件可以通过 npm init的方式自动初始化出来 2. 执行 `npm install 包名 [--save]`的时候都加上--save这个选项,目的是保存依赖项信息到包描述文件 3. `npm install`命令可以将包描述文件中dependencies保存的依赖项全部下载还原到项目中 ``` Package.json 属性说明 name - 包名。 version - 包的版本号。 description - 包的描述。 homepage - 包的官网 url 。 author - 包的作者姓名。 contributors - 包的其他贡献者姓名。 dependencies - 依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下。 repository - 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上。 main - main 字段指定了程序的主入口文件,require('moduleName') 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js。 keywords - 关键字 ``` ## 5.8 npm常用命令 1. 下载多个包 —— npm install [--save] art-template jquery bootstrap (高版本看省略--sasve,会更新到包描述文件中,npm install,会将 dependencies保存的依赖项全部安装 2. 安装包描述文件 —— npm init [-y] ,-y可以跳过向导,快速生成 3. 根据package.json中的dependencies属性重新安装第三方模块 —— npm install 4. 升级npm —— npm install --global npm , --global 表示安装到全局 5. 删除包,但保存依赖项 —— npm uninstall 包名 6. 删除包的同时删除依赖项 —— npm uninstall --save 包名 7. 查看使用帮助 —— npm help 8. 查看npm命令使用帮助 —— npm 命令 help,如 npm uninstall help 9. 简写形式(安装以及删除包): - npm i 包名/npm i -S 包名 - npm un 包名/npm un -S 包名 10. npm淘宝镜像源使用方法 : - npm install --global cnpm(永久) - cnpm install jquery art-template(临时) ## 5.9 修改代码自动重启 nodemon 1. nodemon基于node.js开发的第三方命令行工具,使用需要独立安装 2. 安装:`npm install --global nodemon` 3. 使用`nodemon index.js` 代替 node index.js # 6. Express 框架 ## 6.1 示例 ```javascript var express = require('express'); // 创建app var app = express(); // 创建路由表 app.get('/', function (req, res) { res.end('hello world!'); }).get('/login',function(){ res.end('login.'); }) // 绑定端口 app.listen(3000, function () { console.log('running 3000...'); }) ``` ## 6.2 基本路由 路由:请求方法 \ 请求路径 \ 请求处理函数 ```javascript // get app.get('/', function (req, res) { res.end('hello world! '); }) // post app.post('/', function (req, res) { res.end('got a post request'); }) ``` ## 6.3 express.static 静态资源服务 ```javascript //当以/public/请求开头时,去./public/ 目录中查找相关资源 app.use('/',express.static('./public/')) //省略第一个参数时,直接访问./public/ 目录中资源即可,不需要写完整路径 app.use(express.static('./public/')) //这种情况,可以理解为/a/ 是./public/的形参,访问资源为/a/public目录中的文件路劲 app.use('/a/',express.static('./public/')) ``` ## 6.4 Express & art-template & 视图路径 ```javascript /*安装 npm install express --save npm install --save art-template express-art-template */ // 加载 var express = require('express'); var app = express(); // 配置使用art-template 模板引擎 /* 第一个参数(可改):当渲染.art 结尾的文件时,使用art-template模板引擎 express-art-template 是专门用来在Express中把art-template 整合到Express 中的 express-art-template 依赖了 art-template包文件 */ app.engine('art', require('express-art-template')); /* Express 为Response相应对象提供了一个方法 render(默认不可用) 当配置了模板引擎后,render方法才可以使用,语法:res.render('html模板名',{模板数据}) 第一个参数不能写路径,默认会去项目中的views 目录查找该模板文件 Express有一个约定,开发人员会把所有的视图文件放到views 目录中, 如果需要修改默认的视图路径,(注意;第一个参数 views 不要写错):app.set('views',url); */ // 使用 app.get('/', function (req, res) { res.render('index.html',{ title:'index' }); }) // 公开指定目录 app.use('/piblic/',express.static('./public/')); ``` ## 6.5 Express获取表单GET请求体数据 express内置了一个API,可以直接通过 req.query获取 ## 6.6 Express获取表单POST请求体数据(body-parser) 1. 安装 在Express中没有内置获取表单post请求体地API,这里需要使用一个第三方包:body-parser ```npm install --save body-parser``` 2. 配置 ```javascript var express = require('express') // 1.引包 var app = express() var bodyParser = require('body-parser') // 2.配置body-parser, //接入配置,req会自动添加一个 body属性,可以通获取表单POST地请求体数据了 app.use(bodyParser.urlencoded({ extended: false })) // parse application/json app.use(bodyParser.json()) ``` 3. 使用 ```javascript app.use(function (req, res) { res.setHeader('Content-Type', 'text/plain') res.write('you posted:\n') //可以通过 req.body来获取表单POST的请求体数据 res.end(JSON.stringify(req.body, null, 2)) }) ``` ## 6.7 Node路由设计进阶 初始化 --> 模板处理 --> 路由设计 ### 路由设计 | 请求方法 | 请求路径 | get参数 | post参数 | 备注 | | ----------- | ----------- | ----------- |---------------------- |----------- | | get | /students | | | 渲染首页 | | get |/students/new | | | 渲染添加页面 | | post |/students | | name\age\gender\hobbies | 处理添加请求 | | get |/students/edit | id | | 渲染添加请求 | | post |/students/edit | | id\age\gender\hobbies | 处理编辑请求 | | delete/get |/students/delete | id | | 处理删除请求 | ### router.js 实际开发中,一般将路由表配置到 router.js文件中,app.js一般只作为唯一入口函数和简单配置环境 ```javascript //app.js var express = require('express'); var router = require('./router.js'); var app = express(); router(app); ``` ```javascript //router.js 自己利用函数调用传递参数 moudle.exports=function(app){ // 配置路由表 app.get('/', function (req, res) { return ; }) } ``` 第二种方式(推荐使用,express官方提供的方法) ```javascript //app.js var express = require('express'); var router = require('./router.js'); var app = express(); //4.将路由器挂载到 app服务中 app.use(router); ``` ```javascript //router.js express提供了专门用来包装路由的,需要引入 express包 var express = require('express'); // 1.创建一个路由容器 var router = express.Router(); // 2.挂载路由表 router.get('/', function (req, res) { return ; }) //3. 导出 router容器 moudle.exprorts = router; ``` ## 补充: ### 1. mongoDB 1. 关系数据库与非关系数据库 所有关系型数据库需要通过sql语句查询,并且在操作之前都需要设计表机构(表 == 关系) 所有非关系型数据库数据表都支持约束、唯一的、主键、默认值、非空。 非关系型数据库非常灵活,有些非关系型数据库就是键值对形式存储数据 mongoDB较相似关系型数据库,mongoDB不需要设计表结构 数据库 = 数据库; 数据表 = 集合(数组); 表记录 = 文档对象 2. 数据库的开启与关闭 启动命令 -mongod 注意:首次启动需要在根目录手动新建/data/db作为数据存储目录 关闭 ctrl+c / exit命令 修改默认的数据存储目录 mongod --dpPath=数据存储目录路径 连接与退出数据库 mongo 连接数据库 exit 退出数据库 3. 基本命令 `show dbs` 查看所有数据库 `db` 查看当前数据库 `use` 数据库名 切换到指定数据库(没有会新建) 4. Node中使用mongoDB [官方mongoDB包](https://github.com/mongodb/node-mongodb-native ) [第三方mongoDB包](https://mongoosejs.com/ ) ```js let mongoose = require('mongoose') let Schema = mongoose.connect.Schema // 连接数据库 mongoose.connect('mongodb://localhost/itcast') // 设计集合(表)结构 // 建立约束 - 保护数据的完整性 const blogSchema = new Schema({ username: { type: String, required: true //必须有 }, password: { type: String, required: true }, email: { type: String } }); ```