node.js之全栈开发

node.js之全栈开发

1.为什么要学习Node.js

  • 企业需求
    • 具有服务端开发经验更改
    • front-end
    • back-end
    • 全栈开发工程师
    • 基本的网站开发能力
      • 服务端
      • 前端
      • 运维部署
    • 多人社区

Node.js是什么

  • Node.js是JavaScript 运行时
  • 通俗易懂的讲,Node.js是JavaScript的运行平台
  • Node.js既不是语言,也不是框架,它是一个平台
  • 浏览器中的JavaScript
    • EcmaScript
      • 基本语法
      • if
      • var
      • function
      • Object
      • Array
    • Bom
    • Dom
  • Node.js中的JavaScript
    • 没有Bom,Dom
    • EcmaScript
    • 在Node中这个JavaScript执行环境为JavaScript提供了一些服务器级别的API
      • 例如文件的读写
      • 网络服务的构建
      • 网络通信
      • http服务器
    • 构建与Chrome的V8引擎之上
      • 代码只是具有特定格式的字符串
      • 引擎可以认识它,帮你解析和执行
      • Google Chrome的V8引擎是目前公认的解析执行JavaScript代码最快的
      • Node.js的作者把Google Chrome中的V8引擎移植出来,开发了一个独立的JavaScript运行时环境
  • Node.js uses an envent-driven,non-blocking I/O mode that makes it lightweight and efficent.
    • envent-driven 事件驱动
    • non-blocking I/O mode 非阻塞I/O模型(异步)
    • ightweight and efficent. 轻量和高效
  • Node.js package ecosystem,npm,is the larget scosystem of open sourcr libraries in the world
    • npm 是世界上最大的开源生态系统
    • 绝大多数JavaScript相关的包都存放在npm上,这样做的目的是为了让开发人员更方便的去下载使用
    • npm install jquery

Node能做什么

  • web服务器后台
  • 命令行工具
    • npm(node)
    • git(c语言)
    • hexo(node)
  • 对于前端工程师来讲,接触最多的是它的命令行工具
    • 自己写的很少,主要是用别人第三方的
    • webpack
    • gulp
    • npm

一些资源

在这里插入图片描述

学习到什么

在这里插入图片描述
在这里插入图片描述

2.Node

2.1起步

安装Node环境

  • 查看Node环境的版本号
  • 下载:https://nodejs.org/en/
  • 安装:
    • 傻瓜式安装,一路next
    • 安装过再次安装会升级
  • 确认Node环境是否安装成功
    • 查看node的版本号:node --version
    • 或者node -v
  • 配置环境变量

解析执行JavaScript

  1. 创建编写JavaScript脚本文件
  2. 打开终端,定位脚本文件的所属目录 cd…
  3. 输入node 文件名执行对应的文件
var foo = 'hello nodejs'
console.log(foo)

在这里插入图片描述
注意:文件名不要用node.js来命名,也就是说除了node这个名字随便起,最好不要使用中文。

没有bom和dom

// 在Node中,采用EcmaScript进行编码
// 没有Bom,Dom
// 和浏览器中的JavaScript不一样
console.log(window)
console.log(document)

在这里插入图片描述

2.2文件的读写

文件读取:

创建一个02-读取文件.js文件,并在同级文件目录下创建一个data文件,创建一个hello.txt文件,写入
在这里插入图片描述
在02-读取文件.js文件中

//浏览器中的JavaScript是没有文件操作能力的
//但是Node中的JavaScript具有文件操作能力
//fs是file-system的简写,就是文件系统的意思
//在Node中如果想要进行文件的操作就必须引用fs这个核心模块
//在fs这个和兴模块中,就提供了人所有文件操作相关的API
//例如 fs.readFile就是用来读取文件的

//  1.使用require加载fs核心模块
var fs = require('fs');
// 2.读取文件
//   第一个参数就是要读取的文件路径
//   第二个参数是一个回调函数
//     成功
//       data   数据
//       error  null
//     失败
//       data  null
//       error 错误对象
// 2.读取文件
fs.readFile('./data/hello.txt',function(err,data){
   if(err){
        console.log('文件读取失败');
   }
    else{
         console.log(data.toString());
    }
})

在这里插入图片描述

文件写入

var fs = require('fs')

// $ajax({
// 	...
// 	success:function(a){

// 	}
// })

// 第一个参数: 文件路径
// 第二个参数: 文件内容
// 第三个参数: 回调函数
// 	   error

//     成功:
//       文件写入成功
//       error  是null
//     失败
//       文件写入失败
//       error 是null

// 2.将数据写入文件
fs.writeFile('./data/你好.md','你好啊,我是node.js',function(err){
   if(err){
        console.log('文件写入失败');
   }
    else{
         console.log('文件写入成功');
    }
})

在这里插入图片描述

2.3服务器

最简单的服务器

// 在node中专门提供一个核心模块: http
// http 这个模块的职责就是帮你创建编写服务器的

// 1.加载http核心模块
var http = require('http')

// 2.使用http.createServer() 方法创建一个web服务器
// 返回一个Server实例
var server = http.createServer()

// 3.服务器有什么用?
// 提供服务: 对 数据的服务
// 发请求
// 接受请求
// 处理请求
// 给个反馈(发送响应)
// 注册请求事件
// 当客户端请求过来, 就会自动触发服务器的request请求事件,然后执行第二个参数,回调处理函数
server.on('request', function() {
	console.log('收到客户端的请求了')
})

// 4.绑定端口号,启动服务器
server.listen(3000, function () {
	console.log('服务器启动成功了,可以通过http://127.0.0.1:3000/来访问')
})

服务器加上请求响应

var http = require('http')

// 2.使用http.createServer() 方法创建一个web服务器
// 返回一个Server实例
var server = http.createServer()

// request请求处理函数,需要接收两个参数
//	Request请求对象
//		请求对象可以用来获取客户端的一些请求信息,例如请求路径
//	Response 响应对象
//		响应对象可以用来给客户端发送响应信息
server.on('request', function(request, response) {
	// 127.0.0.1:3000/
	// 127.0.0.1:3000/a
	// 127.0.0.1:3000/foo
	console.log('收到客户端的请求了,请求路径是:' + request.url)

	// response对象有一个方法:write可以用来给客户端发送响应数据
	// write可以使用多次。但是最后一定要使用end来结束响应,否则客户端会一直等待
	response.write('hello')
	response.write(' node')
	// 告诉客户端,我的话说完了,你可以呈递给用户了
	response.end()
	// 由于我们的服务器的能力还非常弱,无论什么请求,都只能响应hello ndoe
	// 思考
	// 我们希望当请求不同的路径时,响应不同的结果
	// 例如:
	// /index
	// /login 登录
	// /register 注册
	// /haha 哈哈哈
 })

// 4.绑定端口号,启动服务器
server.listen(3000, function () {
	console.log('服务器启动成功了,可以通过http://127.0.0.1:3000/来访问')
})

3.Node中的模块系统

使用Node编写应用程序主要就是在使用:

  • EcmaScript语言
    • 和浏览器一样,在Node中没有Bom和Dom
  • 核心模块
    • 文件操作的fs
    • http服务操作的http
    • url路径操作模块
    • path路径处理模块
    • os操作系统信息
  • 第三方模块
    • art-template
    • 必须通过npm来下载才可以使用
  • 自己写的模块
    • 自己创建的文件

什么是模块化?

  • 文件作用域(模块是独立的,在不同的文件使用必须要重新引用)【在node中没有全局作用域,它是文件模块作用域,也就是说超出这个文件就不管用了】
  • 通信规则
    • 加载require,相对路径中的(./)不能省略,文件后缀名可以省略。
    • 导出exports

CommonJS模块规范

在Node中的JavaScript还有一个重要的概念,模块系统。

  • 模块作用域
  • 使用require方法来加载模块
  • 使用exports接口对象来导出模板中的成员

加载require

语法:

var 自定义变量名 = require('模块')

作用:

  • 执行被加载模块中的代码
  • 得到被加载模块中的exports导出接口对象

导出exports

  • Node中是模块作用域,默认文件中所有的成员只在当前模块有效
  • 对于希望可以被其他模块访问到的成员,我们需要把这些公开的成员都挂载到exports接口对象中就可以了
    导出多个成员(必须在对象中):
exports.a = 123;
exports.b = function(){
    console.log('bbb')
};
exports.c = {
    foo:"bar"
};
exports.d = 'hello';

导出单个成员(拿到的就是函数,字符串):

module.exports = 'hello';

以下情况会覆盖:

module.exports = 'hello';
//后者会覆盖前者
module.exports = function add(x,y) {
    return x+y;
}

也可以通过以下方法来导出多个成员:

module.exports = {
    foo = 'hello',
    add:function(){
        return x+y;
    }
};

模块原理

exports

exports和module.exports的一个引用:

console.log(exports === module.exports);	//true

exports.foo = 'bar';

//等价于
module.exports.foo = 'bar';

当给exports重新赋值后,指向变化了,exports!= module.exports.
最终return的是module.exports,无论exports中的成员是什么都没用。

// 真正去使用的时候:
//    导出多个成员:exports.xxx = xxx
//    导出多个成员也可以:module.exports = {
//                        }
//    导出单个成员:module.exports

总结

