目录
package.json和package-lock.json
1.命令行窗口(小黑屏)、CMD窗口、终端、shell
- 开始菜单 --> 运行 --> CMD --> 回车
- 常用的指令:
dir 列出当前目录下的所有文件
cd 目录名 进入到指定的目录
md 目录名 创建一个文件夹
rd 目录名 删除一个文件夹
- 目录
. 表示当前目录
.. 表示上一级目录
环境变量(windows系统中变量)
path
C:\work\jdk\jdk1.7.0_75/bin;
%CATALINA_HOME%/bin;
C:\work\soft\tools\AppServ\Apache24\bin;
C:\work\soft\tools\AppServ\php5;
C:\Users\lilichao\AppData\Local\Programs\Fiddler;
C:\work\environment\Egret\Egret Wing 3\bin;
C:\Users\lilichao\AppData\Roaming\npm;
C:\Program Files\MongoDB\Server\3.2\bin;
C:\Users\lilichao\Desktop\hello
- 当我们在命令行窗口打开一个文件,或调用一个程序时,
系统会首先在当前目录下寻找文件程序,如果找到了则直接打开
如果没有找到则会依次到环境变量path的路径中寻找,直到找到为止
如果没找到则报错
- 所以我们可以将一些经常需要访问的程序和文件的路径添加到path中,
这样我们就可以在任意位置来访问这些文件和程序了
I/O (Input/Output)
- I/O操作指的是对磁盘的读写操作
进程和线程
进程:
①进程负责为程序的运行提供必备的环境
②进程就相当于工厂中的车间
线程:
①线程是计算机中的最小的计算单位,线程负责执行进程中的程序
②线程就相当于工厂中的工人
单线程
多线程
Node
- Node是对ES标准一个实现,Node也是一个JS引擎
- 通过Node可以使js代码在服务器端执行
- Node仅仅对ES标准进行了实现,所以在Node中不包含DOM 和 BOM
- Node中可以使用所有的内建对象
String Number Boolean Math Date RegExp Function Object Array
而BOM和DOM都不能使用
但是可以使用 console 也可以使用定时器(setTimeout() setInterval())
- Node可以在后台来编写服务器
Node编写服务器都是单线程的服务器
- 进程
- 进程就是一个一个的工作计划(工厂中的车间)
- 线程
- 线程是计算机最小的运算单位(工厂中的工人)
线程是干活的
- 传统的服务器都是多线程的
- 每进来一个请求,就创建一个线程去处理请求
- Node的服务器单线程的
- Node处理请求时是单线程,但是在后台拥有一个I/O线程池
Web服务器开发
- ip 地址用来定位计算机
- 端口号用来定位具体的应用程序
- 所有需要联网通信的应用程序都会占用一个端口号,范围为0-65536
- 可以同时开启多个服务,但一定要确保不同服务占用 的端口号不一致才可以。
Node.js 第1天
上午总结
-
Node.js 是什么
-
JavaScript 运行时
-
既不是语言,也不是框架,它是一个平台
-
-
Node.js 中的 JavaScript
-
没有 BOM、DOM
-
EcmaScript 基本的 JavaScript 语言部分
-
在 Node 中为 JavaScript 提供了一些服务器级别的 API
-
文件操作的能力
-
http 服务的能力
-
-
总结
-
Node 中的 JavaScript
-
EcmaScript
-
变量
-
方法
-
数据类型
-
内置对象
-
Array
-
Object
-
Date
-
Math
-
-
模块系统
-
在 Node 中没有全局作用域的概念
-
在 Node 中,只能通过 require 方法来加载执行多个 JavaScript 脚本文件
-
require 加载只能是执行其中的代码,文件与文件之间由于是模块作用域,所以不会有污染的问题
-
模块完全是封闭的
-
外部无法访问内部
-
内部也无法访问外部
-
-
模块作用域固然带来了一些好处,可以加载执行多个文件,可以完全避免变量命名冲突污染的问题
-
但是某些情况下,模块与模块是需要进行通信的
-
在每个模块中,都提供了一个对象:
exports
-
该对象默认是一个空对象
-
你要做的就是把需要被外部访问使用的成员手动的挂载到
exports
接口对象中 -
然后谁来
require
这个模块,谁就可以得到模块内部的exports
接口对象 -
还有其它的一些规则,具体后面讲,以及如何在项目中去使用这种编程方式,会通过后面的案例来处理
-
-
核心模块
-
核心模块是由 Node 提供的一个个的具名的模块,它们都有自己特殊的名称标识,例如
-
fs 文件操作模块
-
http 网络服务构建模块
-
os 操作系统信息模块
-
path 路径处理模块
-
。。。。
-
-
所有核心模块在使用的时候都必须手动的先使用
require
方法来加载,然后才可以使用,例如:-
var fs = require('fs')
-
-
-
-
http
-
require
-
端口号
-
ip 地址定位计算机
-
端口号定位具体的应用程序
-
-
Content-Type
-
服务器最好把每次响应的数据是什么内容类型都告诉客户端,而且要正确的告诉
-
不同的资源对应的 Content-Type 是不一样,具体参照:http://tool.oschina.net/commons
-
对于文本类型的数据,最好都加上编码,目的是为了防止中文解析乱码问题
-
-
通过网络发送文件
-
发送的并不是文件,本质上来讲发送是文件的内容
-
当浏览器收到服务器响应内容之后,就会根据你的 Content-Type 进行对应的解析处理
-
-
-
模块系统
-
Node 中的其它的核心模块
-
做一个小管理系统:
-
CRUD
-
-
Express Web 开发框架
-
npm install express
-
day02
代码风格问题
当你采用了无分号的代码风格的时候,只需要注意以下情况就不会有上面的问题了:
当一行代码是以:
(
[
`
开头的时候,则在前面补上一个分号用以避免一些语法解析错误。
所以你会发现在一些第三方的代码中能看到一上来就以一个 ; 开头。
结论:
无论你的代码是否有分号,都建议如果一行代码是以 (、[、` 开头的,则最好都在其前面补上一个分号。
有些人也喜欢玩儿一些花哨的东西,例如可以使用 ! ~ 等。
Node.js 第2天课堂笔记
知识点
反馈
-
-
HTML 也是标记语言
-
markdown 标记语言
-
#
就是标题 -
-
、*
就是列表 -
**加粗内容**
-
GFM
-
代码风格
var foo = 'bar'
var foo ='bar'
var foo= 'bar'
var foo = "bar"
if (true) {
console.log('hello')
}
if (true) {
console.log('hello')
}
if (true ){
console.log('hello')
}
为了约定大家的代码风格,所以在社区中诞生了一些比较规范的代码风格规范:dnsajkndkjsabnjkdnjksandjknsajkdnjkasnjkdnjksandjknsajkdnjksajkdnas
-
Airbnb JavaScript Style
复习
上午总结
-
代码风格
-
无分号
-
(
-
[
-
`
-
最好前面补分号,避免一些问题
-
《编写可维护的 JavaScript》
-
不仅是功能,还要写的漂亮
-
-
服务端渲染
-
说白了就是在服务端使用模板引擎
-
模板引擎最早诞生于服务端,后来才发展到了前端
-
-
服务端渲染和客户端渲染的区别
-
客户端渲染不利于 SEO 搜索引擎优化
-
服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的
-
所以你会发现真正的网站既不是纯异步也不是纯服务端渲染出来的
-
而是两者结合来做的
-
例如京东的商品列表就采用的是服务端渲染,目的了为了 SEO 搜索引擎优化
-
而它的商品评论列表为了用户体验,而且也不需要 SEO 优化,所以采用是客户端渲染
-
留言板实践
浏览器收到 HTML 响应内容之后,就要开始从上到下依次解析,
当在解析的过程中,如果发现:
link
script
img
iframe
video
audio
等带有 src 或者 href(link) 属性标签(具有外链的资源)的时候,浏览器会自动对这些资源发起新的请求。
day03
Node.js 第3天课堂笔记
知识点
-
增删改查
-
登陆
-
注册
-
头像
-
服务端图片
-
水印
-
图片水印
-
-
找回密码
-
密码修改
-
模块系统
-
核心模块
-
第三方模块
-
自己写的模块
-
加载规则以及加载机制
-
循环加载
-
-
npm
-
package.json
-
Express
-
第三方 Web 开发框架
-
高度封装了 http 模块
-
更加专注于业务,而非底层细节
-
知其所以然
-
-
增删改查
-
使用文件来保存数据(锻炼异步编码)
-
-
MongoDB
-
(所有方法都封装好了)
-
反馈
-
希望老师再推荐一些前端学习的书籍,谢谢!
-
《JavaScript 高级编程》第3班
-
学习,解决问题
-
书本可以更好的系统的整理学过的内容,了解一些细节
-
《JavaScript 语言精粹》
-
-
seo的资料,嘿嘿
-
网站运营 SEO
-
SEO 运营专员
-
百度、Google、搜狗、
-
-
最后老师那个怎么做案例的步骤真的是很有用 觉得今天的反馈 大概又是夸老师的比较多 老师声音很有特点
-
老师讲的很仔细,虽然语速有点快但是会重复很多遍,即使第一遍没听会第二遍第三遍也懂了.很好.
-
使用markdown一次只能打开一个文件,不知道怎么建文件夹,是需要安插件吗?
-
老师,软件版本的升级是以什么作为理论支持的,为什么跳跃间隙可以这么大?还有,看上了老师的电子图书馆,瞬间好爱学习呀,真的!
-
软件开发版本里面涉及到软件工程学:
-
x.x.x
-
0.0.1
-
0.0.2
-
1.1.5
-
1.9.2
-
2(新增功能比较多,甚至可能去除了某些功能).5(加入了新功能).0(修复bug,提升性能)
-
大版本
-
一般是这些客户端软件、技术框架开发者比较理解的多
-
做网站很少涉及到版本的概念,网站的目的就是快
-
-
-
art-template里面用的语法是jQuery吗, each什么的 我晕了 each,forEach, 遍历的全混了
-
art-template 和 jQuery 一毛钱关系都没有
-
each 是 art-template 的模板语法,专属的
-
{{each 数组}}
-
<li>{{ $value }}</li>
-
{{/each}} 这是 art-template 模板引擎支持的语法,只能在模板字符串中使用
-
$.each(数组, function)
-
$('div').each(function) 一般用于遍历 jQuery 选择器选择到的伪数组实例对象
-
forEach 是 EcmaScript 5 中的一个数组遍历函数,是 JavaScript 原生支持的遍历方法 可以遍历任何可以被遍历的成员
-
jQuery 的 each 方法和 forEach 几乎一致
-
由于 forEach 是 EcmaScript 5 中的,所以低版本浏览器不支持
-
-
每一次的复习贼重要 老师很不错 我喜欢
-
在以后的工作中 用到node.js的地方多吗? 在留言本的案例中 点击发表留言跳转页面的路径是url路径 和之前写的页面跳转写的文件路径还是有点分不清。
-
技多不压身
-
Node 对于前端来讲是进阶高级前端开发工程师必备的技能
-
屌丝最容易逆袭的职业
-
见得东西多了你就不怕了
-
为所欲为
-
-
老师讲的挺清晰的 可是第一节太困了 路径有点没转变过来
-
如果从a中调用b中的数据,又从b中调用a中的数据,执行a代码,为什么把b中的执行完后才会执行a,而不是在b调用a的时候a中的代码继续执行
-
a 加载了 b
-
执行 b 中的代码
-
同时得到 b 中导出的接口对象:exports
-
执行 b 的过程中发现 b 也在 require a
-
b 就会反过来执行 a
-
a 中又加载 b
-
b 又反过来加载 a
-
这就是循环加载
-
如果你一旦出现了这种情况,说明你的思路有问题。
-
jQuery.js (可能不可能出现 jQuery 依赖了 main)
-
main.js 依赖了 jQuery
-
这个问题是矛盾。
-
-
b 中也加载了 a
-
-
网页中所有的路径其实都是 url 路径,不是文件路径
-
-
问题就是不知道问题是什么,写案例的时候似懂非懂
-
感觉思维有点跟不上,
复习
-
网站开发模型
-
黑盒子、哑巴
-
写代码让它变得更智能
-
按照你设计好的套路供用户使用
-
-
在 Node 中使用 art-template 模板引擎
-
安装
-
加载
-
template.render()
-
-
客户端渲染和服务端渲染的区别
-
最少两次请求,发起 ajax 在客户端使用模板引擎渲染
-
客户端拿到的就是服务端已经渲染好的
-
-
处理留言本案例首页数据列表渲染展示
-
处理留言本案例发表留言功能
-
路径
-
设计好的请求路径
-
$GET 直接或查询字符串数据
-
Node 中需要咱们自己动手来解析
-
url.parse()
-
-
/pinglun?name=jack&message=hello
-
split('?')
-
name=jack&message=hello
-
split('&')
-
name=jack message=hello
-
forEach()
-
name=jack.split('=')
-
0 key
-
1 value
-
-
掌握如何解析请求路径中的查询字符串
-
url.parse()
-
-
如何在 Node 中实现服务器重定向
-
header('location')
-
301 永久重定向 浏览器会记住
-
a.com b.com
-
a 浏览器不会请求 a 了
-
直接去跳到 b 了
-
-
302 临时重定向 浏览器不记忆
-
a.com b.com
-
a.com 还会请求 a
-
a 告诉浏览器你往 b
-
-
-
-
Node 中的 Console(REPL)使用
上午总结
-
jQuery 的 each 和 原生的 JavaScript 方法 forEach
-
EcmaScript 5 提供的
-
不兼容 IE 8
-
-
jQuery 的 each 由 jQuery 这个第三方库提供
-
jQuery 2 以下的版本是兼容 IE 8 的
-
它的 each 方法主要用来遍历 jQuery 实例对象(伪数组)
-
同时它也可以作为低版本浏览器中 forEach 替代品
-
jQuery 的实例对象不能使用 forEach 方法,如果想要使用必须转为数组才可以使用
-
[].slice.call(jQuery实例对象)
-
-
-
模块中导出多个成员和导出单个成员
-
导出多个
-
exports.a=123 exports.b='hello' exports.c=function (){ console.log('ccc') }
也可以这么写
-
module.exports={ foo:'bar', add:function(){ console.log('jhhh') } }
导出单个
-
module.exports='hello'
-
301 和 302 状态码区别
-
301 永久重定向,浏览器会记住
-
302 临时重定向
-
-
exports 和 module.exports 的区别
-
每个模块中都有一个 module 对象
-
module 对象中有一个 exports 对象
-
我们可以把需要导出的成员都挂载到 module.exports 接口对象中
-
也就是:
moudle.exports.xxx = xxx
的方式 -
但是每次都
moudle.exports.xxx = xxx
很麻烦,点儿的太多了 -
所以 Node 为了你方便,同时在每一个模块中都提供了一个成员叫:
exports
-
exports === module.exports
结果为true
s -
所以对于:
moudle.exports.xxx = xxx
的方式 完全可以:expots.xxx = xxx
-
当一个模块需要导出单个成员的时候,这个时候必须使用:
module.exports = xxx
的方式 -
不要使用
exports = xxx
不管用 -
因为每个模块最终向外
return
的是module.exports
-
而
exports
只是module.exports
的一个引用 -
所以即便你为
exports = xx
重新赋值,也不会影响module.exports
-
但是有一种赋值方式比较特殊:
exports = module.exports
这个用来重新建立引用关系的 -
之所以让大家明白这个道理,是希望可以更灵活的去用它
-
-
Node 是一个比肩 Java、PHP 的一个平台
-
JavaScript 既能写前端也能写服务端
-
moudle.exports = {
a: 123
}
// 重新建立 exports 和 module.exports 之间的引用关系
exports = module.exports
exports.foo = 'bar'
Array.prototype.mySlice = function () {
var start = 0
var end = this.length
if (arguments.length === 1) {
start = arguments[0]
} else if (arguments.length === 2) {
start = arguments[0]
end = arguments[1]
}
var tmp = []
for (var i = start; i < end; i++) {
// fakeArr[0]
// fakeArr[1]
// fakeArr[2]
tmp.push(this[i])
}
return tmp
}
var fakeArr = {
0: 'abc',
1: 'efg',
2: 'haha',
length: 3
}
// 所以你就得到了真正的数组。
[].mySlice.call(fakeArr)
下午总结
-
jQuery 的 each 和 原生的 JavaScript 方法 forEach
-
301 和 302 的区别
-
模块中导出单个成员和导出多个成员的方式
-
module.exports 和 exports 的区别
-
require 方法加载规则
-
①优先从缓存加载
-
-
结果:
-
-
-
②核心模块
-
// 核心模块的本质也是文件
// 核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字来加载就可以了
// require('fs')
// require('http')
-
③ 路径形式的模块
-
// ./ 当前目录,不可省略
// ../ 上一级目录,不可省略
// /xxx 几乎不用
// d:/a/foo.js 几乎不用
// 首位的 / 在这里表示的是当前文件模块所属磁盘根路径
// .js 后缀名可以省略
// require('./foo.js')
-
④第三方模块
-
node_modules
-
// 凡是第三方模块都必须通过 npm 来下载
// 使用的时候就可以通过 require('包名') 的方式来进行加载才可以使用
// 不可能有任何一个第三方包和核心模块的名字是一样的
// 既不是核心模块、也不是路径形式的模块
// 先找到当前文件所处目录中的 node_modules 目录
// node_modules/art-template
// node_modules/art-template/package.json 文件
// node_modules/art-template/package.json 文件中的 main 属性
// main 属性中就记录了 art-template 的入口模块
// 然后加载使用这个第三方包
// 实际上最终加载的还是文件
-
// 如果 package.json 文件不存在或者 main 指定的入口模块是也没有
// 则 node 会自动找该目录下的 index.js
// 也就是说 index.js 会作为一个默认备选项
-
// 如果以上所有任何一个条件都不成立,则会进入上一级目录中的 node_modules 目录查找
// 如果上一级还没有,则继续往上上一级查找
// 。。。
// 如果直到当前磁盘根目录还找不到,最后报错:
// can not find module xxx
-
// 注意:我们一个项目有且只有一个 node_modules,放在项目根目录中,这样的话项目中所有的子目录中的代码都可以加载到第三方包
// 不会出现有多个 node_modules
-
// 模块查找机制
// 优先从缓存加载
// 核心模块
// 路径形式的文件模块
// 第三方模块
// node_modules/art-template/
// node_modules/art-template/package.json
// node_modules/art-template/package.json main
// index.js 备选项
// 进入上一级目录找 node_modules,兄弟路径的不可以
// 按照这个规则依次往上找,直到磁盘根目录还找不到,最后报错:Can not find moudle xxx
// 一个项目有且仅有一个 node_modules 而且是存放到项目的根目录
-
-
package.json 包描述文件
-
dependencies 选项的作用,它可以通过 --save来获得
-
这个文件可以通过npm init来获得
-
-
-
目前对我们来说,最有用的是那个dependencies选项,可以用来帮我们保存第三方包的依赖信息。
-
建议每个项目的根目录下都有一个package.json
-
建议执行 npm install 包名 的时候都加上--save这个选项,目的是用来保存依赖项信息
-
如果你的node_modules删除了也不用担心,我们只需要:npm install 就会自动把package.json中的dependences中的所有依赖项都下载回来。
-
-
npm
- npm命令行工具
- npm install --global npm (自己升级自己)
-
npm常用命令
- npm init
- npm init -y 可以跳过向导,快速生成
- npm install
- npm i 简写
- 一次性吧dependencies选项中的依赖项全部安装
- npm install 包名
- 只下载
- npm i 包名
- npm install --save 包名
- 下载并保存依赖项(package.json文件中的dependencies选项
- npm i -S 包名
- npm uninstall 包名
- 只删除,如果有依赖项会依然保存
- npm un 包名
- npm uninstall --save 包名
- 删除的同时也会把依赖信息也去除
- npm un -S 包名
- npm help
- 查看使用帮助
- npm 命令 --help
- 查看指定命令的使用帮助
-
解决npm被(qiang)问题
- npm 存储包文件的服务器在国外,有时候会被qiang,速度很慢,所以我们需要解决这个问题
- https://developer.aliyun.com/mirror/NPM?from=tnpm淘宝的开发团队在国内做了一个备份
- 查看npm配置信息
- npm config list
-
Express 基本使用
- npm install --save express
-
修改完代码自动重启
- 我们可以使用第三方命名航工具:nodemon 来帮我们解决频繁修改代码重启服务器问题
- nodemon 是一个基于Node.js开发的一个第三方命令行工具,我们使用的时候需要独立安装
- npm install --global nodemon (--global来安装的包都可以在任意目录进行)
- 使用: nodemon app.js
- 它会监视你的文件变化,当文件发生变化的时候,自动帮你重启服务器
-
基本路由
- 路由器:
- 请求方法
- 请求路径
- 请求处理函数
- get
-
app.get('/',function (req,res) { res.send('Hello world') })
post
-
使用 Express 把之前的留言本案例自己动手改造一下
-
在Express中配置使用art-template模板引擎
- art-template-GitHub仓库
- 安装:
-
npm install --save art-template npm install --save express-art-template
-
配置使用 art-template 模板引擎
-
// 第一个参数,表示,当渲染以 .art 结尾的文件的时候,使用 art-template 模板引擎
// express-art-template 是专门用来在 Express 中把 art-template 整合到 Express 中
// 虽然外面这里不需要加载 art-template 但是也必须安装
// 原因就在于 express-art-template 依赖了 art-template
app.engine('html', require('express-art-template'))
// Express 为 Response 相应对象提供了一个方法:render
// render 方法默认是不可以使用,但是如果配置了模板引擎就可以使用了
// res.render('html模板名', {模板数据})
// 第一个参数不能写路径,默认会去项目中的 views 目录查找该模板文件
// 也就是说 Express 有一个约定:开发人员把所有的视图文件都放到 views 目录中
app.get('/',function (req,res){
res.render('404.html')
})
-
// 如果想要修改默认的 views 目录,则可以
// app.set('views', render函数的默认路径)
在Express中获取表单GET请求参数
Express内置了一个API,可以直接获得req.query来获取
在Express中获取表单POST请求体数据
- 在Express中没有内置获取表单POST请求体的API,这里我们需要使用一个第三方包:body-parser
- 安装: npm install --save body-parser
- 配置:
-
// 配置 body-parser 中间件(插件,专门用来解析表单 POST 请求体)
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
异步编程
回调函数
目的:获取异步操作的结果
eg:
调用:
save:
调用:
Promise(回调地狱)
无法保证顺序的代码:
模块标识中的 /
和文件操作路径中的 /
使用的所有文件操作的 API 都是异步的
文件操作中的相对路径可以省略 ./
在模块加载中,相对路径中的 ./ 不能省略
在文件操作的相对路径中
./data/a.txt 相对于当前目录
data/a.txt 相对于当前目录
/data/a.txt 绝对路径,当前文件模块所处磁盘根目录
c:/xx/xx... 绝对路径
// 这里如果忽略了 . 则也是磁盘根目录
//require('/data/foo.js')
上午总结
演讲
说服 PPT 脑图 markdown 结构思维
-
找痛点 why 为什么
-
解决方案 what 是什么
-
怎么去使用 how 怎么用
-
where 在哪儿用
-
when 什么时候用
-
文件路径中的
/
和模块标识中的/
-
nodemon
-
Express
-
art-template 模板引擎的配置
-
body-parser 解析表单 POST 请求体
-
-
技术只是一种解决问题的手段、工具而已
-
第三方的东西,不要纠结
-
先以解决问题为主
-
-
详解了 express 静态服务 API
-
app.use('/public/', express.static('./public'))
-
-
crud
-
文件路径中的
/
和模块标识中的/
-
Express 中配置使用 art-template 模板引擎
-
Express 中配置使用 body-parser
-
Express 中配置处理静态资源
-
CRUD 案例中单独提取路由模块
-
增删改查
app_1.js
/**
* app.js 入门模块
* 职责:
* 创建服务
* 做一些服务相关配置
* 模板引擎
* body-parser 解析表单 post 请求体
* 提供静态资源服务
* 挂载路由
* 监听端口启动服务
*/
var express=require('express')
var router=require('./router_1')
var app=express()
var bodyParser=require('body-parser')
app.use('/node_modules/',express.static('./node_modules/'))
app.use('/public/',express.static('./public/'))
app.engine('html',require('express-art-template'))
//配置模板引擎和body-parser一定要在app.use(router)挂载路由之前
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
//把路由容器挂载到app服务中
app.use(router)
app.listen(3000,function(){
console.log('running 3000.....')
})
router_1.js
/**
* router.js 路由模块
* 职责:
* 处理路由
* 根据不同的请求方法+请求路径设置具体的请求处理函数
* 模块职责要单一,不要乱写
* 我们划分模块的目的就是为了增强项目代码的可维护性
* 提升开发效率
*/
var fs = require('fs')
var Student = require('./student_1')
//修改成功,说明updateById方法正确
// Student.updateById({
// id:1,
// name:'zhangxiaosan'
// },function(err){
// if(err){
// console.log('修改失败')
// }
// console.log('修改成功')
// })
// Express 提供了一种更好的方式
// 专门用来包装路由的
var express = require('express')
// 1. 创建一个路由容器
var router = express.Router()
// 2. 把路由都挂载到 router 路由容器中
/*
* 渲染学生列表页面
*/
router.get('/students', function (req, res) {
Student.find(function (err, students) {
if (err) {
return res.status(500).send('Server error.')
}
res.render('index_1.html', {
fruits: [
'苹果',
'香蕉',
'橘子'
],
students: students
})
})
})
/*
* 渲染添加学生页面
*/
router.get('/students/new', function (req, res) {
res.render('new_1.html')
})
/*
* 处理添加学生
*/
router.post('/students/new', function (req, res) {
// 1. 获取表单数据
// 2. 处理
// 将数据保存到 db.json 文件中用以持久化
// 3. 发送响应
Student.save(req.body, function (err) {
if (err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
})
/*
* 渲染编辑学生页面
*/
router.get('/students/edit', function (req, res) {
// 1. 在客户端的列表页中处理链接问题(需要有 id 参数)
// 2. 获取要编辑的学生 id
//
// 3. 渲染编辑页面
// 根据 id 把学生信息查出来
// 使用模板引擎渲染页面
//console.log(req.query.id)
Student.findById(parseInt(req.query.id), function (err, student) {
if (err) {
return res.status(500).send('Server error.')
}
// console.log(student)
res.render('edit_1.html', {
student: student
})
})
})
/*
* 处理编辑学生
*/
router.post('/students/edit', function (req, res) {
// 1. 获取表单数据
// req.body
// 2. 更新
// Student.updateById()
// 3. 发送响应
//console.log(req.body)
Student.updateById(req.body, function (err) {
if (err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
})
/*
* 处理删除学生
*/
router.get('/students/delete', function (req, res) {
// 1. 获取要删除的 id
// 2. 根据 id 执行删除操作
// 3. 根据操作结果发送响应数据
Student.deleteById(req.query.id, function (err) {
if (err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
})
// 3. 把 router 导出
module.exports = router
// 这样也不方便
// module.exports = function (app) {
// app.get('/students', function (req, res) {
// // readFile 的第二个参数是可选的,传入 utf8 就是告诉它把读取到的文件直接按照 utf8 编码转成我们能认识的字符
// // 除了这样来转换之外,也可以通过 data.toString() 的方式
// fs.readFile('./db.json', 'utf8', function (err, data) {
// if (err) {
// return res.status(500).send('Server error.')
// }
// // 从文件中读取到的数据一定是字符串
// // 所以这里一定要手动转成对象
// var students = JSON.parse(data).students
// res.render('index.html', {
// fruits: [
// '苹果',
// '香蕉',
// '橘子'
// ],
// students: students
// })
// })
// })
// app.get('/students/new', function (req, res) {
// })
// app.get('/students/new', function (req, res) {
// })
// app.get('/students/new', function (req, res) {
// })
// app.get('/students/new', function (req, res) {
// })
// app.get('/students/new', function (req, res) {
// })
// }
students_1.js
/**
* student.js
* 数据操作文件模块
* 职责:操作文件中的数据,只处理数据,不关心业务
*
* 这里才是我们学习 Node 的精华部分:奥义之所在
* 封装异步 API
*/
var fs = require('fs')
var dbPath = './db.json'
/**
* 获取学生列表
* @param {Function} callback 回调函数
*/
exports.find = function (callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
callback(null, JSON.parse(data).students)
})
}
/**
* 根据 id 获取学生信息对象
* @param {Number} id 学生 id
* @param {Function} callback 回调函数
*/
exports.findById = function (id, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students
var ret = students.find(function (item) {
return item.id === parseInt(id)
})
callback(null, ret)
})
}
/**
* 添加保存学生
* @param {Object} student 学生对象
* @param {Function} callback 回调函数
*/
exports.save = function (student, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students
// 添加 id ,唯一不重复
student.id = students[students.length - 1].id + 1
// 把用户传递的对象保存到数组中
students.push(student)
// 把对象数据转换为字符串
var fileData = JSON.stringify({
students: students
})
// 把字符串保存到文件中
fs.writeFile(dbPath, fileData, function (err) {
if (err) {
// 错误就是把错误对象传递给它
return callback(err)
}
// 成功就没错,所以错误对象是 null
callback(null)
})
})
}
/**
* 更新学生
*/
exports.updateById = function (student, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students
// 注意:这里记得把 id 统一转换为数字类型
student.id = parseInt(student.id)
// 你要修改谁,就需要把谁找出来
// EcmaScript 6 中的一个数组方法:find
// 需要接收一个函数作为参数
// 当某个遍历项符合 item.id === student.id 条件的时候,find 会终止遍历,同时返回遍历项
var stu = students.find(function (item) {
return item.id === student.id
})
// 这种方式你就写死了,有 100 个难道就写 100 次吗?
// stu.name = student.name
// stu.age = student.age
// 遍历拷贝对象
for (var key in student) {
stu[key] = student[key]
}
// 把对象数据转换为字符串
var fileData = JSON.stringify({
students: students
})
// 把字符串保存到文件中
fs.writeFile(dbPath, fileData, function (err) {
if (err) {
// 错误就是把错误对象传递给它
return callback(err)
}
// 成功就没错,所以错误对象是 null
callback(null)
})
})
}
/**
* 删除学生
*/
exports.deleteById = function (id, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students
// findIndex 方法专门用来根据条件查找元素的下标
var deleteId = students.findIndex(function (item) {
return item.id === parseInt(id)
})
//console.log(deleteId)
// 根据下标从数组中删除对应的学生对象
students.splice(deleteId, 1)
// 把对象数据转换为字符串
var fileData = JSON.stringify({
students: students
})
// 把字符串保存到文件中
fs.writeFile(dbPath, fileData, function (err) {
if (err) {
// 错误就是把错误对象传递给它
return callback(err)
}
// 成功就没错,所以错误对象是 null
callback(null)
})
})
}
异步编程
回调函数
不成立的情况:
①
②
回调函数

基于原生XMLHttpRequest封装get方法:
// setTimeout
// readFile
// wirteFile
// readdir
// ajax
// 往往异步 API 都伴随有一个回调函数
function get(url, callback) {
var oReq = new XMLHttpRequest()
// 当请求加载成功之后要调用指定的函数
oReq.onload = function () {
// 我现在需要得到这里的 oReq.responseText
callback(oReq.responseText)
}
oReq.open("get", url, true)
oReq.send()
}
get('data.json', function (data) {
console.log(data)
})
Promise
Promise本身不是异步,但是内部往往都是封装一个异步任务
Promise基本语法:
调用
p1
.then(function (data) {
console.log(data)
return p2
}, function (err) {
console.log('读取文件失败了', err)
})
图解:
Promise api
var fs = require('fs')
var p1 = new Promise(function (resolve, reject) {
fs.readFile('./data/a.txt', 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
var p2 = new Promise(function (resolve, reject) {
fs.readFile('./data/b.txt', 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
var p3 = new Promise(function (resolve, reject) {
fs.readFile('./data/c.txt', 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
p1
.then(function (data) {
console.log(data)
// 当 p1 读取成功的时候
// 当前函数中 return 的结果就可以在后面的 then 中 function 接收到
// 当你 return 123 后面就接收到 123
// return 'hello' 后面就接收到 'hello'
// 没有 return 后面收到的就是 undefined
// 上面那些 return 的数据没什么卵用
// 真正有用的是:我们可以 return 一个 Promise 对象
// 当 return 一个 Promise 对象的时候,后续的 then 中的 方法的第一个参数会作为 p2 的 resolve
//
return p2
}, function (err) {
console.log('读取文件失败了', err)
})
.then(function (data) {
console.log(data)
return p3
})
.then(function (data) {
console.log(data)
console.log('end')
})
p1,p2,p3都是new 出来的Promise对象
封装Promise版本的ReadFile
var fs = require('fs')
function pReadFile(filePath) {
return new Promise(function (resolve, reject) {
fs.readFile(filePath, 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
pReadFile('./data/a.txt')
.then(function (data) {
console.log(data)
return pReadFile('./data/b.txt')
})
.then(function (data) {
console.log(data)
return pReadFile('./data/c.txt')
})
.then(function (data) {
console.log(data)
})
+ 模块作用域
+ 可以使用 API 来进行文件与文件之间的依赖加载
+ 在 Node 这个环境中对 JavaScript 进行了特殊的模块化支持 CommonJS
+ JavaScript 天生不支持模块化
* require
* exports
* Node.js 才有的
+ 在浏览器中也可以像在 Node 中的模块一样来进行编程
* `<script>` 标签来引用加载,而且你还必须考虑加载的顺序问题
* require.js 第三方库 AMD
* sea.js 第三方库 CMD
+ 无论是 CommonJS、AMD、CMD、UMD、EcmaScript 6 Modules 官方规范
* 都是为了解决 JavaScript 的模块化问题
* CommonJS、AMD、CMD 都是民间搞出来的
* EcmaScript 是官方规范定义
* 官方看民间都在乱搞,开发人员为了在不同的环境使用不同的 JavaScript 模块化解决方案
* 所以 EcmaScript 在 2015 年发布了 EcmaScript 2016 官方标准
* 其中就包含了官方对 JavaScript 模块化的支持
* 也就是说语言天生就支持了
* 但是虽然标准已经发布了,但是很多 JavaScript 运行换将还不支持
* Node 也是只在 8.5 版本之后才对 EcmaScript 6 module 进行了支持
* 后面学 Vue 的时候会去学习
* less 编译器 > css
* EcmaScript 6 -> 编译器 -> EcmaScript 5
* 目前的前端情况都是使用很多新技术,然后利用编译器工具打包可以在低版本浏览器运行。
* 使用新技术的目的就是为了提高效率,增加可维护性
package.json和package-lock.json

path路径操作模块
参考文档https://nodejs.org/dist/latest-v13.x/docs/api/path.html
eg:
Node中的其它成员
在每个模块中,除了require、exports等模块相关API之外,还有两个特殊的成员:
__dirname可以用来获取当前文件模块所属目录的绝对路径(动态获取)
__filename可以用来获取当前文件的绝对路径(动态获取 )
__dirname和__filename是不受执行node命令所属路径影响的
文件操作路径中,相对路径设计的就是相对于执行 node 命令所处的路径
// fs.readFile('C:/Users/lpz/Desktop/nodejs/06/code/foo/a.txt', 'utf8', function (err, data) {
// if (err) {
// throw err
// }
// console.log(data)
// })
// console.log(__dirname + '/a.txt')
// C:\Users\lpz\Desktop\nodejs\06\code
fs.readFile(path.join(__dirname, './a.txt'), 'utf8', function (err, data) {
if (err) {
throw err
}
console.log(data)
})
模块中的路径标识和文件操作中的相对路径标识不一致
模块中的路径标识就是相对于当前文件模块,不受执行 node 命令所处路径影响
require('./b')
MongoDB
参考菜鸟教程https://www.runoob.com/mongodb/mongodb-tutorial.html
启动:
mongodb默认使用执行mongod命令所处盘符根目录下的/data/db 作为自己的数据存储目录
所以在第一次执行该命令之前先自己手动新建一个 /data/db
如果想要修改默认的数据存储目录,可以: mongod --dbpath=数据存储目录路径
停止:
在开启服务的控制台,直接按ctrl+c即可停止
或者直接关闭开启服务的控制台也可以
连接和退出数据库
mongo :该命令默认连接本机的MongoDB 服务
exit:在连接状态输入exit退出连接
基本命令
show dbs : 查看显示所有数据库
db : 查看当前操作的数据库
use 数据库名称: 切换到指定的数据 (如果没有会新建)
插入数据:
在Node中如何操作MongoDB数据
使用官方的mongodb包来操作
https://github.com/mongodb/node-mongodb-native
使用第三方mongoose来操作MongoDB数据库
MongoDB数据库的基本概念
eg:
Mongoose
设计Scheme发布Model
var mongoose = require('mongoose')
var Schema = mongoose.Schema
// 1. 连接数据库
// 指定连接的数据库不需要存在,当你插入第一条数据之后就会自动被创建出来
mongoose.connect('mongodb://localhost/itcast')
// 2. 设计文档结构(表结构)
// 字段名称就是表结构中的属性名称
// 约束的目的是为了保证数据的完整性,不要有脏数据
var userSchema = new Schema({
username: {
type: String,
required: true // 必须有
},
password: {
type: String,
required: true
},
email: {
type: String
}
})
// 3. 将文档结构发布为模型
// mongoose.model 方法就是用来将一个架构发布为 model
// 第一个参数:传入一个大写名词单数字符串用来表示你的数据库名称
// mongoose 会自动将大写名词的字符串生成 小写复数 的集合名称
// 例如这里的 User 最终会变为 users 集合名称
// 第二个参数:架构 Schema
//
// 返回值:模型构造函数
var User = mongoose.model('User', userSchema)
增加数据
var admin = new User({
username: 'zs',
password: '123456',
email: 'admin@admin.com'
})
admin.save(function (err, ret) {
if (err) {
console.log('保存失败')
} else {
console.log('保存成功')
console.log(ret)
}
})
查询数据
查询所有:
User.find(function (err, ret) {
if (err) {
console.log('查询失败')
} else {
console.log(ret)
}
})
按条件查询所有
User.find({
username: 'zs'
}, function (err, ret) {
if (err) {
console.log('查询失败')
} else {
console.log(ret)
}
})
按条件查询单个
User.findOne({
username: 'zs'
}, function (err, ret) {
if (err) {
console.log('查询失败')
} else {
console.log(ret)
}
})
删除数据
根据条件删除所有:
User.remove({
username: 'zs'
}, function (err, ret) {
if (err) {
console.log('删除失败')
} else {
console.log('删除成功')
console.log(ret)
}
})
根据条件删除一个:
Model.findOneAndRemove(conditions,[options],[callback])
根据id删除一个:
Model.findByIdAndRemove(id,[options],[callback])
更新数据
根据id更新一个:
User.findByIdAndUpdate('5a001b23d219eb00c8581184', {
password: '123'
}, function (err, ret) {
if (err) {
console.log('更新失败')
} else {
console.log('更新成功')
}
})
根据条件更新所有:
Model.update(conditions,doc,[options],[callback])
根据指定条件更新一个:
Model.findOneAndUpdate([conditions],[update],[options],[callback])
中间件
中间件:处理请求的,本质就是个函数
在 Express 中,对中间件有几种分类
当请求进来,会从第一个中间件开始进行匹配
如果匹配,则进来
如果请求进入中间件之后,没有调用 next 则代码会停在当前中间件
如果调用了 next 则继续向后找到第一个匹配的中间件
如果不匹配,则继续判断匹配下一个中间件
不关心请求路径和请求方法的中间件
也就是说任何请求都会进入这个中间件
中间件本身是一个方法,该方法接收三个参数:
Request 请求对象
Response 响应对象
next 下一个中间件
当一个请求进入一个中间件之后,如果不调用 next 则会停留在当前中间件
所以 next 是一个方法,用来调用下一个中间件的
调用 next 方法也是要匹配的(不是调用紧挨着的那个)
app.use(function (req, res, next) {
console.log('1')
next()
})
app.use(function (req, res, next) {
console.log('2')
next()
})
app.use(function (req, res, next) {
console.log('3')
res.send('333 end.')
})
app.use(function (req, res, next) {
console.log(1)
next()
})
app.use('/b', function (req, res, next) {
console.log('b')
})
// 以 /xxx 开头的路径中间件
app.use('/a', function (req, res, next) {
console.log('a')
next()
})
app.use(function (req, res, next) {
console.log('2')
next()
})
app.use('/a', function (req, res, next) {
console.log('a 2')
})
除了以上中间件之外,还有一种最常用的
严格匹配请求方法和请求路径的中间件
app.get
app.post
app.use(function (req, res, next) {
console.log(1)
next()
})
app.get('/abc', function (req, res, next) {
console.log('abc')
next()
})
app.get('/', function (req, res, next) {
console.log('/')
next()
})
app.use(function (req, res, next) {
console.log('haha')
next()
})
app.get('/abc', function (req, res, next) {
console.log('abc 2')
})
app.use(function (req, res, next) {
console.log(2)
next()
})
app.get('/a', function (req, res, next) {
console.log('/a')
})
app.get('/', function (req, res, next) {
console.log('/ 2')
})
// 如果没有能匹配的中间件,则 Express 会默认输出:Cannot GET 路径
app.listen(3000, function () {
console.log('app is running at port 3000.')
})