// 引用服务
var http = require('http');
var fs = require('fs');
// 引用模板
var template = require('art-template');
// 创建服务
var server = http.createServer();
// 公共路径
var wwwDir = 'D:/app/www';
server.on('request', function (req, res) {
    var url = req.url;
    // 读取文件
    fs.readFile('./template-apche.html', function (err, data) {
        if (err) {
            return res.end('404 Not Found');
        }
        fs.readdir(wwwDir, function (err, files) {
            if (err) {
                return res.end('Can not find www Dir.')
            }
            // 使用模板引擎解析替换data中的模板字符串
            // 去xmpTempleteList.html中编写模板语法
            var htmlStr = template.render(data.toString(), { 
                title: 'D:/app/www/ 的索引',
                files:files 
            });
            // 发送响应数据
            res.end(htmlStr);
        })
    })
});
server.listen(3000, function () {
    console.log('running....');
})
1.jQuery中的each 和 原生JavaScript方法forEach的区别:
	提供源头:
    	原生js是es5提供的(不兼容IE8,
        jQuery的each是jQuery第三方库提供的(如果要使用需要用2以下的版本也就是1.版本),它的each方法主要用来遍历jQuery实例对象(伪数组),
        同时也可以做低版本forEach的替代品,jQuery的实例对象不能使用forEach方法,如果想要使用必须转为数组([].slice.call(jQuery实例对象))才能使用
2.模块中导出多个成员和导出单个成员
3.301302的区别:
	301永久重定向,浏览器会记住
    302临时重定向
4.exports和module.exports的区别:
	每个模块中都有一个module对象
    module对象中有一个exports对象
    我们可以把需要导出的成员都挂载到module.exports接口对象中
	也就是`module.exports.xxx = xxx`的方式
    但是每次写太多了就很麻烦,所以Node为了简化代码,就在每一个模块中都提供了一个成员叫`exports`
    `exports === module.exports`结果为true,所以完全可以`exports.xxx = xxx`
    当一个模块需要导出单个成员的时候必须使用`module.exports = xxx`的方式,=,使用`exports = xxx`不管用,因为每个模块最终return的是module.exports,而exports只是module.exports的一个引用,所以`exports`即使重新赋值,也不会影响`module.exports`。
    有一种赋值方式比较特殊:`exports = module.exports`这个用来新建立引用关系的。

require加载规则

  • 核心模块
    • 模块名
  • 第三方模块
    • 模块名
  • 用户自己写的
    • 路径

require的加载规则:

  • 优先从缓存加载
  • 判断模块标识符
    • 核心模块
    • 自己写的模块(路径形式的模块)
    • 第三方模块(node_modules)
      • 第三方模块的标识就是第三方模块的名称(不可能有第三方模块和核心模块的名字一致)
      • npm
        -开发人员可以把写好的框架库发布到npm上
        -使用者通过npm命令来下载
      • 使用方式:var 名称 = require('npm install【下载包】 的包名')
        -node_modules/express/package.json main
        -如果package.json或者main不成立,则查找被选择项:index.js
        -如果以上条件都不满足,则继续进入上一级目录中的node_modules按照上面的规则依次查找,直到当前文件所属此盘根目录都找不到最后报错
// 如果非路径形式的标识
// 路径形式的标识:
    // ./  当前目录 不可省略
    // ../  上一级目录  不可省略
    //  /xxx也就是D:/xxx
    // 带有绝对路径几乎不用(D:/a/foo.js)
// 首位表示的是当前文件模块所属磁盘根目录
// require('./a'); 


// 核心模块
// 核心模块本质也是文件,核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字来加载就可以了
require('fs'); 

// 第三方模块
// 凡是第三方模块都必须通过npm下载(npm i node_modules),使用的时候就可以通过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不存在或者mian指定的入口模块不存在
//      则node会自动找该目录下的index.js
//      也就是说index.js是一个备选项,如果main没有指定,则加载index.js文件
//      
        // 如果条件都不满足则会进入上一级目录进行查找
// 注意:一个项目只有一个node_modules,放在项目根目录中,子目录可以直接调用根目录的文件
var template = require('art-template');

// 注意:我们一个项目有且只有一个 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 而且是存放到项目的根目录

模块标识符中的/和文件操作路径中的/
文件操作路径:

// 咱们所使用的所有文件操作的API都是异步的
// 就像ajax请求一样

// 读取文件
// 文件操作中 ./ 相当于当前模块所处磁盘根目录,可以省略
// ./index.txt    相对于当前目录
// /index.txt    相对于当前目录
// /index.txt   绝对路径,当前文件模块所处根目录
// d:/express/index.txt   绝对路径
fs.readFile('./index.txt',function(err,data){
    if(err){
       return  console.log('读取失败');
    }
    console.log(data.toString());
})

模块操作路径:

// 在模块加载中,相对路径中的./不能省略
// 这里省略了.也是磁盘根目录
require('./index')('hello')

npm

  • node package manage(node包管理器)
  • 通过npm命令安装jQuery包(npm install --save jquery),在安装时加上–save会主动生成说明书文件信息(将安装文件的信息添加到package.json里面)

npm网站

npmjs.com 网站 是用来搜索npm包的或发布包
https://www.npmjs.com/

npm命令行工具

npm是一个命令行工具,只要安装了node就已经安装了npm。

npm也有版本概念,可以通过npm --version来查看npm的版本

升级npm(自己升级自己):

npm install --global npm

常用命令

  • npm init(生成package.json说明书文件)
    • npm init -y(可以跳过向导,快速生成)
  • npm install
    • 一次性把dependencies选项中的依赖项全部安装
    • 简写(npm i)
  • 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 uninstall --help)

解决npm被墙问题
npm存储包文件的服务器在国外,有时候会被墙,速度很慢,所以需要解决这个问题。
安装淘宝的cnpm:

npm install -g cnpm --registry=https://registry.npm.taobao.org;

#在任意目录执行都可以
#--global表示安装到全局,而非当前目录
#--global不能省略,否则不管用
npm install --global cnpm

安装包的时候把以前的npm替换成cnpm。

#走国外的npm服务器下载jQuery包,速度比较慢
npm install jQuery;

#使用cnpm就会通过淘宝的服务器来下载jQuery
cnpm install jQuery;

如果不想安装cnpm又想使用淘宝的服务器来下载:

npm install jquery --registry=https://npm.taobao.org;

但是每次手动加参数就很麻烦,所以我们可以把这个选项加入到配置文件中:

npm config set registry https://npm.taobao.org;

#查看npm配置信息
npm config list;

// 有时淘宝镜像网站不可以,我们则取消
npm config delete registry
npm config set registry https://registry.npmjs.org

只要经过上面的配置命令,则以后所有的npm install都会通过淘宝的服务器来下载

package.json

每一个项目都要有一个package.json文件(包描述文件,就像产品的说明书一样)
这个文件可以通过npm init自动初始化出来


D:\code\node中的模块系统>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (node中的模块系统)
Sorry, name can only contain URL-friendly characters.
package name: (node中的模块系统) cls
version: (1.0.0)
description: 这是一个测试项目
entry point: (main.js)
test command:
git repository:
keywords:
author: xiaochen
license: (ISC)
About to write to D:\code\node中的模块系统\package.json:

{
  "name": "cls",
  "version": "1.0.0",
  "description": "这是一个测试项目",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "xiaochen",
  "license": "ISC"
}


Is this OK? (yes) yes

对于目前来讲,最有用的是dependencies选项,可以用来帮助我们保存第三方包的依赖信息。

如果node_modules删除了也不用担心,只需要在控制面板中npm install就会自动把package.json中的dependencies中所有的依赖项全部都下载回来。

  • 建议每个项目的根目录下都有一个package.json文件
  • 建议执行npm install 包名的时候都加上–save选项,目的是用来保存依赖信息

package.json和package-lock.json

npm i --global npm升级
npm 5以前是不会有package-lock.json这个文件

npm5以后才加入这个文件

当你安装包的时候,npm都会生成或者更新package-lock.json这个文件

  • npm5以后的版本安装都不要加–save参数,它会自动保存依赖信息
  • 当你安装包的时候,会自动创建或者更新package-lock.json文件
  • package-lock.json这个文件会包含node_modules中所有包的信息(版本,下载地址。。。)
    • 这样的话重新npm install的时候速度就可以提升
  • 从文件来看,有一个lock称之为锁
    • 这个lock使用来锁版本的
    • 如果项目依赖了1.1.1版本
    • 如果你重新install其实会下载最细版本,而不是1.1.1
    • package-lock.json的另外一个作用就是锁定版本号,防止自动升级

path路径操作模块
参考文档:https://nodejs.org/docs/latest-v13.x/api/path.html

  • path.basename:获取路径的文件名,默认包含扩展名
  • path.dirname:获取路径中的目录部分
  • path.extname:获取一个路径中的扩展名部分
  • path.parse:把路径转换为对象
    • root:根路径
    • dir:目录
    • base:包含后缀名的文件名
    • ext:后缀名
    • name:不包含后缀名的文件名
  • path.join:拼接路径
  • path.isAbsolute:判断一个路径是否为绝对路径

path路径操作模块

参考文档:https://nodejs.org/dist/latest-v14.x/docs/api/path.html

  • path.basename:获取路径的文件名,默认包含扩展名
  • path.dirname:获取路径中的目录部分
  • path.extname:获取一个路径中的扩展名部分
  • path.parse:把路径转换为对象
    • root:根路径
    • dir:目录
    • base:包含后缀名的文件名
    • ext:后缀名
    • name:不包含后缀名的文件名
  • path.join:拼接路径
  • path.isAbsolute:判断一个路径是否为绝对路径

Node的其他成员

在每个模块中,除了require,exports等模块相关的API之外,还有两个特殊的成员:

  • __dirname,是一个成员,可以用来动态获取当前文件模块所属目录的绝对路径

  • __filename,可以用来动态获取当前文件的绝对路径(包含文件名)

  • __dirname和filename是不受执行node命令所属路径影响的

在文件操作中,使用相对路径是不可靠的,因为node中文件操作的路径被设计为相对于执行node命令所处的路径。(不是bug,有使用场景)

所以为了解决这个问题,只需要把相对路径变为绝对路径(绝对路径不受任何影响)就可以了。

就可以使用__dirname或者__filename来帮助我们解决这个问题

在拼接路径的过程中,为了避免手动拼接带来的一些低级错误,推荐使用path.join()来辅助拼接

var fs = require('fs');
var path = require('path');

// console.log(__dirname + 'a.txt');
// path.join方法会将文件操作中的相对路径都统一的转为动态的绝对路径
fs.readFile(path.join(__dirname + '/a.txt'),'utf8',function(err,data){
	if(err){
		throw err
	}
	console.log(data);
});

补充:模块中的路径标识和这里的路径没关系,不受影响(就是相对于文件模块)

注意:
模块中的路径标识和文件操作中的相对路径标识不一致
模块中的路径标识就是相对于当前文件模块,不受node命令所处路径影响

4.web服务器开发

4.1ip地址和端口号

  • ip 地址定位计算机
  • 端口号定位具体的应用程序
    在这里插入图片描述
    注意:可以同时开启多个服务,但一定要确保不同的服务占用的端口号不一致,说的通俗易懂点儿,在一台计算机上一次一个端口号只能开启一个服务。

服务器中配置的Content-Type

不同的文件资源对应的Content-Type格式可以去网站查找

11-http.js
// require
// 端口号

var http = require('http')

var server = http.createServer()

server.on('request', function(req, res){
	// 服务器默认发送的数据,其实是utf-8编码内容
	// 但是浏览器不知道你是utf-8编码的内容
中文操作系统默认是gbk	// 浏览器在不知道服务器响应内容的编码的情况下会按照当前操作系统的默认编码区解析
	// 
	// 解决办法就是正确的告诉浏览器我给你发送的内容是什么编码的
	// 在http协议中, Content-Type就是用来开告知对方我给你发送的数据内容是什么类型
	// res.setHeader('Content-Type', 'text/plain; charset=utf-8')
	// res.end('hello 世界')
  var url = req.url

  if (url === '/plain') {
    // text/plain 就是普通文本
    res.setHeader('Content-Type', 'text/plain; charset=utf-8')
    res.end('hello 世界')
  } else if (url === '/html') {
    // 如果你发送的是 html 格式的字符串,则也要告诉浏览器我给你发送是 text/html 格式的内容
    res.setHeader('Content-Type', 'text/html; charset=utf-8')
    res.end('<p>hello html <a href="">点我</a></p>')
  }
})

server.listen(5000, function(){
	console.log('Server is runnning...')
})

不同的文件资源类型的content-type

// 1. 结合 fs 发送文件中的数据
// 2. Content-Type
//    http://tool.oschina.net/commons
//    不同的资源对应的 Content-Type 是不一样的
//    图片不需要指定编码
//    一般只为字符数据才指定编码

var http = require('http')
var fs = require('fs')

var server = http.createServer()

server.on('request', function (req, res) {
  // / index.html
  var url = req.url

  if (url === '/') {
    // 肯定不这么干
    // res.end('<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Document</title></head><body><h1>首页</h1></body>/html>')

    // 我们要发送的还是在文件中的内容
    fs.readFile('./resource/index.html', function (err, data) {
      if (err) {
        res.setHeader('Content-Type', 'text/plain; charset=utf-8')
        res.end('文件读取失败,请稍后重试!')
      } else {
        // data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串
        // res.end() 支持两种数据类型,一种是二进制,一种是字符串
        res.setHeader('Content-Type', 'text/html; charset=utf-8')
        res.end(data)
      }
    })
  } else if (url === '/xiaoming') {
    // url:统一资源定位符
    // 一个 url 最终其实是要对应到一个资源的
    fs.readFile('./resource/ab2.jpg', function (err, data) {
      if (err) {
        res.setHeader('Content-Type', 'text/plain; charset=utf-8')
        res.end('文件读取失败,请稍后重试!')
      } else {
        // data 默认是二进制数据,可以通过 .toString 转为咱们能识别的字符串
        // res.end() 支持两种数据类型,一种是二进制,一种是字符串
        // 图片就不需要指定编码了,因为我们常说的编码一般指的是:字符编码
        res.setHeader('Content-Type', 'image/jpeg')
        res.end(data)
      }
    })
  }
})

server.listen(3000, function () {
  console.log('Server is running...')
})

第一次总结

  • 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

服务端渲染

像Apache一样

var http = require('http')
var fs = require('fs')

var server = http.createServer()

var wwwDir = 'D:/Movie/www'

server.on('request', function (req, res) {
  var url = req.url
  // / index.html
  // /a.txt wwwDir + /a.txt
  // /apple/login.html wwwDir + /apple/login.html
  // /img/ab1.jpg wwwDir + /img/ab1.jpg
  

  var filePath = '/index.html'
  if (url !== '/') {
    filePath = url
  }

  fs.readFile(wwwDir + filePath, function (err, data) {
    if (err) {
      return res.end('404 Not Found.')
    }
    res.end(data)
  })
})

// 3. 绑定端口号,启动服务
server.listen(3000, function () {
  console.log('running...')
})

目录列表

var http = require('http')
var fs = require('fs')

var server = http.createServer()

var wwwDir = 'D:/Movie/www'

server.on('request', function (req, res) {
  var url = req.url
  fs.readFile('./template.html', function (err, data) {
    if (err) {
      return res.end('404 Not Found.')
    }
    // 1. 如何得到 wwwDir 目录列表中的文件名和目录名
    //    fs.readdir
    // 2. 如何将得到的文件名和目录名替换到 template.html 中
    //    2.1 在 template.html 中需要替换的位置预留一个特殊的标记(就像以前使用模板引擎的标记一样)
    //    2.2 根据 files 生成需要的 HTML 内容
    // 只要你做了这两件事儿,那这个问题就解决了
    fs.readdir(wwwDir, function (err, files) {
      if (err) {
        return res.end('Can not find www dir.')
      }

      // 2.1 生成需要替换的内容
      var content = ''
      files.forEach(function (item) {
        // 在 EcmaScript 6 的 ` 字符串中,可以使用 ${} 来引用变量
        content += `
          <tr>
            <td data-value="apple/"><a class="icon dir" href="/D:/Movie/www/apple/">${item}/</a></td>
            <td class="detailsColumn" data-value="0"></td>
            <td class="detailsColumn" data-value="1509589967">2017/11/2 上午10:32:47</td>
          </tr>
        `
      })

      // 2.3 替换
      data = data.toString()
      data = data.replace('^_^', content)

      // 3. 发送解析替换过后的响应数据
      res.end(data)
    })
  })
})
server.listen(3000, function () {
  console.log('running...')
})

node中使用模板引擎

// art-template
// art-template 不仅可以在浏览器使用,也可以在 node 中使用

// 安装:
//    npm install art-template
//    该命令在哪执行就会把包下载到哪里。默认会下载到 node_modules 目录中
//    node_modules 不要改,也不支持改。

// 在 Node 中使用 art-template 模板引擎
// 模板引起最早就是诞生于服务器领域,后来才发展到了前端。
// 
// 1. 安装 npm install art-template
// 2. 在需要使用的文件模块中加载 art-template
//    只需要使用 require 方法加载就可以了:require('art-template')
//    参数中的 art-template 就是你下载的包的名字
//    也就是说你 isntall 的名字是什么,则你 require 中的就是什么
// 3. 查文档,使用模板引擎的 API


var template = require('art-template')
var fs = require('fs')

// 这里不是浏览器
// template('script 标签 id', {对象})

// var tplStr = `
// <!DOCTYPE html>
// <html lang="en">
// <head>
//   <meta charset="UTF-8">
//   <title>Document</title>
// </head>
// <body>
//   <p>大家好,我叫:{{ name }}</p>
//   <p>我今年 {{ age }} 岁了</p>
//   <h1>我来自 {{ province }}</h1>
//   <p>我喜欢:{{each hobbies}} {{ $value }} {{/each}}</p>
// </body>
// </html>
// `

fs.readFile('./tpl.html', function (err, data) {
  if (err) {
    return console.log('读取文件失败了')
  }
  // 默认读取到的 data 是二进制数据
  // 而模板引擎的 render 方法需要接收的是字符串
  // 所以我们在这里需要把 data 二进制数据转为 字符串 才可以给模板引擎使用
  var ret = template.render(data.toString(), {
    name: 'Jack',
    age: 18,
    province: '北京市',
    hobbies: [
      '写代码',
      '唱歌',
      '打游戏'
    ],
    title: '个人信息'
  })

  console.log(ret)
})

服务端渲染示意图
在这里插入图片描述
客户端渲染
在这里插入图片描述

  • 代码风格

  • 无分号

    • (
    • [
    • `
    • 最好前面补分号,避免一些问题
    • 《编写可维护的 JavaScript》
    • 不仅是功能,还要写的漂亮
  • 服务端渲染

    • 说白了就是在服务端使用模板引擎
    • 模板引擎最早诞生于服务端,后来才发展到了前端
  • 服务端渲染和客户端渲染的区别

    • 客户端渲染不利于 SEO 搜索引擎优化
    • 服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的
    • 所以你会发现真正的网站既不是纯异步也不是纯服务端渲染出来的
    • 而是两者结合来做的
    • 例如京东的商品列表就采用的是服务端渲染,目的了为了 SEO 搜索引擎优化
    • 而它的商品评论列表为了用户体验,而且也不需要 SEO 优化,所以采用是客户端渲染

5.一个案例-留言本

feedback
总结:

  • art-template里面用的语法是jQuery吗, each什么的 我晕了 each,forEach, 遍历的全混了

    • art-template 和 jQuery 一毛钱关系都没有
    • each 是 art-template 的模板语法,专属的
    • {{each 数组}}
    • {{ $value }}
  • {{/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
      • 这个问题是矛盾。
  • 网站开发模型

    • 黑盒子、哑巴
    • 写代码让它变得更智能
    • 按照你设计好的套路供用户使用
  • 在 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()
  • 6.Express

    原生的http在某些方面表现不足以应对我们的开发需求,所以就需要使用框架来加快我们的开发效率,框架的目的就是提高效率,让我们的代码高度统一。
    在node中有很多web开发框架。主要学习express

    • http://expressjs.com/,其中主要封装的是http。
    // 1 安装
    // npm init -y     npm i -S express    创建一个app.js文件夹
    // 2 引包
    var express = require('express');
    // 3 创建服务器应用程序
    //      也就是原来的http.createServer();
    var app = express();
    
    // 公开指定目录
    // 只要通过这样做了,就可以通过/public/xx的方式来访问public目录中的所有资源
    // 在Express中开放资源就是一个API的事
    app.use('/public/',express.static('/public/'));
    
    //模板引擎在Express中开放模板也是一个API的事
    
    // 当服务器收到get请求 / 的时候,执行回调处理函数
    app.get('/',function(req,res){
        res.send('hello express');
    })
    
    // 相当于server.listen
    app.listen(3000,function(){
        console.log('app is runing at port 3000');
    })
    

    修改完代码自动重启的插件nodemon

    我们这里可以使用一个第三方命令行工具,ndoemon来帮我们解决频繁修改代码重启服务器的问题
    nodemon是一个基于Node.js开发的一个第三方命令行工具,我们使用的时候需要独立安装

    # 在任意目录下执行该命令都可以
    # 也就是说,所有需要 --global 来安装的包都可以在任意目录下执行
    npm install --global nodemon
    

    安装完毕之后,使用:

    node app.js
    
    # 使用 nodemon
    nodemon app.js
    

    只要通过nodemon启动的服务,则它会监视你的文件变化,当文件发生变化的时候,自动帮你重启服务器。

    基本路由

    路由:

    • 请求方法
    • 请求路径
    • 请求处理函数
      get:
    //当你以get方法请求/的时候,执行对应的处理函数
    app.get('/',function(req,res){
        res.send('hello world');
    })
    
    

    post:

    //当你以post方法请求/的时候,执行对应的处理函数
    app.post('/',function(req,res){
        res.send('hello world');
    })
    
    

    访问静态资源

    // app.use不仅仅是用来处理静态资源的,还可以做很多工作(body-parser的配置)
    app.use(express.static('public'));
    
    app.use(express.static('files'));
    
    app.use('/stataic',express.static('public'));
    
    
    // 引入express
    var express = require('express');
    
    // 创建app
    var app = express();
    
    // 开放静态资源
    // 1.当以/public/开头的时候,去./public/目录中找对应资源
    // 访问:http://127.0.0.1:3000/public/login.html
    app.use('/public/',express.static('./public/')); 
    
    // 2.当省略第一个参数的时候,可以通过省略/public的方式来访问
    // 访问:http://127.0.0.1:3000/login.html
    // app.use(express.static('./public/'));   
    
    // 3.访问:http://127.0.0.1:3000/a/login.html
    // a相当于public的别名
    // app.use('/a/',express.static('./public/')); 
    
    //  
    app.get('/',function(req,res){
        res.end('hello world');
    });
    
    app.listen(3000,function(){
        console.log('express app is runing...');
    });
    
    

    在Express中配置使用art-templete模板引擎

    基本使用

    安装:

    npm install --save art-template
    npm install --save express-art-template
    
    //两个一起安装
    npm i --save art-template 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 目录中
    

    如果希望修改默认的views视图渲染存储目录,可以:

    // 第一个参数views千万不要写错
    app.set('views',目录路径);
    

    子模板和模板的继承(模板引擎高级语法)【include,extend,block】

    模板页:

    <!DOCTYPE html>
    <html lang="zh">
    <head>
    	<meta charset="UTF-8">
    	<meta name="viewport" content="width=device-width, initial-scale=1.0">
    	<meta http-equiv="X-UA-Compatible" content="ie=edge">
    	<title>模板页</title>
    	<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css"/>
    	{{ block 'head' }}{{ /block }}
    </head>
    <body>
    	<!-- 通过include导入公共部分 -->
    	{{include './header.html'}}
    	
    	<!-- 留一个位置 让别的内容去填充 -->
    	{{ block  'content' }}
    		<h1>默认内容</h1>
    	{{ /block }}
    	
    	<!-- 通过include导入公共部分 -->
    	{{include './footer.html'}}
    	
    	<!-- 公共样式 -->
    	<script src="/node_modules/jquery/dist/jquery.js" ></script>
    	<script src="/node_modules/bootstrap/dist/js/bootstrap.js" ></script>
    	{{ block 'script' }}{{ /block }}
    </body>
    </html>
    
    

    模板的继承:

    ​ header页面:

    <div id="">
    	<h1>公共的头部</h1>
    </div>
    
    

    ​ footer页面:

    <div id="">
    	<h1>公共的底部</h1>
    </div>
    
    

    模板页的使用:

    <!-- 继承(extend:延伸,扩展)模板也layout.html -->
    <!-- 把layout.html页面的内容都拿进来作为index.html页面的内容 -->
    {{extend './layout.html'}}
    
    <!-- 向模板页面填充新的数据 -->
    <!-- 填充后就会替换掉layout页面content中的数据 -->
    <!-- style样式方面的内容 -->
    {{ block 'head' }}
    	<style type="text/css">
    		body{
    			background-color: skyblue;
    		}
    	</style>
    {{ /block }}
    {{ block 'content' }}
    	<div id="">
    		<h1>Index页面的内容</h1>
    	</div>
    {{ /block }}
    <!-- js部分的内容 -->
    {{ block 'script' }}
    	<script type="text/javascript">
    		
    	</script>
    {{ /block }}
    
    

    在Express中获取表单请求数据

    获取get请求数据:
    Express内置了一个api,可以直接通过req.query来获取数据

    // 通过requery方法获取用户输入的数据
    // req.query只能拿到get请求的数据
     var comment = req.query;
    

    获取post请求数据:
    在Express中没有内置获取表单post请求体的api,这里我们需要使用一个第三方包body-parser来获取数据。

    安装:

    npm install --save body-parser;
    

    配置:

    // 配置解析表单 POST 请求体插件(注意:一定要在 app.use(router) 之前 )

    var express = require('express')
    // 引包
    var bodyParser = require('body-parser')
    
    var app = express()
    
    // 配置body-parser
    // 只要加入这个配置,则在req请求对象上会多出来一个属性:body
    // 也就是说可以直接通过req.body来获取表单post请求数据
    // parse application/x-www-form-urlencoded
    app.use(bodyParser.urlencoded({ extended: false }))
    
    // parse application/json
    app.use(bodyParser.json())
    

    使用:

    app.use(function (req, res) {
      res.setHeader('Content-Type', 'text/plain')
      res.write('you posted:\n')
      // 可以通过req.body来获取表单请求数据
      res.end(JSON.stringify(req.body, null, 2))
    })
    

    在Express中配置使用express-session插件操作

    参考文档:https://github.com/expressjs/session
    安装:

    npm install express-session
    

    配置:

    //该插件会为req请求对象添加一个成员:req.session默认是一个对象
    //这是最简单的配置方式
    //Session是基于Cookie实现的
    app.use(session({
      //配置加密字符串,他会在原有的基础上和字符串拼接起来去加密
      //目的是为了增加安全性,防止客户端恶意伪造
      secret: 'keyboard cat',
      resave: false,
      saveUninitialized: true,//无论是否适用Session,都默认直接分配一把钥匙
      cookie: { secure: true }
    }))
    

    使用:

    // 读
    //添加Session数据
    //session就是一个对象
    req.session.foo = 'bar';
    
    //写
    //获取session数据
    req.session.foo
    
    //删
    req.session.foo = null;
    delete req.session.foo
    

    提示:

    默认Session数据时内存储数据,服务器一旦重启,真正的生产环境会把Session进行持久化存储。

    案例-学生管理系统

    // 创建一个名为crud的文件夹,在该文件夹路径下,使用
    // npm init -y
    // npm i -S exxpress
    // npm install --save art-template express-art-template
    // npm i -S bootstrap@3.3.7
    // 将所有需要的环境配置好
    // 创建一个名为app.js和db.json的文件,名为public和views的文件夹,在public文件夹下创建js css img文件夹,views文件中放art-template模板
    // 找一个网页模板,如https://v3.bootcss.com/examples/dashboard/  打开网页源码,直接ctrl+c,在views文件下创建一个index.html文件,ctrl+v把源码复制进去(这是标准流程,修改后的index.html代码会贴在下方)
    // 具体修改后的app.js,db.json和index.html如下
    

    app.js

    var express = require('express')
    var fs = require('fs')
    var app = express()
    
    app.engine('html', require('express-art-template'))
    
    app.use('/node_modules/', express.static('./node_modules/'))
    app.use('/public/', express.static('./public/'))
    
    app.get('/', function (req, res) {
      // readFile 的第二个参数是可选的,传入 utf8就是告诉它把读取到的文件直接按照utf-8编码 转成我们能认识的字符
      // 除了这样的转换之外,也可以通过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.listen(3000, function () {
      console.log('running 3000...')
    })
    
    

    db.json

    {
    	"students": [
    		{"id": 1, "name": "张三", "gender": 0, "age": 18, "hobbies": "吃饭,睡觉,打豆豆,lol"},
    		{"id": 2, "name": "张三", "gender": 0, "age": 18, "hobbies": "吃饭,睡觉,打豆豆,lol"},
    		{"id": 3, "name": "张三", "gender": 0, "age": 18, "hobbies": "吃饭,睡觉,打豆豆,lol"},
    		{"id": 4, "name": "张三", "gender": 0, "age": 18, "hobbies": "吃饭,睡觉,打豆豆,lol"},
    		{"id": 5, "name": "张三", "gender": 0, "age": 18, "hobbies": "吃饭,睡觉,打豆豆,lol"},
    		{"id": 6, "name": "张三", "gender": 0, "age": 18, "hobbies": "吃饭,睡觉,打豆豆,lol"}
    	] 
    }
    

    index.html

    
    <!doctype html>
    <html lang="zh-CN">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
        <meta name="description" content="">
        <meta name="author" content="">
        <link rel="icon" href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.8/favicon.ico">
        <link rel="canonical" href="https://getbootstrap.com/docs/3.4/examples/dashboard/">
    
        <title>Dashboard Template for Bootstrap</title>
    
        <!-- Bootstrap core CSS -->
        <link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    
        <!-- Custom styles for this template -->
        <link href="/public/css/main.css" rel="stylesheet">
      </head>
    
      <body>
    
        <nav class="navbar navbar-inverse navbar-fixed-top">
          <div class="container-fluid">
            <div class="navbar-header">
              <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
              </button>
              <a class="navbar-brand" href="#">Project name</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
              <ul class="nav navbar-nav navbar-right">
                <li><a href="#">Dashboard</a></li>
                <li><a href="#">Settings</a></li>
                <li><a href="#">Profile</a></li>
                <li><a href="#">Help</a></li>
              </ul>
              <form class="navbar-form navbar-right">
                <input type="text" class="form-control" placeholder="Search...">
              </form>
            </div>
          </div>
        </nav>
    
        <div class="container-fluid">
          <div class="row">
            <div class="col-sm-3 col-md-2 sidebar">
              <ul class="nav nav-sidebar">
                <li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li>
                <li><a href="#">Reports</a></li>
                <li><a href="#">Analytics</a></li>
                <li><a href="#">Export</a></li>
              </ul>
              <ul class="nav nav-sidebar">
                <li><a href="">Nav item</a></li>
                <li><a href="">Nav item again</a></li>
                <li><a href="">One more nav</a></li>
                <li><a href="">Another nav item</a></li>
                <li><a href="">More navigation</a></li>
              </ul>
              <ul class="nav nav-sidebar">
                <li><a href="">Nav item again</a></li>
                <li><a href="">One more nav</a></li>
                <li><a href="">Another nav item</a></li>
              </ul>
            </div>
            <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
              <h1 class="page-header">Dashboard</h1>
    
              <div class="row placeholders">
                {{ each fruits }}
                  <div class="col-xs-6 col-sm-3 placeholder">
                  <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200" height="200" class="img-responsive" alt="Generic placeholder thumbnail">
                  <h4>{{ $value }}</h4>
                  <span class="text-muted">Something else</span>
                </div>
                {{ /each }}
              </div>
    
              <h2 class="sub-header">Section title</h2>
              <a class="btn btn-success" href="">添加学生</a>
              <div class="table-responsive">
                <table class="table table-striped">
                  <thead>
                    <tr>
                      <th>#</th>
                      <th>姓名</th>
                      <th>性别</th>
                      <th>年龄</th>
                      <th>爱好</th>
                    </tr>
                  </thead>
                  <tbody>
                    {{ each students }}
                      <tr>
                      <td>{{ $value.id }}</td>
                      <td>{{ $value.name }}</td>
                      <td>{{ $value.gender }}</td>
                      <td>{{ $value.age }}</td>
                      <td>{{ $value.hobbies }}</td>
                    </tr>
                    {{ /each }}
                  </tbody>
                </table>
              </div>
            </div>
          </div>
        </div>
      </body>
    </html>
    
    

    在该项目的根路径下nodemon app.js即可启动项目


    进一步改进案例

    路由设计

    请求方法请求路径get 参数post 参数备注
    GET/studens渲染首页
    GET/students/new渲染添加学生页面
    POST/studens/newname、age、gender、hobbies处理添加学生请求
    GET/students/editid渲染编辑页面
    POST/studens/editid、name、age、gender、hobbies处理编辑请求
    GET/students/deleteid处理删除请求

    提取路由模块

    router.js

    /**
     * router.js 路由模块
     * 职责:
     *   处理路由
     *   根据不同的请求方法+请求路径设置具体的请求处理函数
     * 模块职责要单一,不要乱写
     * 我们划分模块的目的就是为了增强项目代码的可维护性
     * 提升开发效率
     */
    
    var fs = require('fs')
    
    var Student = require('./student')
    var express = require('express')
    // 1.创建一个路由容器
    var router = express.Router()
    
    // 把路由挂载到router路由容器中
    	  // readFile 的第二个参数是可选的,传入 utf8就是告诉它把读取到的文件直接按照utf-8编码 转成我们能认识的字符
    	  // 除了这样的转换之外,也可以通过data.toString() 的方式
    router.get('/students', function (req, res) {
    	Student.find(function(err, students){
    	  	if (err) {
    	  		return res.status(500).send('Server error')
    	  	}
    	  	// 从文件中读取到的数一定是字符串
    	  	// 所以这里一定要手动转成对象
    	  	res.render('index.html', {
    	  	fruits: [
    	  		'苹果',
    	  		'香蕉',
    	  		'橘子',
    	  		'梨'
    	  	],
    	  	students: students
    	  })
    	})
    })
    // router.get('/students', function (req, res) {
    // 	  // readFile 的第二个参数是可选的,传入 utf8就是告诉它把读取到的文件直接按照utf-8编码 转成我们能认识的字符
    // 	  // 除了这样的转换之外,也可以通过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
    // 	  })
    // 	  })
    // })
    router.get('/students/new', function(req, res){
    	res.render('new.html')
    })
    
    router.post('/students/new', function(req, res){
    	// 1.获取表单数据
    	// 2.处理
    	// 将数据保存到 db.json 文件中用以持久化
    	// 3.发送响应
    	// 先读取出来,转成对象
    	// 然后往对象中 push 数据
    	// 然后把对象转成字符串
    	// 然后把字符串再次写入文件
    	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把学生信息查出来
      //   使用模板引擎渲染页面
      Student.findById(parseInt(req.query.id), function(err, student){
      	if (err) {
    		return res.status(500).send('Server error')
    	}
    	res.render('edit.html', {
      	student: student
      })
      })
    })
    /*
    *渲染编辑学生页面
    */
    router.post('/students/edit', function(req, res){
      // 1.获取表单数据
      // req.body
      // 2.更新
      // Student.update()
      // 3.发送响应
      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
    

    设计操作数据的API文件模块

    es6中的find和findIndex:

    find接受一个方法作为参数,方法内部返回一个条件

    find会便利所有的元素,执行你给定的带有条件返回值的函数

    符合该条件的元素会作为find方法的返回值

    如果遍历结束还没有符合该条件的元素,则返回undefined

    // EcmaScript 6 对数组新增了很多方法
    //    find
    //    findIndex
    
    // find 接收一个方法作为参数,方法内部返回一个条件
    // find 会遍历所有的元素,执行你给定的带有条件返回值的函数
    // 符合该条件的元素会作为 find 方法的返回值
    // 如果遍历结束还没有符合该条件的元素,则返回 undefined
    
    var users = [
      {id: 1, name: '张三'},
      {id: 2, name: '张三'},
      {id: 3, name: '张三'},
      {id: 4, name: '张三'}
    ]
    
    Array.prototype.myFind = function (conditionFunc) {
      // var conditionFunc = function (item, index) { return item.id === 4 }
      for (var i = 0; i < this.length; i++) {
        if (conditionFunc(this[i], i)) {
          return this[i]
        }
      }
    }
    
    var ret = users.myFind(function (item, index) {
      return item.id === 2
    })
    
    console.log(ret)
    

    student.js

    /**
    *职责:操作文件中的数据,只处理数据,不关心业务
    */
    var fs = require('fs')
    var dbPath = './db.json'
    
    
    /**
    * 获取所有学生列表
    * callback中的参数
    *	第一个参数是 err
    *		成功是null
    *		错误是 错误对象
    * 	第二个参数是 结果
    *		成功是 数组
    *		错误是undefined
    */
    
    exports.find = function(callback) {
    	fs.readFile(dbPath, 'utf8', function(err, data){
    		if (err) {
    			return callback(err)
    		}
    		callback(null, JSON.parse(data).students)
    	})
    }
    /**
    * 根据id获取学生信息对象
    * @parm {number} id  学生id
    * @parm {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)
    	})
    }
    
    
    /**
    *添加保存学生
    */
    
    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
    	// 你要修改谁,就需要把谁找出来
    	// EcmaScript 6中的一个数组方阿飞: find
    	// 需要接受一个函数作为参数
    	// 当某个遍历项符合 item.id === student.id 条件的时候,find会终止遍历,同时返回
    
    	// 把id统一转换为数字类型
    	student.id = parseInt(student.id)
    
    	var stu = students.find(function(item){
    		return item.id === parseInt(student.id)
    	})
    	// 遍历拷贝对象
    	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)
    	})
    
    	// 根据下标从数组中删除对应的学生对象
    	students.splice(deleteId, 1)
    
    	// 把对象数据转换为字符串
    	var fileData = JSON.stringify({
    		students: students
    	})
    	fs.writeFile(dbPath, fileData, function(err){
    		if (err) {
    			// 错误就是把错误对象传递给它
    			return callback(err)
    		}
    		// 成功就没错,所以错误对象是null
    		callback(null)
    	})
      })
    }
    

    前端显示的模板页面

    主页面

    
    <!doctype html>
    <html lang="zh-CN">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
        <meta name="description" content="">
        <meta name="author" content="">
        <link rel="icon" href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.8/favicon.ico">
        <link rel="canonical" href="https://getbootstrap.com/docs/3.4/examples/dashboard/">
    
        <title>Dashboard Template for Bootstrap</title>
    
        <!-- Bootstrap core CSS -->
        <link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    
        <!-- Custom styles for this template -->
        <link href="/public/css/main.css" rel="stylesheet">
      </head>
    
      <body>
    
        <nav class="navbar navbar-inverse navbar-fixed-top">
          <div class="container-fluid">
            <div class="navbar-header">
              <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
              </button>
              <a class="navbar-brand" href="#">Project name</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
              <ul class="nav navbar-nav navbar-right">
                <li><a href="#">Dashboard</a></li>
                <li><a href="#">Settings</a></li>
                <li><a href="#">Profile</a></li>
                <li><a href="#">Help</a></li>
              </ul>
              <form class="navbar-form navbar-right">
                <input type="text" class="form-control" placeholder="Search...">
              </form>
            </div>
          </div>
        </nav>
    
        <div class="container-fluid">
          <div class="row">
            <div class="col-sm-3 col-md-2 sidebar">
              <ul class="nav nav-sidebar">
                <li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li>
                <li><a href="#">Reports</a></li>
                <li><a href="#">Analytics</a></li>
                <li><a href="#">Export</a></li>
              </ul>
              <ul class="nav nav-sidebar">
                <li><a href="">Nav item</a></li>
                <li><a href="">Nav item again</a></li>
                <li><a href="">One more nav</a></li>
                <li><a href="">Another nav item</a></li>
                <li><a href="">More navigation</a></li>
              </ul>
              <ul class="nav nav-sidebar">
                <li><a href="">Nav item again</a></li>
                <li><a href="">One more nav</a></li>
                <li><a href="">Another nav item</a></li>
              </ul>
            </div>
            <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
              <h1 class="page-header">Dashboard</h1>
    
              <div class="row placeholders">
                {{ each fruits }}
                  <div class="col-xs-6 col-sm-3 placeholder">
                  <img src="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" width="200" height="200" class="img-responsive" alt="Generic placeholder thumbnail">
                  <h4>{{ $value }}</h4>
                  <span class="text-muted">Something else</span>
                </div>
                {{ /each }}
              </div>
    
              <h2 class="sub-header">Section title</h2>
              <a class="btn btn-success" href="/students/new">添加学生</a>
              <div class="table-responsive">
                <table class="table table-striped">
                  <thead>
                    <tr>
                      <th>#</th>
                      <th>姓名</th>
                      <th>性别</th>
                      <th>年龄</th>
                      <th>爱好</th>
                      <th>操作</th>
                    </tr>
                  </thead>
                  <tbody>
                    {{ each students }}
                      <tr>
                      <td>{{ $value.id }}</td>
                      <td>{{ $value.name }}</td>
                      <td>{{ $value.gender }}</td>
                      <td>{{ $value.age }}</td>
                      <td>{{ $value.hobbies }}</td>
                       <td>
                         <a href="/students/edit?id={{ $value.id }}">编辑</a>
                         <a href="/students/delete?id={{ $value.id }}">删除</a>
                       </td>
                    </tr>
                    {{ /each }}
                  </tbody>
                </table>
              </div>
            </div>
          </div>
        </div>
      </body>
    </html>
    
    

    添加学生的页面

    
    <!doctype html>
    <html lang="zh-CN">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
        <meta name="description" content="">
        <meta name="author" content="">
        <link rel="icon" href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.8/favicon.ico">
        <link rel="canonical" href="https://getbootstrap.com/docs/3.4/examples/dashboard/">
    
        <title>Dashboard Template for Bootstrap</title>
    
        <!-- Bootstrap core CSS -->
        <link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    
        <!-- Custom styles for this template -->
        <link href="/public/css/main.css" rel="stylesheet">
      </head>
    
      <body>
    
        <nav class="navbar navbar-inverse navbar-fixed-top">
          <div class="container-fluid">
            <div class="navbar-header">
              <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
              </button>
              <a class="navbar-brand" href="#">Project name</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
              <ul class="nav navbar-nav navbar-right">
                <li><a href="#">Dashboard</a></li>
                <li><a href="#">Settings</a></li>
                <li><a href="#">Profile</a></li>
                <li><a href="#">Help</a></li>
              </ul>
              <form class="navbar-form navbar-right">
                <input type="text" class="form-control" placeholder="Search...">
              </form>
            </div>
          </div>
        </nav>
    
        <div class="container-fluid">
          <div class="row">
            <div class="col-sm-3 col-md-2 sidebar">
              <ul class="nav nav-sidebar">
                <li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li>
                <li><a href="#">Reports</a></li>
                <li><a href="#">Analytics</a></li>
                <li><a href="#">Export</a></li>
              </ul>
              <ul class="nav nav-sidebar">
                <li><a href="">Nav item</a></li>
                <li><a href="">Nav item again</a></li>
                <li><a href="">One more nav</a></li>
                <li><a href="">Another nav item</a></li>
                <li><a href="">More navigation</a></li>
              </ul>
              <ul class="nav nav-sidebar">
                <li><a href="">Nav item again</a></li>
                <li><a href="">One more nav</a></li>
                <li><a href="">Another nav item</a></li>
              </ul>
            </div>
            <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
              <h2 class="sub-header">添加学生</h2>
              <div class="table-responsive">
                <form action="/students/new" method="post">
                  <div class="form-group">
                    <label for="exampleInputEmail1">姓名</label>
                    <input type="text" class="form-control" id="" name="name" placeholder="Email">
                  </div>
                  <div class="form-group">
                    <label for="exampleInputPassword1">性别</label>
                    <div>
                      <label class="radio-inline">
                        <input type="radio" name="gender" id="inlineRadio1" value="option1"></label>
                      <label class="radio-inline">
                        <input type="radio" name="gender" id="inlineRadio2" value="option2"></label>
                    </div>
                  </div>
                  <div class="form-group">
                    <label for="">年龄</label>
                    <input class="form-control" type="number" id="" name="age">
                  </div>
                  <div class="form-group">
                    <label for="">爱好</label>
                    <input class="form-control" type="text" id="" name="hobbies">
                  </div>
                  <button type="submit" class="btn btn-default">Submit</button>
                </form>
              </div>
            </div>
          </div>
        </div>
      </body>
    </html>
    
    

    编辑学生信息的页面

    
    <!doctype html>
    <html lang="zh-CN">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
        <meta name="description" content="">
        <meta name="author" content="">
        <link rel="icon" href="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.8/favicon.ico">
        <link rel="canonical" href="https://getbootstrap.com/docs/3.4/examples/dashboard/">
    
        <title>Dashboard Template for Bootstrap</title>
    
        <!-- Bootstrap core CSS -->
        <link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
    
        <!-- Custom styles for this template -->
        <link href="/public/css/main.css" rel="stylesheet">
      </head>
    
      <body>
    
        <nav class="navbar navbar-inverse navbar-fixed-top">
          <div class="container-fluid">
            <div class="navbar-header">
              <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
              </button>
              <a class="navbar-brand" href="#">Project name</a>
            </div>
            <div id="navbar" class="navbar-collapse collapse">
              <ul class="nav navbar-nav navbar-right">
                <li><a href="#">Dashboard</a></li>
                <li><a href="#">Settings</a></li>
                <li><a href="#">Profile</a></li>
                <li><a href="#">Help</a></li>
              </ul>
              <form class="navbar-form navbar-right">
                <input type="text" class="form-control" placeholder="Search...">
              </form>
            </div>
          </div>
        </nav>
    
        <div class="container-fluid">
          <div class="row">
            <div class="col-sm-3 col-md-2 sidebar">
              <ul class="nav nav-sidebar">
                <li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li>
                <li><a href="#">Reports</a></li>
                <li><a href="#">Analytics</a></li>
                <li><a href="#">Export</a></li>
              </ul>
              <ul class="nav nav-sidebar">
                <li><a href="">Nav item</a></li>
                <li><a href="">Nav item again</a></li>
                <li><a href="">One more nav</a></li>
                <li><a href="">Another nav item</a></li>
                <li><a href="">More navigation</a></li>
              </ul>
              <ul class="nav nav-sidebar">
                <li><a href="">Nav item again</a></li>
                <li><a href="">One more nav</a></li>
                <li><a href="">Another nav item</a></li>
              </ul>
            </div>
            <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
              <h2 class="sub-header">编辑学生信息</h2>
              <div class="table-responsive">
                <form action="/students/edit" method="post">
                  <!-- 用来放一些不希望被用户看见,但是需要被提交到服务端的数据 -->
                  <input type="hidden" value="{{ student.id }}" name="id">
                  <div class="form-group">
                    <label for="">姓名</label>
                    <input type="text" class="form-control" id="" name="name" placeholder="name" required minlength="2" maxlength="10" value="{{ student.name }}">
                  </div>
                  <div class="form-group">
                    <label for="exampleInputPassword1">性别</label>
                    <div>
                      <label class="radio-inline">
                        <input type="radio" name="gender" id="inlineRadio1" value="option1"></label>
                      <label class="radio-inline">
                        <input type="radio" name="gender" id="inlineRadio2" value="option2"></label>
                    </div>
                  </div>
                  <div class="form-group">
                    <label for="">年龄</label>
                    <input class="form-control" type="number" id="" name="age" required min="1" max="150" value="{{ student.age }}">
                  </div>
                  <div class="form-group">
                    <label for="">爱好</label>
                    <input class="form-control" type="text" id="" name="hobbies" value="{{ student.hobbies }}">
                  </div>
                  <button type="submit" class="btn btn-default">Submit</button>
                </form>
              </div>
            </div>
          </div>
        </div>
      </body>
    </html>
    
    

    步骤

    • 处理模板
    • 配置静态开放资源
    • 配置模板引擎
    • 简单的路由,/studens渲染静态页出来
    • 路由设计
    • 提取路由模块
    • 由于接下来的一系列业务操作都需要处理文件数据,所以我们需要封装Student.js’
    • 先写好student.js文件结构
      • 查询所有学生列别哦的API
      • findById
      • save
      • updateById
      • deleteById
    • 实现具体功能
      • 通过路由收到请求
      • 接受请求中的参数(get,post)
        • req.query
        • req.body
      • 调用数据操作API处理数据
      • 根据操作结果给客户端发送请求
    • 业务功能顺序
      • 列表
      • 添加
      • 编辑
      • 删除

    回调函数

    在这里插入图片描述

    // 使用回调函数可行
    function add(x, y, callback) {
      console.log(1)
      setTimeout(function () {
        var ret = x + y
        callback(ret)
      }, 1000)
    }
    
    add(10, 20, function (ret) {
      console.log(ret)
    })
    

    在这里插入图片描述
    Javascript单线程,事件循环
    注意:凡是需要得到一个函数内部异步操作的结果
    setTimeout
    readFile
    writeFile
    ajax
    这种情况必须通过:回调函数
    基于原生XMLHTTPRequest封装get方法

     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)
        })
    

    模块化

    • PHP 中为什么就可以直接 requireinclude 因为 PHP 当初在设计的时候就加入了这个功能
      • PHP 这门语言天生就支持
      • 模块作用域
      • 可以使用 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
        • 目前的前端情况都是使用很多新技术,然后利用编译器工具打包可以在低版本浏览器运行。
        • 使用新技术的目的就是为了提高效率,增加可维护性

    MongoDB

    关系型和非关系型数据库

    关系型数据库(表就是关系,或者说表与表之间存在关系)。

    • 所有的关系型数据库都需要通过sql语言来操作
    • 所有的关系型数据库在操作之前都需要设计表结构
    • 而且数据表还支持约束
      • 唯一的
      • 主键
      • 默认值
      • 非空

    非关系型数据库

    • 非关系型数据库非常的灵活
    • 有的关系型数据库就是key-value对儿
    • 但MongDB是长得最像关系型数据库的非关系型数据库
      • 数据库 -》 数据库
      • 数据表 -》 集合(数组)
      • 表记录 -》文档对象

    mongoDB数据库的基本概念

    • 一个数据库中可以有多个数据库
    • 一个数据库中可以有多个集合(数组)
    • 一个集合中可以有多个文档(表记录)
    • 文档结构很灵活,没有限制
    • MongoDB很灵活,不需要像MySql一样先创建数据库,表,设计表结构
      • 在这里只需要:当你需要插入数据的时候,只需要你指定往哪个数据库的哪个集合操作就可以了
      • 一切都由MongoDB来帮你自动完成建库建表这件事儿
    {
        qq:{
           user:[
               {},{},{}...
           ]
        }
    }
    
    
    • 也就是说你可以任意的往里面存数据,没有结构性这么一说

    安装

    启动和关闭数据库

    启动:

    mongodb 默认使用执行mongod 命令所处盼复根目录下的/data/db作为自己的数据存储目录
    所以在第一次执行该命令之前先自己手动新建一个 /data/db
    mongod
    

    如果想要修改默认的数据存储目录,可以:

    mongod --dbpath = 数据存储目录路径
    

    停止:

    在开启服务的控制台,直接Ctrl+C;
    或者直接关闭开启服务的控制台。
    

    连接数据库

    另开一个控制台
    连接:

    # 该命令默认连接本机的 MongoDB 服务
    mongo
    

    退出:

    # 在连接状态输入 exit 退出连接
    exit
    

    基本命令

    • show dbs
      • 查看数据库列表(数据库中的所有数据库)
    • db
      • 查看当前连接的数据库
    • use 数据库名称
      • 切换到指定的数据库,(如果没有会新建)
    • show collections
      • 查看当前目录下的所有数据表
    • db.表名.find()
      • 查看表中的详细信息
        在这里插入图片描述

    在Node中如何操作MongoDB数据库

    使用官方的MongoDB包来操作(不建议)

    https://github.com/mongodb/node-mongodb-native
    

    使用第三方包mongoose来操作MongoDB数据库

    第三方包:mongoose基于MongoDB官方的mongodb包再一次做了封装,名字叫mongoose,是WordPress项目团队开发的。
    官网:https://mongoosejs.com/

    1.起步

     npm init -y
     npm i mongoose
    

    小案例

    // 1.引包
    // 注意:安装后才能require使用
    const mongoose = require('mongoose');
    // 2.连接数据库
    // 指定连接数据库后不需要存在,当你插入第一条数据库后会自动创建数据库
    mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true, useUnifiedTopology: true});
    // 创建一个模型
    // 就是在设计数据库
    // MongoDB 是动态的,非常灵活,只需要在代码中设计你的数据库就可以了
    // mongoose 这个包就可以让你的设计编写过程变的非常的简单
    const Cat = mongoose.model('Cat', { name: String });
    for (var i=0; i < 100; i++) {
      // 实例化一个 Cat
      const kitty = new Cat({ name: '喵喵' + i });
      // 持久化保存 kitty 实例
      kitty.save().then(() => console.log('meow'));
    }
    

    学习指南

    设计Scheme 发布Model (创建表)

    // 1.引包
    var mongoose = require('mongoose');
    
    var Schema = mongoose.Shema
    // 2.连接数据库
    // 指定连接数据库后不需要存在,当你插入第一条数据库后会自动创建数据库
    mongoose.connect('mongodb://localhost:27017/test', {useNewUrlParser: true, useUnifiedTopology: true});
    
    // 设计集合结构(表结构)
    // 字段名称就是表结构的属性名称
    // 值
    // 约束的目的是为了保证数据的完整性,不要有脏数据
    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)
    // 4. 当我们有了模型构造函数之后,就可以使用这个构造函数对 users 集合中的数据为所欲为了(增删改查)
    
    

    增加数据

    // **********************
    // #region /新增数据
    // **********************
    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)
      }
    })
    // **********************
    // #endregion /新增数据
    // **********************
    

    查询数据

    查询所有

    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)
      }
    })
    

    条件查询单个

    // 按照条件查询单个,查询出来的数据是一个对象({})
    // 没有条件查询使用findOne方法,查询的是表中的第一条数据
    User.findOne({
    	username: 'xiaoxiao'
    }, 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删除一个:

    User.findByIdAndRemove(id,[options],[callback]);
    

    修改数据

    • 更新所有:
    User.remove(conditions,doc,[options],[callback]);
    
    • 根据指定条件更新一个:
    User.FindOneAndUpdate([conditions],[update],[options],[callback]);
    
    • 根据id更新一个:
    User.findByIdAndUpdate('5a001b23d219eb00c8581184', {
      password: '123'
    }, function (err, ret) {
      if (err) {
        console.log('更新失败')
      } else {
        console.log('更新成功')
      }
    })
    

    使用数据库改造crud的案例

    使用Node操作MySQL数据库

    文档:https://www.npmjs.com/package/mysql
    安装:npm install --save mysql

    var mysql  = require('mysql');
    
    // 1. 创建连接
    var connection = mysql.createConnection({
      host     : 'localhost',
      user     : 'root',
      password : 'root',
      database : 'users'
    });
     
     // 2. 连接数据库 打开冰箱门
    connection.connect();
     
    
     // 3. 执行数据操作 把大象放到冰箱
    connection.query('SELECT * FROM `users`', function (error, results, fields) {
      if (error) throw error;
      console.log('The solution is: ', results);
    });
    // 插入数据
    //  connection.query('INSERT INTO users VALUES(0, "admin", "123456")', function (error, results, fields) {
    //   if (error) throw error;
    //   console.log('The solution is: ', results);
    // });
    
    // 4. 关闭连接 关闭冰箱门
    connection.end();
    

    异步编程

    回调函数

    通过回调嵌套的方式来保证顺序

    var fs = require('fs')
    
    fs.readFile('./data/a.txt', 'utf8', function (err, data) {
      if (err) {
        // return console.log('读取失败')
        // 抛出异常
        //    1. 阻止程序的执行
        //    2. 把错误消息打印到控制台
        throw err
      }
      console.log(data)
      fs.readFile('./data/b.txt', 'utf8', function (err, data) {
        if (err) {
          // return console.log('读取失败')
          // 抛出异常
          //    1. 阻止程序的执行
          //    2. 把错误消息打印到控制台
          throw err
        }
        console.log(data)
        fs.readFile('./data/c.txt', 'utf8', function (err, data) {
          if (err) {
            // return console.log('读取失败')
            // 抛出异常
            //    1. 阻止程序的执行
            //    2. 把错误消息打印到控制台
            throw err
          }
          console.log(data)
        })
      })
    })
    

    Promise

    为了解决以上编码方式带来的问题(回调地狱嵌套),所以在EcmaScript6新增了一个API:Promise
    在这里插入图片描述
    promise基本语法:

    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)
        }
      })
    })
    
    p1
      .then(function (data) {
        console.log(data)
      }, function (err) {
        console.log('读取文件失败了', err)
      })
    

    在这里插入图片描述
    栗子:

    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')
      })
    
    

    封装Promise的api

    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)
      })
    
    

    mongoose所有的API都支持Promise:

    User.findOne({username:'admin'},function(user){
        if(user){
            console.log('用户已存在')
        } else {
            new User({
                 username:'aaa',
                 password:'123',
                 email:'fffff'
            }).save(function(){
                console.log('注册成功');
            })
        }
    })
    
    User.findOne({
        username:'admin'
    })
        .then(function(user){
            if(user){
                // 用户已经存在不能注册
                console.log('用户已存在');
            }
            else{
                // 用户不存在可以注册
                return new User({
                    username:'aaa',
                    password:'123',
                    email:'fffff'
                }).save();
            }
        })
        .then(funciton(ret){
    		console.log('注册成功');
        })
    

    EcmaScript 6

    参考文档:https://es6.ruanyifeng.com/

    案例-blog

    目录结构

    .
    app.js 项目的入口文件
    controllers
    models 存储使用mongoose设计的数据模型
    node_modules 第三方包
    package.json 包描述文件
    package-lock.json 第三方包版本锁定文件(npm5之后才有)
    public 公共静态资源
    routes
    views 存储视图目录

    步骤

    • 创建目录结构
    • 整合静态也-模板页
      • include
      • block
      • extend
    • 设计用户登陆,退出,注册的路由
    • 用户注册
      • 先处理客户端页面的内容(表单控件的name,收集表单数据,发起请求)
      • 服务端
        • 获取从客户端收到的数据
        • 操作数据库
          • 如果有错,发送500告诉客户端服务器错了‘
          • 其他的根据业务发送不同的响应数据
    • 登录
    • 退出

    中间件

    参考文档:http://expressjs.com/en/guide/using-middleware.html
    中间件:把很复杂的事情分割成单个,然后依次有条理的执行。就是一个中间处理环节,有输入,有输出。

    说的通俗易懂点儿,中间件就是一个(从请求到响应调用的方法)方法。

    把数据从请求到响应分步骤来处理,每一个步骤都是一个中间处理环节。

    var http = require('http');
    var url = require('url');
    
    var cookie = require('./expressPtoject/cookie');
    var query = require('./expressPtoject/query');
    var postBody = require('./expressPtoject/post-body');
    
    var server = http.createServer(function(){
    	// 解析请求地址中的get参数
    	// var obj = url.parse(req.url,true);
    	// req.query = obj.query;
    	query(req,res);	//中间件
    	
    	// 解析请求地址中的post参数
    	req.body = {
    		foo:'bar'
    	}
    });
    
    if(req.url === 'xxx'){
    	// 处理请求
    	...
    }
    
    server.listen(3000,function(){
    	console.log('3000 runing...');
    });
    

    同一个请求对象所经过的中间件都是同一个请求对象和响应对象。

    var express = require('express');
    var app = express();
    app.get('/abc',function(req,res,next){
    	// 同一个请求的req和res是一样的,
    	// 可以前面存储下面调用
    	console.log('/abc');
    	// req.foo = 'bar';
    	req.body = {
    		name:'xiaoxiao',
    		age:18
    	}
    	next();
    });
    app.get('/abc',function(req,res,next){
    	// console.log(req.foo);
    	console.log(req.body);
    	console.log('/abc');
    });
    app.listen(3000, function() {
    	console.log('app is running at port 3000.');
    });
    

    中间件的分类:

    应用程序级别的中间件
    万能匹配(不关心任何请求路径和请求方法的中间件):

    app.use(function(req,res,next){
        console.log('Time',Date.now());
        next();
    });
    

    关心请求路径和请求方法的中间件:

    app.use('/a',function(req,res,next){
        console.log('Time',Date.now());
        next();
    });
    

    路由级别的中间件

    严格匹配请求路径和请求方法的中间件

    get:

    app.get('/',function(req,res){
    	res.send('get');
    });
    

    post:

    app.post('/a',function(req,res){
    	res.send('post');
    });
    

    put:

    app.put('/user',function(req,res){
    	res.send('put');
    });
    

    delete:

    app.delete('/delete',function(req,res){
    	res.send('delete');
    });
    

    var express = require('express');
    var app = express();
    
    // 中间件:处理请求,本质就是个函数
    // 在express中,对中间件有几种分类
    
    // 1 不关心任何请求路径和请求方法的中间件
    // 也就是说任何请求都会进入这个中间件
    // 中间件本身是一个方法,该方法接收三个参数
    // Request 请求对象
    // Response 响应对象
    // next 下一个中间件
    // // 全局匹配中间件
    // app.use(function(req, res, next) {
    // 	console.log('1');
    // 	// 当一个请求进入中间件后
    // 	// 如果需要请求另外一个方法则需要使用next()方法
    // 	next();
    // 	// next是一个方法,用来调用下一个中间件
    //  // 注意:next()方法调用下一个方法的时候,也会匹配(不是调用紧挨着的哪一个)
    // });
    // app.use(function(req, res, next) {
    // 	console.log('2');
    // });
    
    // // 2 关心请求路径的中间件
    // // 以/xxx开头的中间件
    // app.use('/a',function(req, res, next) {
    // 	console.log(req.url);
    // });
    
    // 3 严格匹配请求方法和请求路径的中间件
    app.get('/',function(){
    	console.log('/');
    });
    app.post('/a',function(){
    	console.log('/a');
    });
    
    app.listen(3000, function() {
    	console.log('app is running at port 3000.');
    });
    

    错误处理中间件

    app.use(function(err,req,res,next){
        console.error(err,stack);
        res.status(500).send('Something broke');
    });
    

    配置使用404中间件:

    app.use(function(req,res){
        res.render('404.html');
    });
    

    配置全局错误处理中间件:

    app.get('/a', function(req, res, next) {
    	fs.readFile('.a/bc', funtion() {
    		if (err) {
            	// 当调用next()传参后,则直接进入到全局错误处理中间件方法中
            	// 当发生全局错误的时候,我们可以调用next传递错误对象
            	// 然后被全局错误处理中间件匹配到并进行处理
    			next(err);
    		}
    	})
    });
    //全局错误处理中间件
    app.use(function(err,req,res,next){
        res.status(500).json({
            err_code:500,
            message:err.message
        });
    });
    

    内置中间件

    第三方中间件

    参考文档:http://expressjs.com/en/resources/middleware.html

    • body-parser
    • compression
    • cookie-parser
    • mogran
    • response-time
    • server-static
    • session
Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它可以让JavaScript脱离浏览器在服务器端运行。使用Node.js进行Web全栈开发,意味着可以使用JavaScript作为前端和后端的编程语言,从而实现前后端的无缝衔接。Node.js的非阻塞I/O和事件驱动模型使得它在处理大量并发连接时性能优越。 Node.js Web全栈开发通常包括以下几个部分: 1. **后端开发**:利用Node.js平台,开发者可以使用Express.js、Koa.js等框架来创建HTTP服务器、处理请求和响应,并与数据库进行交互。后端主要负责业务逻辑的处理、数据存储和服务器端渲染等功能。 2. **前端开发**:虽然Node.js专注于后端,但开发者经常使用React、Angular或Vue.js等JavaScript框架来构建用户界面。这些框架可以与Node.js配合使用,实现动态的用户界面。 3. **数据库连接**:Node.js可以连接多种数据库,包括但不限于MongoDB、MySQL、PostgreSQL等。使用Mongoose(对于MongoDB)或Sequelize(对于SQL数据库)等ORM(对象关系映射)工具,可以更方便地进行数据的存取操作。 4. **部署**:Node.js应用可以部署在多种平台上,包括传统的Linux服务器、云服务平台如AWS、Azure、Heroku等。Docker容器化技术也常用于Node.js应用的部署,以确保环境的一致性和快速部署。 Node.js的生态系统非常丰富,提供了大量的npm(Node.js包管理器)模块,用于处理各种任务,如身份验证、缓存、会话管理、模板渲染等。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值