我是靠谱客的博主 淡然翅膀,最近开发中收集的这篇文章主要介绍Node.js基础笔记,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Node.js基础笔记

    • 1. NodeJS入门基础
        • 1.1 NodeJS 介绍
            • 1.1.1 NodeJS 是什么
            • 1.1.2 NodeJS 的优势
            • 1.1.3 NodeJS 的技术特性
            • 1.1.4 NodeJS 相关资源
        • 1.2 NodeJS 开发环境的搭建
            • 1.2.1 Node.JS的下载和安装
            • 1.2.2 NodeJS 的运行环境
            • 1.2.3 NodeJS 的基础语法
            • 1.2.4 NodeJS 在 VScode 中运行
        • 1.3 npm 概述
            • 1.3.1 npm 是什么
            • 1.3.2 npm 安装
            • 1.3.3 npm 常用命令
            • 1.3.4 npm 镜像
        • 1.4 全局作用域成员
            • 1.4.1 全局对象1
            • 1.4.2 全局对象2
            • 1.4.3 全局函数
            • 1.4.4 调试程序
            • 1.4.5 全局作用域练习
        • 1.5 NodeJS 工作原理
            • 1.5.1 NodeJS 解决的主要问题
            • 1.5.2 并发问题的相关概念
            • 1.5.3 阻塞问题的来源
            • 1.5.4 解决并发问题的方案
            • 1.5.5 NodeJS 的异步回调和事件轮询
            • 1.5.6 异步回调的原理
            • 1.5.7 回调函数的概念和语法
            • 1.5.8 相关练习
    • 2. NodeJS 模块
        • 2.1 模块概述
        • 2.2 NodeJS的模块标准
        • 2.3 NodeJS 的模块类型
        • 2.4 NodeJS的模块的常用属性
        • 2.5 NodeJS 的模块化开发流程
        • 2.6 定义模块的方式
        • 2.7 模块的加载
        • 2.8 NodeJS 的模块加载规则
        • 2.9 模块练习
    • 3. NodeJS 事件机制
        • 3.1 事件机制概述
        • 3.2 事件应用语法
        • 3.3 事件应用案例
        • 3.4 事件练习
    • 4. NodeJS 文件相关模块
        • 4.1 NodeJS 的核心模块
        • 4.2 NodeJS 常见核心模块
        • 4.3 全局路径变量
        • 4.4 Path 模块常用属性和方法
        • 4.5 fs 模块常用属性和方法
        • 4.6 fs 文件读写及简单错误处理
        • 4.7 fs 模块的同步和异步方法
        • 4.8 fs 模块的同步方法的异常处理机制
        • 4.9 fs 模块练习
        • 4.10 流的概述
        • 4.11 文件流工作原理
        • 4.12 文件流
        • 4.13 文件流读写操作
        • 4.14 文件流的常见方法和事件
        • 4.15 文件流练习
    • 5. Web 服务器的请求和响应
        • 5.1 http 模块
            • 5.1.1 http 协议
            • 5.1.2 http 模块概述
            • 5.1.3 http 模块的服务端方法
            • 5.1.4 http 模块的客户端方法
            • 5.1.5 http 模块练习
        • 5.2 url 模块
            • 5.2.1 url 模块
            • 5.2.2 url 模块常用属性和方法
        • 5.3 Querystring 模块
            • 5.3.1 Querystring 模块
            • 5.3.2 Querystring 模块常用方法
        • 5.4 请求对象
            • 5.4.1 请求对象
            • 5.4.2 get 请求
            • 5.4.3 post 请求
            • 5.4.4 请求对象练习
        • 5.5 响应对象
            • 5.5.1 响应对象
            • 5.5.2 响应状态码
        • 5.6 请求和响应对象的实际应用
            • 5.6.1 服务器静态网页的访问步骤
            • 5.6.2 服务器样式 & 脚本文件等资源的访问步骤
            • 5.6.3 请求响应练习
    • 6. NodeJS 的 EJS 模版
        • 6.1 EJS 模板是什么
        • 6.2 NodeJS 的技术特性
        • 6.3 EJS 模块的安装和使用
        • 6.4 EJS 模版的特殊标签
        • 6.5 EJS 模版练习
    • 7. NodeJS 路由
        • 7.1 路由是什么
        • 7.2 路由的理解
        • 7.3 路由的实现
        • 7.4 路由的封装
        • 7.5 路由练习
    • 8. NodeJS 数据库访问
        • 8.1 NodeJS 访问 MySQL 数据库
            • 8.1.1 访问数据库的步骤
            • 8.1.2 创建数据库连接对象
            • 8.1.3 连接数据库的方法
        • 8.2 NodeJS 对 MySQL 数据库的常见操作
            • 8.2.1 数据查询的方法
            • 8.2.2 将数据传到前端页面
            • 8.2.3 数据库登陆练习
            • 8.2.2 跨域请求的处理
            • 8.2.3 防止注入式攻击
            • 8.2.4 模块化设计中的数据返回方法
            • 8.2.5 数据增删改的方法
            • 8.2.6 数据增删改方法的参数
            • 8.2.7 存储过程的调用
            • 8.2.8 数据库连接池原理
            • 8.2.9 数据库连接池应用语法
    • 9. Express 框架
        • 9.1 Express 概述
            • 9.1.1 Express 是什么
            • 9.1.2 Express 框架的安装
            • 9.1.3 Express 搭建 Web 服务器
            • 9.1.4 Express 路由配置
            • 9.1.5 Express 路由请求方式配置
            • 9.1.6 Express 路由的使用
            • 9.1.7 Express 静态资源的配置
            • 9.1.8 Express 页面模版的配置
            • 9.1.9 Express 参数的获取
            • 9.1.10 Express 参数的获取
        • 9.2 Express 框架的中间件
            • 9.2.1 Express 的中间件
            • 9.2.2 Express 的中间件类型
            • 9.2.3 Express 中间件的加载
        • 9.3 Express MVC 框架
            • 9.3.1 MVC 框架含义
            • 9.3.2 Express MVC 框架的安装启动
            • 9.3.3 Express MVC 框架目录结构
            • 9.3.4 Express MVC 框架添加新路由
        • 5. 简单的 http 服务
        • 6. 根据不同请求路径返回不同数据
        • 7. each 与 forEach
        • 8. 文件操作路径和模块路径
        • 9. 使用 nodemon 自动重启
    • 3. Node 中的 JavaScript
        • 1. 核心模块
        • 2. 简单的模块化
        • 3. 加载与导出
        • 4. 第三方模块
        • 5. http
        • 6. 异步编程
    • 4. Web 服务器开发
        • 1. ip 地址和端口号
        • 2. Content-Type 响应内容类型
        • 3. 发送文件数据
        • 4. 实现 Apache 目录列表渲染
        • 5. 在 Node 中使用模板引擎
        • 6. 统一处理静态资源
        • 7. 客户端渲染与服务端渲染
    • 5. 留言本案例
        • 1. 页面跳转及404处理
        • 2. 渲染评论首页
        • 3. 处理表单get请求
        • 4. 表单提交重定向
    • 6. Node 中的模块系统
        • 1. 什么是模块化
        • 2. CommonJS 模块规范
            • 1. 加载 require
            • 2. 导出 exports
            • 3. 原理解析
            • 4. exports 和 module.exports 的区别
            • 5. require 方法加载规则
        • 3. npm
            • 1. npm 网站
            • 2. npm 命令行工具
            • 3. 常用命令
            • 4. 解决 npm 被墙问题
        • 4. package.json
        • 5. package-lock.json
    • 7. Express
        • 1. Express 起步
            • 1. 安装
            • 2. hello world
            • 3. 基本路由:
            • 4. static-server 静态服务
        • 2. Express 安装 art-template 模版引擎
            • 1. 安装
            • 2. 相关配置
            • 3. 如何使用
        • 3. 在 Express 中获取表单 GET 请求参数
        • 4. 在 Express 获取表单 POST 请求体数据
        • 5. 路由模块的提取
    • 8. Promise
        • 1. Promise 容器概念
        • 2. Promise 基本语法
        • 3. Promise API
        • 4. 封装 Promise API
    • 9. 中间件
        • 1. 应用程序级别中间件
        • 2. 路由级别中间件
        • 3. 错误处理中间件
        • 4. 内置中间件
        • 5. 第三方中间件

1. NodeJS入门基础

1.1 NodeJS 介绍

1.1.1 NodeJS 是什么
  • Node.js® is a JavaScript runtime built on [Chrome's V8 JavaScript engine](https://v8.dev/).

    • Node.js不是一门语言,不是库,不是框架
    • Node.js是一个基于V8引擎的JavaScript运行时环境,可以解析和执行JavaScript代码
    • JavaScript 可以完全脱离浏览器来运行,一切都归功于Node.js
  • Node.js中的JavaScript

    • 没有BOM、DOM
    • ECMAScript
    • 服务器级别的操作API
      • 例如文件读写
      • 网络服务的构建
      • 网络通信
      • HTTP服务器
      • 。。。
  • 构建于Chrome的V8引擎之上

    • 代码只是具有特定格式的字符串
    • 引擎可以认识,解析和执行
    • Google Chrome的V8引擎是目前公认的解析执行JavaScript代码最快
  • Node.js uses an event-driven,non-blocking I/O model that makes it lightweight and efficient

    • event-driven 事件驱动
    • non-blocking I/O model 非阻塞IO模型(异步)
    • lightweight and efficient 轻量和高效
  • Node.js' package ecosystem,npm,is the largest ecosystem of open sourse libraries in the world

    • npm是世界上最大的开源库生态系统
    • 绝大多数JavaScript相关的包都存放在了npm中,为了让开发人员更方便的去下载使用
    • 例如:npm install jquery

在这里插入图片描述

1.1.2 NodeJS 的优势
  • 采用Javascript语法,学习门槛低
  • 具备V8引擎的高性能服务器
  • 超强高并发能力
  • 开发周期短,节省成本
1.1.3 NodeJS 的技术特性
  • Node 可以用来开发服务端应用程序Web 系统
  • Node提供了大量的前端工具集
1.1.4 NodeJS 相关资源
  • 《深入浅出Node.js》
  • 《Node.js权威指南》
  • JavaScript 标准参考教程(alphad): http://javascript.ruanyifeng.com/
  • Node入门:http://www.nodebeginner.org/index-zh-cn.html
  • 官方API文档:https://nodejs.org/dist/latest-v6.x/docs/api/
  • 中文文档(版本比较旧):http://www.nodeclass.com/api/node.html
  • CNODE社区:http://cnodejs.org
  • CNODE-新手入门:http://cnodejs.org/getstart

1.2 NodeJS 开发环境的搭建

1.2.1 Node.JS的下载和安装
  • Node.JS 官网
    • https://nodejs.org/en/download/
  • 下载版本
    • LTS:稳定版,推荐采用
    • CURRENT:最新版
  • 确认 Node 环境是否安装成功
    • 打开命令行,输入node --version
1.2.2 NodeJS 的运行环境
  • 运行命令
    • Node或者node js文件
  • REPL环境
    • Read
    • Eval
    • Print
    • Loop
1.2.3 NodeJS 的基础语法
  • 进入 REPL 环境:node
  • 运行脚本文件:
    • node index.js
    • node path/index.js
  • 查看帮助
    • node --help
1.2.4 NodeJS 在 VScode 中运行
  • 终端模式运行JS脚本
  • 安装code Runner插件运行

1.3 npm 概述

1.3.1 npm 是什么
  • 两层概念
    • 一层含义是 Node 的开放式模块登记和管理系统,亦可以说是一个生态圈,一个社区
    • 另一层含义是 Node 默认的模块管理器,是一个命令行下的软件,用来安装和管理 Node 模块
  • 官方链接: https://www.npmjs.com/
  • 国内加速镜像: https://npm.taobao.org/
1.3.2 npm 安装
  • NPM 不需要单独安装
    • 默认在安装 Node 的时候,会连带一起安装 NPM
  • Node 附带的 NPM 可能不是最新版本,最好用下面的命令,更新到最新版本
    • npm install npm –g
  • 模块文件夹 node_modules
  • 全局配置文件 .npmrc
1.3.3 npm 常用命令
常用命令作用
npm init项目初始化,生成package.json配置文件
npm install全局安装 –g / 局部安装 –save[-dev](-save程序部署运行后还需要用到的包、模块 / -dev程序开发阶段使用到的包、模块)
npm uninstall卸载包
npm root获取包安装路径,-g可以获取全局安装路径
npm list显示当前安装的包以及依赖关系
npm search 包名搜索所有条件的包
npm view 包名 versions显示包所有的版本信息
1.3.4 npm 镜像
  • 由于npm官网下载速度较慢,因此建议采用国内镜像下载
npm install 包名 
--registry=https://registry.npm.taobao.org
  • 采用淘宝提供的cnpm安装包
npm install -g cnpm 
--registry=https://registry.npm.taobao.org
cnpm install 包名  

1.4 全局作用域成员

1.4.1 全局对象1
  • global
    • 类似于客户端 JavaScript 运行环境中的 window 对象
  • console
    • Node 中内置的 console 模块,提供操作控制台的输入输出功能,常见使用方式与客户端类似
console.log('hello node.js!')
var age = 18
console.log(global.age)
1.4.2 全局对象2
  • process
    • 获取当前的 Node 进程信息,一般用于获取环境变量之类的信息
对象作用
process.argv 属性显示 Node 运行程序的参数(argv 是一个数组, 第一个成员描述 node.exe 位置, 第二个参数执行的 js 脚本文件, 可以接收参数)
process.platform 属性显示当前操作系统
process.uptime 方法获取 Node 程序运行开始到当前的时间(秒)
process.exit 方法结束 node 程序的运行
//显示Node运行程序的参数,argv是一个数组,第一个成员描述node.exe位置,第二个参数执行的js脚本文件
console.log(process.argv)  

//platform显示当前操作系统
console.log(process.platform)
for(var i = 0 ; i < 10000000000 ; i++){
    var num = 10
}

//uptime方法,获取Node程序运行开始到当前的时间(秒)
console.log(process.uptime())

//结束node程序的运行
process.exit() 
1.4.3 全局函数
  • setInterval(callback, millisecond)
  • clearInterval(timer)
  • setTimeout(callback, millisecond)
  • clearTimeout(timer)
1.4.4 调试程序
  • 最方便也是最简单的:console.log()
  • 开发工具的调试
    • 安装Vscode的扩展插件Node Debug
    • 开启调试菜单

在这里插入图片描述

1.4.5 全局作用域练习
  • 问题
    • 编写代码,启动node程序时输入用户名和密码,进入程序后验证用户名和密码是否是“admin”和“888”,如果不对时,提醒输入错误,并延时5秒钟关闭程序
var userName = process.argv[2]
var userPwd = process.argv[3]
if(userName != 'admin' || userPwd != '888'){
    console.log('您输入的用户名、密码错误,系统5秒后即将关闭!')
    setTimeout(function(){
        process.exit()
    },5000)
}

1.5 NodeJS 工作原理

1.5.1 NodeJS 解决的主要问题
  • 高并发用户量是web服务器需要解决的关键问题
  • 由于I/O引发的阻塞问题
1.5.2 并发问题的相关概念
  • 同步和异步
    • 同步是指按顺序执行任务的工作模式
    • 异步是指同时执行任务的工作模式
  • 进程和线程
    • 进程是一个运行中的程序
    • 线程是进程的组成部分,独立运行和调度
1.5.3 阻塞问题的来源
  • I/O密集型操作
    • 网络访问
    • 数据库访问
    • 文件读写
  • CPU密集型操作
    • CPU计算

在这里插入图片描述

1.5.4 解决并发问题的方案
  • 同步方案
    • 同步方案会引发阻塞
    • Javascript 是单线程的工作模式
  • 异步线程方案
    • 利用多线程提高用户的接待能力
    • 多线程解决阻塞问题的不足
      • 系统资源耗费严重
      • 执行效率未必高于单线程
      • 并发量大时,还是会出现阻塞现象
  • Node.js 异步方案
    • 多隆总管
    • 利用异步回调和事件驱动机制解决大并发量问题
1.5.5 NodeJS 的异步回调和事件轮询

在这里插入图片描述

1.5.6 异步回调的原理
  • 异步回调的原理
    • 利用回调函数的特性
    • I/O阻塞的函数作为主函数
    • I/O结束后的处理函数作为回调函数

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

function eat(food){
    console.log('我是张三!')
    food()
}
function food1(){
    console.log('我要吃火锅!')
}
function food2(){
    console.log('我要吃西餐!')
}
//eat是主函数,内部的参数就是回调函数
eat(food1)
eat(food2)
var fs = require('fs')
//IO操作异步读取方法作为主函数,后续处理的方法作为回调函数
fs.readFile('products.txt',function(err,result){
    console.log('错误内容:',err)
    console.log('文件读取完成!内容:',result)
})
console.log('文件读取后续操作。。。。。。。')
1.5.7 回调函数的概念和语法
  • 回调函数就是作为一个函数参数的函数
  • 回调函数是在主函数调用后再执行的
  • Node回调函数的设计规范
    • 回调函数作为主函数的最后一个参数
    • 回调函数的第一个参数是错误对象,错误优先
  • 回调函数的缺点
    • 回调地狱
    • 不便于理解、维护
# 回调地狱
do1(function() {
  do2(function() {
    do3(function() {
      do4(function() {
        do5(function() {
          do6()
        }); 
	 console.log(‘xxxx’)
      });
    });
  });
});
1.5.8 相关练习
  • 题目1
    • 在Npm官网上下载anywhere包,查看帮助,实现本地服务器功能,能以服务器方式打开网页
## Installation
npm install anywhere -g

## Execution
$ anywhere
// or with port
$ anywhere -p 8000
// or start it but silent(don't open browser)
$ anywhere -s
// or with hostname
$ anywhere -h localhost -p 8888
// or with folder
$ anywhere -d ~/git/anywhere
// or enable html5 history
$ anywhere -f /index.html

## Help
$ anywhere --help
Usage:
  anywhere --help // print help information
  anywhere // 8000 as default port, current folder as root
  anywhere 8888 // 8888 as port
  anywhere -p 8989 // 8989 as port
  anywhere -s // don't open browser
  anywhere -h localhost // localhost as hostname
  anywhere -d /home // /home as root
  anywhere -f /index.html  // Enable html5 history,the index is /index.html

## Visit
http://localhost:8000
  • 题目2
    • 实现在主机服务器开机1小时后关闭应用程序
var os = require('os')
console.log(process.uptime())       //获取程序运行时间
console.log(os.uptime())            //获取服务器开机时间

2. NodeJS 模块

2.1 模块概述

  • Node.js 提供了一个简单的模块系统,让 Node 文件可以相互调用
  • 模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的
  • 模块具有独立的作用域相互之间不冲突

在这里插入图片描述

2.2 NodeJS的模块标准

  • CommonJS 是第一套服务器端约定的模块标准
    • 用于约定模块代码应该是怎样的一种结构,以及怎样使用
  • CommonJS模块规范是同步方式加载模块,只适用于服务器端
  • 其他模块标准
    • AMD 模块标准(适用于浏览器端)
    • ES6 模块标准(适用于浏览器端)

2.3 NodeJS 的模块类型

  • 核心模块
    • Node提供的API
    • 例如fs,http,url,path
  • 自定义模块
    • 自定义的模块 js 文件
  • 第三方模块
    • 社区或者个人开发的模块 js 文件

2.4 NodeJS的模块的常用属性

  • module.id
    • 模块的识别符
    • 通常是带有绝对路径的模块文件名
  • module.filename
    • 模块定义的文件的绝对路径
  • module.exports
    • 表示模块对外输出的值

2.5 NodeJS 的模块化开发流程

在这里插入图片描述

2.6 定义模块的方式

  • 方式一
    • Node.js 提供了模块公开的接口exports可以被外部调用
exports.name='tom';
exports.age=12;
exports.display=function(){
    console.log(I am a person');
};
  • 方式二
    • 使用 module.exports 代替 exports
var user = {
	name:'tom';
    age:12;
	display:function(){
   		console.log(I am a person');
   	};
module.exports = user;

2.7 模块的加载

  • Node 使用 CommonJS 模块规范,内置的 require 函数用于加载模块文件
  • require 的基本功能
    • 读入并执行一个 JavaScript 文件
    • 返回该模块的 exports 对象
    • 如果没有发现指定模块,会报错

在这里插入图片描述

//加载模块
var person1 = require('./1_myModule')
var person2 = require('./2_myModule')
//使用模块
person1.showInfo()
person2.showInfo()

2.8 NodeJS 的模块加载规则

  • 规则一
    • 通过 ./ 开头
      • 则按照相对路径从当前文件所在文件夹开始寻找模块
    • 通过 ../ 开头
      • 则按照相对路径从当前文件所在上级文件夹开始寻找模块
    • 直接使用模块名
      • 加载的是核心模块
      • 加载的是保存在当前及往上各级node_modules文件夹中的模块
  • 规则二
    • 首先匹配同名的 js 文件
    • 匹配同名的 json 文件
    • 匹配同名的文件夹package.jsonmain 所对应的 js 文件
    • 匹配同名文件夹下的 index.js
var name = require('./nav/index.js')
console.log(name)
//1.核心模块加载时不需要添加路径
var os = require('os')
//2.非核心模块加载时,如果模块保存在node_modules文件夹下时,也不需要添加路径
var newAdd = require('newAdd')
console.log(newAdd(5,8))

2.9 模块练习

  • 题目
    • 在项目根路径下创建一个文件夹calculate,在文件夹内创建4个模块文件,add、sub、mul、div,分别实现加减乘除的功能。在项目入口index.js中,加载以上的四个模块,并根据用户输入的运算符和数字,实现四则运算功能
## add.js
//定义模块内容,导出模块数据
module.exports = function(num1,num2){
    return parseInt(num1) + parseInt(num2)
}

## sub.js
//定义模块内容,导出模块数据
module.exports = function(num1,num2){
    return num1 - num2
}

## mul.js
//定义模块内容,导出模块数据
module.exports = function(num1,num2){
    return num1 * num2
}

## div.js
//定义模块内容,导出模块数据
module.exports = function(num1,num2){
    return num1 / num2
}

## index.js
//加载运算模块
var add = require('./calculate/add')
var sub = require('./calculate/sub')
var mul = require('./calculate/mul')
var div = require('./calculate/div')
//接受输入参数
var num1 = process.argv[2]
var num2 = process.argv[3]
var option = process.argv[4]
var result = 0    //保存运算结果
switch(option){
    case '+':result = add(num1,num2);break;
    case '-':result = sub(num1,num2);break;
    case '*':result = mul(num1,num2);break;
    case '/':result = div(num1,num2);break;
    default:console.log('运算符不正确!');process.exit();
}
console.log('运算结果:',result)

3. NodeJS 事件机制

3.1 事件机制概述

  • Node 中的事件处理机制和浏览器中的事件机制类似
  • 事件机制要素
    • 事件源
    • 事件响应对象(区别)
    • 事件注册
    • 事件触发方法(区别)
    • 事件响应方法
  • Node 通过 events 模块实现事件机制
    • events 模块的 EventEmitter 对象
    • 该对象核心就是封装了事件触发与事件监听器的功能

3.2 事件应用语法

  • 导入事件模块
  • 创建 EventEmitter 实例对象
  • 注册事件
事件实例对象.on(‘event_name’,响应方法);
  • 触发事件
事件实例对象.emit(‘event_name’);

3.3 事件应用案例

//1.导入事件模块
var events = require('events')
//2.实例事件对象
var event_emmiter = new events.EventEmitter()
//3.注册事件
event_emmiter.on('e_Test',function(){
    console.log('事件触发了,触发时间:',new Date().toLocaleTimeString())
})
//4.触发事件
event_emmiter.emit('e_Test')

3.4 事件练习

  • 监督员工系统
## emp.js

console.log('当前运行的文件名:',__filename)
var emp = {
    name:'tom',
    salary:1000,
    work:function(){
        console.log('我开工了。。。。。。')
        console.log('今天老板不在,玩会手机,爽!')
    }
}
module.exports = emp
=======================================================
## boss.js

var boss = {
    name:'大富豪',
    manager:function(emp){
        console.log('有人偷懒,要处罚!')
        emp.salary -= 100
    }
}
module.exports = boss
=======================================================
## index.js

//非事件管理员工
var emp = {
    name:'tom',
    salary:1000,
    work:function(){
        console.log('我开工了。。。。。。')
        console.log('今天老板不在,玩会手机,爽!')
    }
}
var boss = {
    name:'大富豪',
    manager:function(emp){
        console.log('有人偷懒,要处罚!')
        emp.salary -= 100
    }
}
emp.work()
console.log('处罚前薪资:',emp.salary)
boss.manager(emp)
console.log('处罚后薪资:',emp.salary)
=======================================================
## index.js

//带事件机制的管理员工
var emp = require('./emp')
var boss = require('./boss')
var events = require('events')
var event_emmiter = new events.EventEmitter()

//注册事件,让boss管理emp
event_emmiter.on('e_playPhone',boss.manager)
emp.work()
console.log('违规之前工资:',emp.salary)
event_emmiter.emit('e_playPhone',emp)
console.log('违规之后工资:',emp.salary)
  • 火灾报警系统
    • 使用事件处理机制模拟以下情况:
      • 创建一个火灾模块和一个消防队模块,火灾对象内部包含火灾的地点和等级属性,以及一个着火方法;消防队具有一个灭火的方法,该方法可以根据火灾的等级,采取不同救火行动
      • 调用火灾对象的着火方法,触发消防队执行灭火方法
## fire.js

var fire = {
    address:'观前街',
    level:3,
    startFire:function(){
        console.log('着火了!')
    }
}
module.exports = fire
===============================================================
## fightFire.js

var fightFire = {
    fight:function(fire){
        switch(fire.level){
            case 1:console.log('小火,吐口水就灭了!');break;
            case 2:console.log('中火,去个中队可以搞定!');break;
            case 3:console.log('大火,全体出动!');break;
            default:console.log('地狱烈火,赶紧撤退!')
        }
    }
}
module.exports = fightFire
===============================================================
## index.js

var events = require('events')
var ee = new events.EventEmitter()
var fire = require('./1_fire')
var fightFire = require('./2_fightFire')
//事件注册
ee.on('e_fire',fightFire.fight)
fire.startFire()
ee.emit('e_fire',fire)

4. NodeJS 文件相关模块

4.1 NodeJS 的核心模块

  • Node 本身提供的一系列核心功能模块,用于与操作系统互动
  • 这些核心的功能模块在 Node 中内置
  • https://nodejs.org/api/

4.2 NodeJS 常见核心模块

  • path:处理文件路径
  • fs:操作(CRUD)文件系统
  • http:提供 HTTP 服务器功能
  • url:用于解析 URL
  • querystring:解析 URL 中的查询字符串
  • crypto:提供加密和解密功能
  • 。。。

4.3 全局路径变量

  • _ _dirname:获取当前项目文件夹名称
  • _ _filename:获取当前Node执行的js文件名

4.4 Path 模块常用属性和方法

属性方法说明
basename()获取文件路径字符串中的文件名(包含扩展名)
dirname()获取文件路径字符串中的文件所在的全目录名称
extname()获取文件路径字符串中的文件扩展名)
join()将参数中的字符串拼接成一个文件路径字符串
parse()将文件路径字符串转换成 path 对象
format()将 path 对象转换成文件路径字符串
// 导入核心模块 path
var path = require('path')
// basename 方法获取完整文件名
console.log(path.basename('E:/nodejsDemo2/emp.js'))
// dirname 方法获取完整文件夹
console.log(path.dirname('E:/nodejsDemo2/emp.js'))
// extname 方法获取文件的扩展名,包含小数点(注意)
console.log(path.extname('E:/nodejsDemo2/emp.js'))
// 1. 连接字符串方式拼接,需要手动添加分隔符
console.log(__dirname + '\boss.js')
// 2. path 的 join 方式拼接,自动添加分隔符,可以返回上一级
console.log(path.join(__dirname,'..','boss.js'))
var strPath = 'E:/nodejsDemo2/emp.js'
console.log(path.parse(strPath).base)

4.5 fs 模块常用属性和方法

属性方法说明
stat()获取文件或文件夹状态
mkdir()创建文件夹
writeFile()创建写入文件
readFile()读取文件
appendFile()追加文件
readdir()读取文件夹,获取文件夹下的所有文件和子文件夹信息
unlink()删除文件
rmdir()删除文件夹
var fs = require('fs')

fs.stat('emp.js',function(err,status){
    if(err){
        console.log('发生错误,错误信息:',err)
    }else{
        console.log(status)
    }
})

var student = {
    name:'alex',
    age:28,
    sex:true,
    address:'人民路18号'
}

//覆盖式写文件
fs.writeFile('stuInfo.txt',JSON.stringify(student),function(err){
    if(err){
        console.log('文件写入失败,原因:',err)
    }else{
        console.log('文件写入成功!')
    }
})

//追加式写文件
fs.appendFileSync('newstuInfo.txt','tom 18 true 人民路18号rn',function(err){
    if(err){
        console.log('文件写入失败,原因:',err)
    }else{
        console.log('文件写入成功!')
    }
})

4.6 fs 文件读写及简单错误处理

  • 文件读取
// 浏览器中的 Javascript 是没有文件操作的能力的
// 但是 Node 中的 JavaScript 具有文件操作的能力

// fs 是 file-system 的简写,就是文件系统的意思
// 在 Node 中如果想要进行文件操作,就必须引入 fs 这个核心模块
// 在 fs 这个核心模块中,就提供了所有的文件操作相关的 API
// 例如:fs.readFile 就是用来读取文件的

// 1. 使用 require 方法加载 fs 核心模块
var fs = require('fs')

// 2. 读取文件
//    第一个参数就是要读取的文件路径
//    第二个参数是一个回调函数
//          成功
//              data  数据
//              error null
//          失败
//              data  undefined 没有数据
//              error 错误对象

fs.readFile('./data/hello.txt',function(error,data){
  // 文件中存储的其实都是二进制数据 0 和 1
  //  为什么看到的不是二进制,是由于二进制转换为了十六进制了
  //  可以通过 toString 方法把其转化为我们能认识的字符
  // console.log(data.toString());

  //  错误判断
  if(error){
    console.log('读取文件失败')
  }else{
    console.log(data.toString());
  }
})
  • 文件写入
// 第一个参数:文件路径
// 第二个参数:文件内容
// 第三个参数:回调函数
//   error
//      成功:
//          文件写入成功
//          error 是 null
//      失败:
//          文件写入失败
//          error 就是错误对象
fs.writeFile('./data/你好.md','大家好,给大家介绍一下,我是Node.js',function(error){
  // 判断
  if(error){
    console.log('写入失败');
  }else{
    console.log('写入成功');
  }
})

4.7 fs 模块的同步和异步方法

  • 文件操作基本都具有同步和异步两种形式
    • 同步调用会阻塞代码的执行
    • 异步调用将读取任务下达到事件队列处理任务执行完成后执行回调
    • 异常处理方面
      • 同步必须使用 try catch 异常处理方式
      • 异步可以通过回调函数的第一个参数返回
var fs = require('fs')
//try把可能存在错误的代码包裹起来
try{
    var content = fs.readFileSync('stuInfo2.txt','utf-8')
    console.log('文件读取成功!')
    fs.writeFileSync('stuInfo1.txt',content)
}
//一旦出现错误,保证程序正常运行
catch(err)
{
    console.log('本次错误信息:',err.message)
}
console.log('文件复制成功!')
console.log('我啥时运行?')

4.8 fs 模块的同步方法的异常处理机制

  • try 块:包含可能出现异常的代码块
  • catch 块:捕获异常并处理的代码块
  • throw抛出异常
错误属性/方法说明
message异常的说明信息
name异常对象的类型名
toString()上述属性的合成信息

4.9 fs 模块练习

  • 题目
    • 获取并显示当前项目目录中的所有的文件,不包含文件夹
//获取并显示当前项目目录中的所有的文件,不包含文件夹
var fs = require('fs')
//获取指定位置下的所有文件和子文件夹
fs.readdir(__dirname, function (err, result) {
    if (err) {
        console.log('读取错误!信息:', err.message)
    } else {
        //循环遍历所有的文件和子文件夹
        for (let i = 0; i < result.length; i++) {
            //查看每一个文件或文件夹的状态
            fs.stat(result[i], function (e, status) {
                if (e) {
                    console.log('文件读取错误,信息:',err.message)
                } else {
                    //如果循环访问到的是文件的话就输出显示,文件夹的话就忽略
                    if (status.isFile()) {
                        console.log(result[i] + '是文件')
                    }
                }
            })
        }
    }
})

4.10 流的概述

  • 在 Node 的文档中经常见到 Stream 的概念
  • 什么是流?
    • 文件流、网络流
    • 数据的表现形式是二进制

4.11 文件流工作原理

在这里插入图片描述

4.12 文件流

  • 文件流就是以面向对象的概念对文件数据进行的抽象
  • 文件流定义了一些对文件数据的操作方式

在这里插入图片描述

4.13 文件流读写操作

  • 文件流读取数据
    • data 事件:读取到文件数据时触发
    • end 事件:读取文件结束时触发
  • 文件流写入数据
    • write (date,编码方式) 方法
    • finish 事件
## readStream.js

var fs = require('fs')
//创建读取文件流对象
var fread = fs.createReadStream('stuInfo.txt')
var content = ''
//注册读取事件
fread.on('data',function(result){
    //把读取的到的数据进行拼接
    content += result
})
//注册读取完成事件
fread.on('end',function(){
    console.log('文件的内容是:',content)
})
=====================================================
## writeStream.js

var fs = require('fs')
//创建文件输出流对象
var fwrite = fs.createWriteStream('stuInfo2.txt','utf-8')
fwrite.write('我学习了NodeJS!')
//结束文件流输出工作
fwrite.end()    
fwrite.on('finish',function(err){
    if(err){
        console.log('写文件失败,错误信息;',err.message)
    }else{
        console.log('写入完成!')
    }
})

4.14 文件流的常见方法和事件

方法事件说明
createReadStream()获取文件读取流对象
createWriteStream()获取文件输出流对象
data 事件数据读取到数据时触发的事件
end 事件数据读取结束后触发的事件
finish 事件数据写入结束后触发的事件
write()利用输出流将内容写入文件中
pipe()将数据从读取流中取出,传输到输出流中

4.15 文件流练习

  • 题目
    • 显示当前项目文件夹下的所有文件夹和文件(包含子文件夹以及内部的文件)
var fs = require('fs')
var path = require('path')

//递归:自我调用,内容变化,变化会导致调用结束
//递归获取指定文件夹下的所有文件和子文件夹信息
function getInfo(filePath){
   var fileInfos =  fs.readdirSync(filePath)        //获取当前文件夹filePath下所有的文件和子文件夹
   for(var i = 0 ; i < fileInfos.length ; i++){
       var absPath = path.join(filePath,fileInfos[i])
        var stats = fs.statSync(absPath)        //获取指定文件或文件夹的状态
        if(stats.isDirectory()){
            console.log('文件夹:',absPath)
            getInfo(absPath)
        }else{
            console.log('文件:',absPath)
        }
   }
}
getInfo(__dirname)
//stat方法的使用,建议采用绝对路径
// fs.stat(path.join(__dirname,'node_modules','jquery','dist','jquery.js'),function(err,status){
//     console.log(status.isDirectory())
// })
  • 题目
    • 一个文本文件中的内容如下
      • 张三 12000
      • 李四 11800
      • 王五 10500
    • 要求在每行数据后面再添加该员工的工资交税金额(按5%计算)
var fs = require('fs')
//1.读取文件
fs.readFile('emp.txt', function (err, content) {
    //2.修改内容
    var newContent = ''
    content = content.toString('utf-8')
    var contents = content.split('rn')
    for (var i = 0; i < contents.length; i++) {
        var reg = /d+/gi
        var tax = parseInt(contents[i].match(reg)) * 0.05
        newContent += contents[i] + ' ' + tax + 'rn'
    }
    console.log(newContent)
    //3.覆盖文件
    fs.writeFile('emp.txt',newContent,function(err){
        if(err){
            console.log('修改失败!')
        }else{
            console.log('修改完成!')
        }
    })
})

5. Web 服务器的请求和响应

5.1 http 模块

5.1.1 http 协议
  • HTTP:超文本传输协议
  • http 协议是网络应用层的协议,是一个无状态的协议
5.1.2 http 模块概述
  • http 模块
    • 提供服务器和服务的功能
    • 可以创建服务器和客户端
    • 应用于网络套接字开发
  • 两个重要的对象
    • request:请求对象
    • response:响应对象
5.1.3 http 模块的服务端方法
  • createServer(req,res,callback)
    • 创建并返回服务器对象
    • req:http 请求对象
    • res:http 响应对象
    • callback:http 请求成功后执行的回调函数
  • listen(port,hostname,callback)
    • 服务器对象的监听方法
    • port:服务器端口
    • hostname:服务器名
    • callback:监听到请求后执行的回调函数
5.1.4 http 模块的客户端方法
  • http.get( )方法用于访问远程服务器资源,可以实现爬虫的功能
http.get(url,function(result){
	result.on(‘data’,function(){  //接受数据 })
   	result.on(‘end’,function(){  //处理接收到的数据  })
})
  • 利用 node 使用 cheerio 爬取数据
var http = require('http')
var cheerio = require('cheerio')
http.get('http://search.jumei.com/?filter=0-11-1&search=%E9%9D%A2%E8%86%9C',function(res){
    var fullContent = ''
    res.on('data',function(content){
        fullContent += content          //接受数据
    })
    res.on('end',function(){
        // console.log(fullContent)
        var $ = cheerio.load(fullContent)
        $('.s_l_name a').each(function(index,item){
            console.log($(item).text().trim())
        })
    })
})
5.1.5 http 模块练习
  • 题目
    • 搭建一个Web服务器,当用户发出请求时,输出一个无序列表到客户端
var http = require('http')
http.createServer(function (req, res) {
    console.log('有用户来访!')
    res.setHeader('content-Type','text/html;charset=utf-8')
    var ulHtml = ` <ul>
                        <li>1</li>
                        <li>2</li>
                        <li>3</li>
                        <li>4</li>
                    </ul>
                 `
    res.end(ulHtml)
})
.listen(3000, function (err) {
    if (err) {
        console.log('服务器发生错误,无法监听!')
    }
    else {
        console.log('服务器开始监听。。。。。。。')
    }
})

5.2 url 模块

5.2.1 url 模块
  • url 是一个用于解析 URL 格式的模块

在这里插入图片描述

5.2.2 url 模块常用属性和方法
属性说明
href最初解析的完整URL字符串
protocol请求协议类型
hostURL的完整主机部分,包含端口
hostname主机的主机名部分
port主机的端口号部分
pathnameURL的路径部分(包括最初的/)
searchURL的查询字符串部分,包括前导的问号
parse()将url字符串转换成url对象
resolve将url的部分内容组合在一起
var url = require('url')
var str_url = 'http://www.wedn.net:8080/admin/users.html?name=zhaojy&age=18#profile'
var myUrl = url.parse(str_url)
console.log(myUrl.pathname)
console.log(myUrl.search)
var url1 = 'http://www.163.com'
var url2 = 'books/newbook.html'
console.log(url.resolve(url1,url2))  //拼接url的部分

5.3 Querystring 模块

5.3.1 Querystring 模块
  • querystring 是一个解析查询字符串的模块
5.3.2 Querystring 模块常用方法
  • parse 方法用于把字符串转换成 querystring 对象
  • stringify 方法用于把 querystring 对象转换成字符串
var url = require('url')
var qs = require('querystring')
var str_url = 'http://www.wedn.net:8080/admin/users.html?name=zhaojy&age=18#profile'
var myUrl = url.parse(str_url)
var strqs = myUrl.search
//1.传统字符串函数解析
// console.log(strqs.slice(1).split('&')[0].split('=')[0])
// console.log(strqs.slice(1).split('&')[0].split('=')[1])
// console.log(strqs.slice(1).split('&')[1].split('=')[0])
// console.log(strqs.slice(1).split('&')[1].split('=')[1])
//2.qs模块解析
console.log(qs.parse(strqs.slice(1)))

5.4 请求对象

5.4.1 请求对象
  • request 对象表示 HTTP 请求,包含了请求查询字符串,参数,内容,HTTP 头部等属性
5.4.2 get 请求
  • 通过 url 传递请求参数
http://localhost:3000/user?name=tom&email=tom@163.com
  • 在服务器端通过 url 对象的相关方法获取 get 参数
## get.js

var http = require('http')
var url = require('url')
var qs = require('querystring')
http.createServer(function (req, res) {
    console.log('有用户来访!')
    //处理请求
    console.log(req.url)
    if (req.url != '/favicon.ico') {
        var str_qs = url.parse(req.url).search.slice(1)
        var userName = qs.parse(str_qs).name
        var userEmail = qs.parse(str_qs).email
        console.log('name:',userName)
        console.log('email:',userEmail)
        //响应输出
        res.setHeader('content-Type', 'text/html;charset=utf-8')
        res.end('hello')
    }
})
    .listen(3000, function (err) {
        if (err) {
            console.log('服务器发生错误,无法监听!')
        }
        else {
            console.log('服务器开始监听。。。。。。。')
        }
    })
5.4.3 post 请求
  • 通过表单post方式传递请求参数
  • 在服务器端通过request对象的事件获取post参数
req.on(‘data’,callback);		//接收参数时
req.on(‘end’,callback);		//接收参数结束时
## post.js

var http = require('http')
var url = require('url')
var qs = require('querystring')
http.createServer(function (req, res) {
    console.log('有用户来访!')
    //处理请求
    console.log(req.url)
    if (req.url != '/favicon.ico') {
        var fullcontent = ''
        req.on('data',function(content){
            fullcontent += content
        })
        req.on('end',function(){
            console.log('post传参:',fullcontent)
        })
        //响应输出
        res.setHeader('content-Type', 'text/html;charset=utf-8')
        res.end('hello')
    }
})
    .listen(3000, function (err) {
        if (err) {
            console.log('服务器发生错误,无法监听!')
        }
        else {
            console.log('服务器开始监听。。。。。。。')
        }
    })
5.4.4 请求对象练习
  • 题目
    • 使用 post 方式,实现注册新用户的功能,将注册用户信息保存在users.json文件中
## register.html

<form action="http://localhost:3000/register" method="POST">
    <input type="text" name="userName">
    <input type="password" name="userPwd">
    <input type="submit" value="注册">
</form>

## login.html

<form action="http://localhost:3000/login" method="POST">
    <input type="text" name="username">
    <input type="password" name="userPwd">
    <input type="submit" value="登陆">
</form>
================================================================
## demo.js

var http = require('http')
var url = require('url')
var qs = require('querystring')
var fs = require('fs')
var path = require('path')
//创建服务器
http.createServer(function (req, res) {
    res.setHeader('content-type', 'text/html;charset=utf-8')
    if (req.url != '/favicon.ico') {
        var fullcontent = ''
        //接受用户post传入的数据
        req.on('data', function (content) {
            fullcontent += content
        })
        //完成接受用户post传入的数据
        req.on('end', function () {
            var objqs = qs.parse(fullcontent)   //解析新注册的数据,并转换成json对象
            //1.读取文件
            fs.readFile(path.join(__dirname, 'user.json'), function (err, users) {
                var usersData = JSON.parse(users)   //把json文件中的用户数据字符串转换成对象
                usersData.users.push(objqs)         //把新注册的用户数据添加到已有的用户数组中
                //把修改后的数据保存到原来的用户文件里
                fs.writeFile(path.join(__dirname, 'user.json'),
                    JSON.stringify(usersData), function (err) {
                        if (err) {
                            res.statusCode = 200
                            res.end('<script>alert("注册失败!")</script>')
                        } else {
                            res.statusCode = 200
                            res.end('<script>location.href="index.html";</script>')
                        }
                    })
            })
        })
    }

}).listen(3000)

5.5 响应对象

5.5.1 响应对象
  • response 对象表示 HTTP 响应,即在接收到请求时向客户端发送的 HTTP 响应数.
5.5.2 响应状态码
  • 常见响应头的状态码
状态码说明
1xx请求已受理,继续处理中
2xx请求被成功处理
3xx请求被重定向
4xx客户端请求错误
5xx服务器错误

5.6 请求和响应对象的实际应用

5.6.1 服务器静态网页的访问步骤
  • 通过请求对象获取静态资源的文件路径
  • 通过fs进行读取,获取资源内容
  • 通过响应对象将网页资源输出客户端
5.6.2 服务器样式 & 脚本文件等资源的访问步骤
  • 通过请求对象path 模块获取资源文件的扩展名
  • 通过扩展名获取MIME类型
    • MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型
  • 通过输出对象设置content-type
var http = require('http')
var url = require('url')
var fs = require('fs')
var path = require('path')
http.createServer(function (req, res) {
    console.log('有用户来访!')
    if (req.url != '/favicon.ico') {
        var filePath = path.join(__dirname,'static',url.parse(req.url).pathname.slice(1)) //获取静态网页的名称
        fs.readFile(filePath, function (err, htmlcontent) {
            if (err) {
                console.log('错误信息:', err.message)
            }
            console.log(htmlcontent.toString('utf-8'))
            res.setHeader('content-Type', 'text/html;charset=utf-8')
            res.end(htmlcontent.toString('utf-8'))
        })
    }
   
}).listen(3000)
5.6.3 请求响应练习
  • 题目
    • 假设在服务器端有一个商品数据文件(product.json),在浏览器窗口的地址栏中输入url,url中带不同的查询字符串,字符串中的参数是价格。根据价格返回不超过该价格的所有商品,以无序列表方式返回浏览器窗口,显示商品的名称。
    • localhost:3000/product?price=10000
    • Json文件格式如下:
    • [{“name”:”iphone”,”price”:5000},{“name”:”p30”,”price”:4000}]
var http = require('http')
var fs = require('fs')
var url = require('url')
var qs = require('querystring')
var path = require('path')

http.createServer(function(req,res){
    res.setHeader('content-type','text/html;charset=utf-8')
    if(req.url != '/favicon.ico'){
        //提取查询的价格
        var price = qs.parse(url.parse(req.url).search.slice(1)).price
        //读取文件
        fs.readFile('products.json',function(err,content){
            if(err){
                console.log('文件读取出现异常!')
                res.end('查询失败!')
            }else{
                // var queryProducts = [] //保存符合查询条件的商品
                var strProductHTML = '<ul>'
                var products = JSON.parse(content) //把读取到的产品数据字符串转换成JSON对象
                for(var i = 0 ; i < products.length ; i++){
                    if(products[i].price < price){
                        // queryProducts.push(products[i])     //过滤商品数据
                        strProductHTML += `<li>${products[i].name}</li>`
                    }
                }
                strProductHTML += '</ul>'
                res.statusCode = 200
                res.end(strProductHTML)
            }
        })
    }
}).listen(3000,'127.0.0.1',function(err){
    console.log('开始监听')
})
  • 题目
    • 往 http 服务器上添加多个商品信息,商品保存在网站 data 文件夹下的 products.json 文件中。商品包含编号、名称、价格和进货日期
    • 请求访问的服务器地址:http://localhost:3000/addProduct
## product.html

<form action="http://localhost:3000/addProduct" method="POST">
    <input type="text" name="pId" id="">
    <input type="text" name="pName" id="">
    <input type="text" name="pPrice" id="">
    <input type="text" name="pDate" id="">
    <input type="submit" value="入库">
</form>
====================================================================
## product.js

var http = require('http')
var fs = require('fs')
var url = require('url')
var qs = require('querystring')
var path = require('path')

http.createServer(function (req, res) {
    console.log('有客户访问')
    res.setHeader('content-type', 'text/html;charset=utf-8')
    if (req.url != '/favicon.ico') {
        var fullContent = ''
        req.on('data', function (content) {
            fullContent += content
        })
        req.on('end', function () {
            var newProduct = qs.parse(fullContent)
            //1.读取现有文件
            fs.readFile('products.json', function (err, products) {
                if (err) {
                    console.log('文件读取错误!')
                    res.statusCode = 500
                    res.end('商品入库失败!')
                } else {
                    //2.数据添加
                    var products = JSON.parse(products.toString())
                    console.log('products',products)
                    products.push(newProduct)
                    //3.写入新文件
                    fs.writeFile('products.json', JSON.stringify(products), function (err) {
                        if (err) {
                            console.log('文件记录失败!')
                            res.statusCode = 500
                            res.end('商品入库失败!')
                        }else{
                            res.statusCode = 200
                            res.end('商品入库成功!')
                        }
                    })
                }
            })
        })
    }
}).listen(3000, 'localhost', function (err) {
    console.log('开始监听')
})

6. NodeJS 的 EJS 模版

6.1 EJS 模板是什么

  • EJS 是一个服务器模板
    • 可以把后端数据HTML页面结合起来,生成动态网页
  • EJS 是一个第三方模块,需要安装加载后使用
  • EJS 常见应用
    • 定义页面结构
    • 输出服务器数据

6.2 NodeJS 的技术特性

  • EJS 特性
    • EJS 模板和 HTML结构相似
    • 使用特殊标记绑定后台数据

6.3 EJS 模块的安装和使用

  • 安装
    • npm install ejs --save
  • 使用
    • 导入模块
    • 调用方法渲染页面
    • 在回调函数中,读取数据并输出到页面
ejs.renderFile( filename , data , callback)
## page.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>EJS模板页面</title>
</head>
<body>
    <h1>这是EJS渲染的页面</h1>
</body>
</html>
=========================================================
## demo.js

var http = require('http')
var fs = require('fs')
var url = require('url')
var qs = require('querystring')
var path = require('path')
var ejs = require('ejs')

http.createServer(function (req, res) {
    res.setHeader('content-type', 'text/html;charset=utf-8')
    if (req.url != '/favicon.ico') {
        //判断用户请求是否是针对page1.ejs模板文件
        if (req.url == '/page1') {
            fs.readFile('view/page1.ejs', function (err, content) {
                if (err) {
                    console.log('ejs文件加载失败!')
                    res.end('网页不存在!')
                } else {
                    res.end(content)
                }
            })
            // ejs.renderFile('view/page1.ejs',{},function(err,content){
            //     if(err){
            //         console.log('ejs文件加载失败!')
            //         res.end('网页不存在!')
            //     }else{
            //         res.end(content)
            //     }
            // })
        }
    }
}).listen(3000, '127.0.0.1', function (err) {
    console.log('开始监听')
})

6.4 EJS 模版的特殊标签

  • <% %>:运行服务器端代码,等价于{ }
  • <%= %>:输出变量到页面
## page.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>EJS模板页面</title>
</head>
<body>
    <h1>这是EJS渲染的页面</h1>
    <h3><%=title%></h3>
    <p><%=content%></p>
    <%
        var now = new Date('2009-1-1')
        var year = now.getFullYear()
        if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0){
    %>
            <span>闰年</span>
    <%
        }
        else{
    %>
            <span>不是闰年</span>
    <%        
        }
    %>
    <ul>
        <%
            for(var i = 0 ; i < names.length ;i++){
        %>
        <li><%=names[i]%></li>
        <%        
            }
        %>

    </ul>
</body>
</html>
================================================================
## demo.js

var http = require('http')
var fs = require('fs')
var url = require('url')
var qs = require('querystring')
var path = require('path')
var ejs = require('ejs')

http.createServer(function (req, res) {
    res.setHeader('content-type', 'text/html;charset=utf-8')
    if (req.url != '/favicon.ico') {
        //判断用户请求是否是针对page1.ejs模板文件
        if (req.url == '/page1') {
            ejs.renderFile('view/page1.ejs',
            {title:'我是EJS的标题',content:'我是EJS的内容',names:['tom','jack','mary']},
            function(err,content){
                if(err){
                    console.log('ejs文件加载失败!')
                    res.end('网页不存在!')
                }else{
                    res.end(content)
                }
            })
        }
    }
}).listen(3000, '127.0.0.1', function (err) {
    console.log('开始监听')
})

在这里插入图片描述

6.5 EJS 模版练习

在这里插入图片描述

  • demo.ejs
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>互联网大神名录</title>
    <style>
        ...
    </style>
</head>
<body>
    <ul>
        <%
            for(var i = 0 ; i < godsInfo.gods.length; i++){
        %>
        <li>
            <img src="<%=godsInfo.gods[i].face%>" alt="">
            <h3><%=godsInfo.gods[i].name%></h3>
            <p><%=godsInfo.gods[i].intr%></p>
        </li>
        <%        
            }
        %>
    </ul>
</body>
</html>
  • demo.js
var http = require('http')
var fs = require('fs')
var url = require('url')
var qs = require('querystring')
var path = require('path')
var ejs = require('ejs')

http.createServer(function (req, res) {
    res.setHeader('content-type', 'text/html;charset=utf-8')
    if (req.url != '/favicon.ico') {
        //判断用户请求是否是针对page1.ejs模板文件
        if (req.url == '/myGods') {
            var myGodsData = null
            // myGodsData = JSON.parse(fs.readFileSync('myGods.json').toString()) //同步方式获取数据
            //1.读取myGods.json的数据
            fs.readFile('myGods.json', function (err, content) {
                myGodsData = JSON.parse(content.toString())
                //2.渲染EJS模板页面
                ejs.renderFile('view/myGods.ejs',
                    { godsInfo: myGodsData },
                    function (err, content) {
                        if (err) {
                            console.log('ejs文件加载失败!')
                            res.end('网页不存在!')
                        } else {
                            res.end(content)
                        }
                    })
            })
           
        }else{
            // 处理非ejs文件的其他静态资源文件,例如图片等
            console.log('imgPath:',req.url)
            // 读取图片文件内容,并通过响应对象,向客户端发出
            fs.readFile('public/images' + req.url,function(err,content){
                if(err){
                    console.log('err:',err.message)
                }else{
                    console.log('content',content)
                    res.setHeader('content-type','image/jpg')
                    res.end(content)
                }
            })
        }
    }
}).listen(3000, '127.0.0.1', function (err) {
    console.log('开始监听')
})

7. NodeJS 路由

7.1 路由是什么

  • 路由(Routing)是由一个URL和一个特定的HTTP方法(GETPOST等)组成的
  • 涉及到应用如何响应客户端对某个网站节点的访问

7.2 路由的理解

在这里插入图片描述

7.3 路由的实现

  • 通过对请求路径的获取,然后调用相应的EJS模板,完成页面的跳转
var http = require('http')
var fs = require('fs')
var url = require('url')
var qs = require('querystring')
var path = require('path')
var ejs = require('ejs')

http.createServer(function (req, res) {
    res.setHeader('content-type', 'text/html;charset=utf-8')
    if (req.url != '/favicon.ico') {
        if(req.url == '/page1'){
            ejs.renderFile('view/page1.ejs',
            {title:'我是EJS的标题',content:'我是EJS的内容',names:['tom','jack','mary']},
            function(err,content){
                res.end(content)
            })
        }else if(req.url == '/page2'){
            ejs.renderFile('view/page2.ejs',{name:[]},function(err,content){
                res.end(content)
            })
        }else{
            res.statusCode = 404
            res.end('无此网页,访问错误!')
        }
    }
}).listen(3000, '127.0.0.1', function (err) {
    console.log('开始监听')
})

7.4 路由的封装

  • 路由负责业务功能
    • 包括处理请求的EJS模板和业务逻辑
    • 通过定义路由模块,实现HTTP Server代码的简化
## routes.js

var App = {
    page1:function(req,res){
        res.end('我是第一个页面')
    },
    page2:function(req,res){
        res.end('我是第二个页面')
    },
    login:function(req,res){},
    register:function(req,res){},
    query:function(req,res){}
}
module.exports = App
=============================================
## index.js

var http = require('http')
var fs = require('fs')
var url = require('url')
var qs = require('querystring')
var path = require('path')
var App = require('./model/routes') //导入自定义路由模块

http.createServer(function (req, res) {
    res.setHeader('content-type', 'text/html;charset=utf-8')
    if (req.url != '/favicon.ico') {
        var path = url.parse(req.url).pathname.slice(1)   
        try {
            App[path](req,res)
        } catch (err) {
            res.statusCode = 404
            res.end('网页不存在,请检查!')
        }
    }
}).listen(3000, '127.0.0.1', function (err) {
    console.log('开始监听')
})

7.5 路由练习

  • 题目
    • 创建商品列表页、商品详情页和商品评论页,利用EJS模板和路由实现三个页面的显示以及页面间的跳转

8. NodeJS 数据库访问

8.1 NodeJS 访问 MySQL 数据库

8.1.1 访问数据库的步骤
  • 首先需要安装加载 mysql 模块
    • npm install mysql --save
  • 再进行数据库的连接
    • 创建数据库连接对象
    • 使用connect方法,连接数据库
8.1.2 创建数据库连接对象
  • 使用mysql模块对象createConnection方法
参数名称描述
host数据库服务器主机 IP
port数据库服务器端口3306
database数据库服务器的数据库对象
user用户,root
password密码
8.1.3 连接数据库的方法
  • 连接数据库
    • connect方法
    • 8.0版本的权限问题解决方法
alter user ‘root’@’localhost’ identified with mysql_native_password by ‘密码’; 
flush privileges;
  • 关闭数据库连接
    • end方法
    • destory方法
var mysql = require('mysql')
//创建数据库连接对象,设置数据库连接参数
var connection = mysql.createConnection({
    host:'localhost',
    port:3306,
    database:'myserver',
    user:'root',
    password:'123456'
})
//连接数据库
connection.connect(function(err){
    if(err){
        console.log('数据库连接失败!',err.message)
    }else{
        console.log('数据库连接成功!')
        //操作数据库的代码段
        console.log('数据库的增删改查。。。。。。')
        //关闭连接
        connection.end()    
    }
})

8.2 NodeJS 对 MySQL 数据库的常见操作

8.2.1 数据查询的方法
  • 使用数据库连接对象的query方法
  • 语法
connection.query ( 查询语句 , 回调函数(err,results,fields){
	处理查询后的后续操作
})
  • err:查询错误对象
  • results:查询的记录信息,需要做JSON的格式处理
  • fields:查询数据的字段信息
//连接数据库
connection.connect(function(err){
    if(err){
        console.log('数据库连接失败!',err.message)
    }else{
        console.log('数据库连接成功!')
        //操作数据库的代码段
        connection.query('select * from emp',function(err,results){
            if(err){
                console.log('数据库查询失败!',err.message)
            }else{
                console.log('数据查询结果:',results)
            }
        })
        //关闭连接
        connection.end()    
    }
})
8.2.2 将数据传到前端页面
  • 将数据库数据传到前端
var http = require('http')
var mysql = require('mysql')

http.createServer(function(req,res){
    res.setHeader('content-type','text/html;charset=utf-8')
    if(req.url != '/favicon.ico'){
        //连接数据库,获取数据,响应对象发送数据
        var connection = mysql.createConnection({
            host:'localhost',
            port:3306,
            database:'myserver',
            user:'root',
            password:'123456'
        })
        connection.connect(function(err){
            if(err){
                console.log('数据库连接失败!')
                res.end(JSON.stringify({code:500,message:'无法查询数据!'}))
            }else{
                connection.query('select * from emp',function(err,results){
                    if(err){
                        console.log('数据库查询失败',err.message)
                    }else{
                        res.end(JSON.stringify({code:200,message:'查询完成',data:results}))
                    }
                })
            }
            connection.end()
        })
    }
}).listen(3000,function(){
    console.log('开始监听。。。')
})
8.2.3 数据库登陆练习
  • login.html
<!-- 普通的表单请求,不受跨域请求限制 -->
<form action="http://localhost:3000/login" method="POST">
    <input type="text" name="userName" id="">
    <input type="password" name="userPwd" id="">
    <input type="submit" value="登录">
</form>
  • login.js
var http = require('http')
var url = require('url')
var qs = require('querystring')
var mysql = require('mysql')

http.createServer(function (req, res) {
    res.setHeader('content-type', 'text/html;charset=utf-8')
    res.setHeader('Access-Control-Allow-Origin','*')        // 允许跨域请求
    if (req.url != '/favicon.ico') {
        if (req.url == '/login') {
            var fullContent = ''
            // 使用post方式接受参数
            req.on('data', function (content) {
                fullContent += content
            })
            req.on('end', function (err) {
                if (err) {
                    console.log('数据接受失败!')
                    res.end(JSON.stringify({ code: 500, message: '数据传参不正确!' }))
                } else {
                    var user = qs.parse(fullContent)
                    console.log('user:', user)
                    // 把参数传入数据库,进行登录验证
                    // 连接数据库,获取数据,响应对象发送数据
                    var connection = mysql.createConnection({
                        host: 'localhost',
                        port: 3306,
                        database: 'myserver',
                        user: 'root',
                        password: '123456'
                    })
                    connection.connect(function (err) {
                        if (err) {
                            console.log('数据库连接失败!')
                            res.end(JSON.stringify({ code: 500, message: '无法查询数据!' }))
                        } else {
                            var sql = 'select * from emp where eName=? and empId=?'
                            console.log('sql:',sql)
                            connection.query(sql,[user.userName,user.userPwd],function (err, results) {
                                if (err) {
                                    console.log('登录失败', err.message)
                                } else {
                                    if(results.length > 0){
                                        res.end(JSON.stringify({ code: 200, message: '登录成功!', data: results }))
                                    }else{
                                        res.end(JSON.stringify({ code: 200, message: '登录失败!', data: results }))
                                    }
                                }
                            })
                        }
                        connection.end()
                    })
                }
            })
        }
    }
}).listen(3000, function () {
    console.log('开始监听。。。')
})
8.2.2 跨域请求的处理
  • 针对客户端跨域请求的处理采用服务器端设置响应头参数
res.setHeader('Access-Control-Allow-Origin','*')
8.2.3 防止注入式攻击
  • 注入式攻击是指传入的参数中包含sql脚本执行特殊的攻击命令
connection.query ( 查询语句 , 参数,回调函数(err,result ){
	处理查询后的后续操作
})
  • 解决方法
// 传统字符串拼接有漏洞,容易被注入式攻击
// var sql = "select * from emp where eName='" 
// + user.userName + "' and empId = " + user.userPwd
// ES6字符串拼接也有漏洞
// var sql = `select * from emp where eName='${user.userName}' and empId=${user.userPwd}`
// 利用参数方式堵住漏洞
var sql = 'select * from emp where eName=? and empId=?'
connection.query(sql,[user.userName,user.userPwd],function (err, results) {
	if (err) {
	    console.log('登录失败', err.message)
	} else {
	    if(results.length > 0){
	        res.end(JSON.stringify({ code: 200, message: '登录成功!', data: results }))
	    }else{
	        res.end(JSON.stringify({ code: 200, message: '登录失败!', data: results }))
	    }
	}
})
8.2.4 模块化设计中的数据返回方法
  • 模块间调用数据库访问方法时,由于异步问题的影响,会造成数据返回不正常
  • 采用回调方式返回数据

在这里插入图片描述

  • 使用Ajax异步请求服务器
  • login.html
<script>
    function login(){
        $.ajax({
            url:'http://localhost:3000/login',   //等同于表单的action属性
            type:'post',             //等同于表单的method属性
            data:{
                userName:$('#userName').val(),      //获取用户名
                userPwd:$('#userPwd').val()         //获取用户密码
            },
            dataType:'json',        //返回的数据是JSON格式
            success:function(results){
                if(results.data.length > 0){
                    alert('欢迎登录!')
                }else{
                    alert('用户名、密码错误,登录失败!')
                }
            }
        })
    }
</script>

<form action="">
    <input type="text" name="userName" id="userName">
    <input type="password" name="userPwd" id="userPwd">
    <input type="button" value="登录" onclick="login()">
</form>
  • login.js
var http = require('http')
var url = require('url')
var routes = require('./6_routes')   //导入路由模块

http.createServer(function (req, res) {
    res.setHeader('content-type', 'text/html;charset=utf-8')
    res.setHeader('Access-Control-Allow-Origin','*')        //允许跨域请求
    if (req.url != '/favicon.ico') {
        if(routes[req.url.slice(1)]){
            //路由正确时
            routes[req.url.slice(1)](req,res)
        }else{
            //路由地址错误时
            res.end(JSON.stringify({code:404,message:'您输入的url地址不正确'}))
        }       
    }
}).listen(3000, function () {
    console.log('开始监听。。。')
})
  • routes.js
var qs = require('querystring')
var loginDB = require('./7_loginDB')

var routes = {
    login: function (req, res) {
        // if (req.url == '/login') {
        var fullContent = ''
        //使用post方式接受参数
        req.on('data', function (content) {
            fullContent += content
        })
        req.on('end', function (err) {
            if (err) {
                console.log('数据接受失败!')
                res.end(JSON.stringify({ code: 500, message: '数据传参不正确!' }))
            } else {
                var user = qs.parse(fullContent)
                console.log('user:', user)
                //把参数传入数据库,进行登录验证
                loginDB(user,function(err,results){
                    if(err){
                        res.end(JSON.stringify({code:500,message:'登录失败'}))
                    }else{
                        if(results.length>0){
                            res.end(
                                JSON.stringify({ 
                                   code: 200, 
                                   message: '登录成功!', 
                                   data: results }))
                        }else{
                            res.end(
                                JSON.stringify({ 
                                    code: 200,
                                    message: '登录失败!', 
                                    data: results }))
                        }
                    }
                })
            }
        })
        // }
    },
    resister: function (req, res) { },
    addUser: function (req, res) { }
}
module.exports = routes
  • loginDB.js
var db = require('./8_commonDB')

//连接数据库,获取数据,相应对象发送数据
function login(user,cb){
   var sql = 'select * from emp where eName=? and empId=?'
   var params = [user.userName,user.userPwd]
   db(sql,params,function(err,results){
        if(err){
            cb(err,null)
        }else{
            cb(null,results)
        }
   })
}
module.exports = login
  • commonDB.js
var mysql = require('mysql')
function db(sql,params,cb){
    var connection = mysql.createConnection({
        host: "localhost",
        port: 3306,
        database: "myserver",
        user: "root",
        password: "123456",
    });
    connection.connect(function (err) {
        if (err) {
            console.log("数据库连接失败!");
            res.end(
                JSON.stringify({ code: 500, message: "无法查询数据!" })
            );
        } else {
            connection.query(sql, params, function (err, results) {
                if (err) {
                    cb(err,null)    //执行回调函数,并且把参数返回
                } else {
                    cb(null,results)
                }
            });
        }
        connection.end();
    });
}
module.exports = db
8.2.5 数据增删改的方法
  • 使用数据库连接对象的query方法
  • 语法
connection.query ( 增删改语句 ,语句使用的参数, 回调函数(err,result ){
	处理增删改后的后续操作
})
  • err:查询错误对象
  • result:查询结果
8.2.6 数据增删改方法的参数
  • 语法
var  insert_sql = ‘ insert into dept values(?,?,?)’;
var insert_para = [40,’技术部2部’,F区’];
  • 使用?表示形式参数
  • 通过数组方式提供实际参数
8.2.7 存储过程的调用
  • 使用数据库连接对象的query方法
  • 语法
connection.query ( 调用存储过程的语句,语句使用的参数, 回调函数(err,result ){
	处理后续操作
})
  • err:查询错误对象
  • result:查询结果
8.2.8 数据库连接池原理

在这里插入图片描述

8.2.9 数据库连接池应用语法
  • 采用连接池技术减少数据库连接的数量
var mysql = require('mysql');
var pool  = mysql.createPool({config});
//通过连接池获取数据库连接对象
pool.getConnection(function(err, connection) {
   if (err) throw err; 		   // 连接失败
   connection.query(SQL语句', function (error, results, fields) {
          if (error) throw error
          connection.release()		//释放连接
   })
})

9. Express 框架

9.1 Express 概述

9.1.1 Express 是什么
  • ExpressNode.js 的一个轻量级的 Web 框架
  • 使用框架可以减少创建应用程序所需要的时间
  • 采用MVC结构,简化开发难度
9.1.2 Express 框架的安装
  • 官网
    • http://expressjs.com.cn
  • 简单安装
    • npm install express --save
9.1.3 Express 搭建 Web 服务器
  • 比原生Server模块要简单
var express = require('express');
var app = express();
app.get('/',function(req,res){   
	console.log('hello express!');
});
app.listen(3000,function(){    
	console.log('Start Listen!');
});
9.1.4 Express 路由配置
  • Express 路由配置组成
    • 请求方式(GET,POST等)
    • 请求访问路径
9.1.5 Express 路由请求方式配置
  • 语法
    • app.Method(path, [callback] callback)
  • Method 分别为 get,post 方式
  • path 是请求 url 的路径部分(pathname)
9.1.6 Express 路由的使用
  • 语法
    • app.use('路由名', 路由模块对象)
    • use 方法用于加载路由模块
    • 路由模块内部通过get、post方法设置子路由
9.1.7 Express 静态资源的配置
  • 语法
    • app.use(express.static(path.join(__dirname, 'public')))
  • 设置静态文件的文件夹目录
    • 该文件夹下保存静态页面、样式、脚本和资源文件
9.1.8 Express 页面模版的配置
  • 语法
    • app.set('views', path.join(__dirname, 'views'));
    • app.set('view engine', 'ejs');
  • 设置动态页面的文件夹目录为 views
  • 设置动态页面的类型为 ejs 模板
9.1.9 Express 参数的获取
  • 实现路由参数的方法
    • Get 方式传参,通过 req.query 获取
    • Post 方式传参,通过 req.body 获取
      • 需要安装body-parser模块,并加载
        • app.use(bodyParse.urlencoded({extended:false}))
        • app.use(bodyParse.json())
    • RESTF风格传参,通过params获取
      • 传参格式/value1/value2
9.1.10 Express 参数的获取
  • 发送客户端方法
    • send(‘’):将字符串发送到客户端
    • json({ }):将一个json对象发送到客户端
    • render(‘’):将一个模板页面发送到客户端
  • 服务器跳转方法
    • redirect()

9.2 Express 框架的中间件

9.2.1 Express 的中间件
  • 中间件的概念
    • 在请求和响应之间实现业务的功能单元
    • 是 Express 的核心
9.2.2 Express 的中间件类型
  • 应用型:通过app加载使用
  • 路由型:通过路由模块Router加载
  • 内置型:例如静态文件的设置
  • 第三方:例如body-parser模块
9.2.3 Express 中间件的加载
  • 中间件通过app.use()挂载
  • 不加路径的中间件,在任何请求时都会执行
  • 中间件函数中需要执行next( ),或者响应输出否则会导致挂起
app.use( path , function(req, res, next) {  		next();
	}
)

9.3 Express MVC 框架

9.3.1 MVC 框架含义
  • M:模型层(model)
  • V:视图层(view)
  • C:业务逻辑层(control)
9.3.2 Express MVC 框架的安装启动
  • 脚手架方式安装(cli)
    • npx express-generator
  • 脚手架方式启动
    • npm start
9.3.3 Express MVC 框架目录结构
  • bin目录:其中的www是程序入口模块
  • app.js:主模块,包含配置信息
  • public目录:提供给web公共文件夹,保存css,js和图片等资源
  • routes目录:保存了对应用程序路由响应的定义
  • views目录:定义应用程序的布局(layout)
9.3.4 Express MVC 框架添加新路由
  • 在routes文件夹中添加新路由模块
  • 在新路由模块中添加渲染(render)或者输出功能(json)
  • 如果是渲染页面,需要在views中添加HTML模板
  • 在app.js中导入新路由模块
  • 在app.js中使用新路由模块

5. 简单的 http 服务

  • 发送请求
// 在 Node 中专门提供了一个核心模块: http
// http 这个模块的职责就是帮你创建编写服务器的

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

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

// 3. 服务器要干嘛?
//    提供服务: 对数据的服务
//    发请求
//    接受请求
//    处理请求
//    反馈(发送响应)


//    注册 request 请求事件
//    当客户端请求过来,就会自动触发服务器的 request 请求事件,然后执行第二个参数;回调处理函数


server.on('request',function(){
  console.log('收到客户端的请求了');
})

// 4. 绑定端口号,启动服务器
server.listen(3000,function(){
  console.log('服务器启动成功了,可以通过 http://127.0.0.1:3000/ 来进行访问');
})
  • 发送响应
var http = require('http');

var server = http.createServer();

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

  // response 对象有一个方法:write 可以用来给客户端发送响应数据
  // write 可以使用多次,但是最后一定要使用 end 来结束响应,否则客户端会一直等待
    response.write('hello node.js');

    // 告诉客户端,话说完了,可以呈递用户
    response.end();
})

server.listen(3000,function(){
  console.log('服务器启动成功,可以通过 localhost:3000 进行访问');
})

6. 根据不同请求路径返回不同数据

// 根据不同的请求路径返回不同数据
var http = require('http');

 // 1. 创建 Server
var server = http.createServer();

// 2. 监听 request 请求事件,设置请求处理函数
server.on('request',function(req,res){
  // console.log('收到请求了,请求路径是:'+req.url);

  // res.write('hello');
  // res.write(' world');
  // res.end();

  // 上面的方式比较麻烦,推荐使用更简单的方式,直接 end 的同时发送响应数据
  // res.end('hello world');

  // 根据不同的请求路径发送不同的响应结果
  // 1. 获取请求路径
  //      req.url 获取到的是端口号之后的那一部分路径
  //      也就是说所有的 url 都是以 / 开头的
  // 2. 判断路径处理响应

    var url = req.url;

    // if(url === '/'){
    //   res.end('index page')
    // }else if (url === '/login') {
    //   res.end('login page')
    // }else {
    //   res.end('404 Not Found')
    // }

    if (url === '/products') {
      var products = [
        {
          name: '苹果',
          price: 8888
        },
        {
          name: '香蕉',
          price: 5888
        },
        {
          name: '菠萝',
          price: 3888
        }
      ]

      // 响应内容只能是二进制数据或者字符串
      // 数字
      // 对象
      // 数组
      // 布尔值
      // 都不行
      res.end(JSON.stringify(products))
    }

})

// 3. 绑定端口号,启动服务
server.listen(80,function(){
  console.log('服务器启动成功, 可以访问!');
})

7. each 与 forEach

jQuery 的 each 和原生的 JavaScript 方法 forEach

  • EcmaScript 5 提供的
    • 不兼容 IE8
  • jQuery 的 each 由 jQuery 这个第三方库提供
    • jQuery 2 以下的版本是兼容 IE8 的
    • 他的 each 方法主要用来遍历 jQuery 实例对象(伪数组)
    • 同时它也可以作为低版本浏览器中 forEach 替代品
    • jQuery 的实例对象不能使用 forEach 方法,如果想要使用必须转为数组才可以使用
    • [].slice.call(jQuery实例对象)

8. 文件操作路径和模块路径

  • 文件操作路径:
在文件操作的相对路径中
     ./data/a.txt 相对于当前目录
     data/a.txt   相对于当前目录
     /data/a.txt  绝对路径,当前文件模块所处磁盘根目录
     c:/xx/xx.... 绝对路径
fs.readFile('./data/a.txt',function(err,data){
  if(err){
    console.log(err);
    return console.log('读取失败');
  }
  console.log(data.toString());
})
  • 模块操作路径:
// 这里如果忽略了,则也是磁盘根目录
require('/data/foo.js')

// 相对路径
require('./data/foo.js')

// 模块加载的路径中的相对路径不能省略 ./

9. 使用 nodemon 自动重启

  • 我们这里可以使用一个第三方命名航工具:nodemon 来帮我们解决频繁修改代码重启服务器问题
  • nodemon 是一个基于 Node.js 开发的一个第三方命令行工具,我们使用的时候需要独立安装:
// 在任意目录执行该命令都可以
// 也就是说,所有需要 --global 来安装的包都可以在任意目录执行
npm install --global nodemon

安装完毕之后,使用:

node app.js

// 使用 nodemon
nodemon app.js

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

3. Node 中的 JavaScript

  • ECMAScript
    • 没有 BOM, DOM
  • 核心模块
  • 第三方模块
  • 用户自定义模块

1. 核心模块

  • Node 为 JavaScript 提供了很多服务器级别的 API ,这些 API 绝大多数都被包装到了一个具名的核心模块中了。
  • 例如文件操作的 fs核心模块,http 服务构建的 http模块,path路径操作模块,os操作系统信息模块。。。
  • 以后只要说这个模块是一个核心模块,就要马上想到如果想要使用它,就必须:
// 用来操作文件系统
var fs = require('fs')

// 用来加载 http
var http = require('http')

// 用来获取操作系统
var os = require('os')

// 用来操作路径
var path = require('path')

...

2. 简单的模块化

  • JavaScript 天生不支持模块化,requireexports,是 Node.js 才有的
  • require 是一个方法
    • 他的作用就是用来加载执行文件模块中的代码
  • 在 Node 中,模块有三种:
    • 具名的核心模块,例如 fshttp
    • 用户自己编写的文件模块
      • 相对路径必须加 ./
      • 可以省略后缀名
      • 相对路径中的 ./ 不能省略,否则报错
    • 在 Node 中,没有全局作用域,只有模块作用域
      • 外部访问不到内部
      • 内部也访问不到外部

a.js

var foo = 'aaa'

console.log('a start');

function add(x,y){
  return x + y;
}

// Error:Cannot find module 'b'
// require('b')

require('./b.js');

// 推荐:可以省略后缀名
require('./b')

console.log('a end');

console.log('foo 的值是' + foo);

b.js

// 不能访问外部文件
// 外部文件也不能访问此文件
console.log('b start');

console.log(add(10,20)); // 不能获取到 a.js 中的 add 方法

var foo = 'bbb'; // 与 a.js 中的 foo 不冲突

require('./c.js');

console.log('b end');

c.js

console.log('ccc');

显示结果:
在这里插入图片描述

3. 加载与导出

  • 模块作用域默认是封闭的

  • 如何让模块与模块之间进行通信

  • 有时候,我们加载文件模块的目的不是为了简简单单的执行里面的代码,更重要的是为了使用里面的某个成员

  • require 方法有两个作用:

    • 加载文件模块并执行里面的代码
    • 拿到被加载文件模块导出的接口对象
  • 在每个文件模块中都提供了一个对象:exports

    • exports 默认是一个空对象
    • 你要做的就是把所有需要被外部访问的成员挂载到这个 exports 对象中

a.js

var bExports = require('./b');
var fs = require('fs');

console.log(bExports.foo);
console.log(bExports.add(10,20));
console.log(bExports.age);//未挂载,访问不到

bExports.readFile('./a.js');

fs.readFile('./a.js',function(err,data){
  if (err) {
    console.log('读取文件失败');
  }else{
    console.log(data.toString());
  }
})

b.js

var foo = 'bbb'

// console.log(exports);

exports.foo = 'hello'

exports.add = function(x,y){
  return x+y;
}

exports.readFile = function(path,callback){
  console.log('文件路径:',path);
}

var age = 18;

function add(x,y){
  return x-y;
}

4. 第三方模块

  • 第三方模块的标识就是第三方模块的名称(不可能有第三方模块和核心模块的名字一致)
  • npm
    • 开放人员可以把写好的框架,库发布到 npm
    • 使用者在使用的时候就可以很方便的通过 npm 来下载
  • 使用方式:var 名字 = require('npm install 包名)
  • node_modules
    • node_modules/express
    • node_modules/express/package.json
    • node_modules/express/package.json main
  • 如果 package.json 或者 package.json main 不成立,则查找备选项:index.js
  • 如果以上条件都不成立,则继续进入上一级目录中的 node_modules 按照上面的规则继续查找
  • 如果直到当前文件模块所属磁盘根目录都找不到,最后报错:can not find module xxx

5. http

  • require
  • 端口号
    • ip 地址定位计算机
    • 端口号定位具体的应用程序
  • Content-Type
    • 服务器最好把每次响应的数据是什么内容类型都告诉客户端,而且要正确的告诉
    • 不同的资源对应的 Content-Type 是不一样,具体参照:http://tool.oschina.net/commons
    • 对于文本类型的数据,最好都加上编码,目的是为了防止中文解析乱码问题
  • 通过网络发送文件
    • 发送的并不是文件,本质上来讲发送时文件的内容
    • 当浏览器收到服务器响应内容之后,就会根据你的 Content-Type 进行对应的解析处理

6. 异步编程

  • 如果需要得到一个函数内部异步操作的结果,这时候必须通过回调函数来获取
  • 在调用的位置传递一个函数进来
  • 在封装的函数内部调用传递进来的函数
  • 不成立的情况:
function add(x,y){
	console.log(1)
	setTimeout(function(){
		console.log(2)
		var ret = x + y
		return ret
	}, 1000)
	console.log(3)
	// 到这里执行就结束了,不会等到前面的定时器,所以直接就返回了默认值 undefined
}

console.log(add(10,20)) // => undefined
  • 不成立的情况:
function add(x,y){
	var ret
	console.log(1)
	setTimeout(function(){
		console.log(2)
		ret = x + y
	}, 1000)
	console.log(3)
	return ret
}

console.log(add(10,20)) // => undefined
  • 回调函数:
function add(x,y,callback){
	// callback 就是回调函数
	// var x = 10
	// var y = 20
	// var callback = function(ret){ console.log(ret) }
	console.log(1)
	setTimeout(function(){
		var ret = x + y
		callback(ret)
	},1000)
}

add(10,20,function(ret){
	console.log(ret)
})
  • 基于原生 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)
})

4. Web 服务器开发

1. ip 地址和端口号

  • ip地址用来定位计算机,端口号用来定位具体的应用程序
  • 一切需要联网通信的软件都会占用一个端口号,端口号的范围从 0-65536 之间
  • 在计算机中有一些默认端口号,最好不要去使用,例如 http 服务的 80
  • 可以同时开启多个服务,但一定要确保不同服务占用的端口号不一致才可以
  • 一台计算机中,同一个端口号同一时间只能被一个程序占用
  • 所有联网的程序都需要进行网络通信
  • 计算机中只有一个物理网卡,而且同一个局域网中,网卡的地址必须唯一
  • 网卡是通过唯一的 ip 地址来进行定位的
var http = require('http')

var server = http.createServer()

// 2. 监听 request 请求事件,设置请求处理函数
server.on('request',function(req,res){
  console.log('收到请求了,请求路径是:' + req.url);
  console.log('请求我的客户端的地址是:',req.socket.remoteAddress,req.socket.remotePort);

  res.end('hello nodejs')
})

server.listen(3000,function(){
  console.log('服务器启动成功,可以访问!');
})

2. Content-Type 响应内容类型

  • http://tool.oschina.net/
  • 在服务器默认发送的数据,其实是 utf8 编码的内容,但是浏览器不知道你是 utf8 编码的内容
  • 浏览器在不知道服务器响应内容的编码的情况下会按照当前操作系统的默认编码去解析
  • 中文操作系统默认是 gbk
  • 解决方法就是正确的告诉浏览器我给你发送的内容是什么编码的
  • 在 http 协议中,Content-Type 就是用来告知对方我发送的数据内容是什么类型
 var http = require('http')

 var server = http.createServer()

 server.on('request',function(req,res){
   // 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(3000,function(){
   console.log('Server is running');
 })

3. 发送文件数据

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

var server = http.createServer()

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

  if (url === '/') {
    // 肯定不这么干
    // res.end('<!DOCTYPE html><html lang="]" dir="ltr"><head><meta charset="utf-8"><title></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 === '/img'){
    // url: 统一资源定位符
    // 一个 url 最终其实是要对应到一个资源的
    fs.readFile('./resource/1.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(8000, function() {
  console.log('Server is running');
})

4. 实现 Apache 目录列表渲染

  • 如何得到 wwwDir 目录列表中的文件名和目录名
    • fs.readdir
  • 如何将得到的文件名和目录名替换到 template.html 中
    • 在 template.html 中需要替换的位置预留一个特殊的标记(就像以前使用模版引擎的标记一样)
    • 根据 files 生成需要的 HTML 内容
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')
    }

    fs.readdir(wwwDir,function(err,files){
      if(err){
        return res.end('can not find www dir')
      }

      // 2.1 生成需要替换的内容
      var content = ''
      files.forEach(function(item){
        // 在 ES6 中的 ` 字符串中,可以使用 ${} 来引用变量
        content += `
          <tr>
            <td data-value="apple/"><a class="icon dir" href="/D:/Movie/www/apple/">${item}/</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)

      console.log(data);

      // 3. 发送解析替换过后的响应数据
      res.end(data)
    })

    
  })
})

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

5. 在 Node 中使用模板引擎

  • 在 node 中使用模版引擎

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

  • 安装:

npm install art-template
  • 该命令在哪里执行就会把包下载到哪里,默认会下载到 node_modules 目录中

  • node_modules 不要改,也不支持改

  • 在需要使用的文件模块中加载 art-template

    • 只需要使用 require 方法加载就可以了: require(‘art-template’)
    • 参数中的 art-template 就是你下载的包的名字
    • 也就是说你 install 的名字是什么,则你 require 中的就是什么
  • 查文档,使用模版引擎的 API

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

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:[
      '写代码',
      '唱歌',
      '打篮球'
    ]
  })

  console.log(ret);
})

6. 统一处理静态资源

  • app application 应用程序
  • 把当前模块所有的依赖项都声明在文件模块最上面
  • 为了让目录结构保持统一清晰,所以我们约定,把所有的 HTML 文件都放到 views(视图)
  • 我们为了方便的统一处理这些静态资源,所以我们约定把所有的静态资源都存放在 public 的目录中
  • 哪些资源能被用户访问,哪些资源不能被用户访问,我现在可以通过代码来进行非常灵活的
  • 统一处理:
    • 如果请求路径是以 /public/ 开头的,则我认为你要获取 public 中的某个资源
    • 所以我们就直接可以把请求路径当作文件路径来直接进行读取
var http = require('http')
var fs = require('fs')

http
    .createServer(function(req,res){
      var url = req.url
      if(url === '/'){
        fs.readFile('./views/index.html',function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }else if (url.indexOf('/public/') === 0) {
        fs.readFile('.' + url,function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }
    })
    .listen(5000,function(){
      console.log('running');
    })

7. 客户端渲染与服务端渲染

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

5. 留言本案例

1. 页面跳转及404处理

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

http
    .createServer(function(req,res){
      var url = req.url
      if(url === '/'){
        fs.readFile('./views/index.html',function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }else if(url === '/post.html'){
        fs.readFile('./views/post.html',function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }else if (url.indexOf('/public/') === 0) {
        fs.readFile('.' + url,function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }else{
        // 其他的都处理成 404 找不到
        fs.readFile('./views/404.html',function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }
    })
    .listen(5000,function(){
      console.log('running');
    })

2. 渲染评论首页

  • html
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title></title>
    <link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.css">
    <link rel="stylesheet" href="/public/css/main.css">
  </head>
  <body>
    <div class="header-container">
      <div class="page-header">
        <h1>Example page header <small>Subtext for header</small></h1>
        <a href="/post.html" class="btn btn-success">发表留言</a>
      </div>
    </div>
    <div class="comments container">
      <ul class="list-group">
        {{each comments}}
        <li class="list-group-item">{{ $value.name }}说:{{ $value.message }}
          <span class="pull-right">{{ $value.dateTime }}</span>
        </li>
        {{/each}}

      </ul>
    </div>
  </body>
</html>

  • js
var http = require('http')
var fs = require('fs')
var template = require('art-template')

// 模拟数据
var comments = [
  {
    name: '张三',
    message: '今天天气不错',
    dateTime: '2015-10-16'
  },
  {
    name: '张三2',
    message: '今天天气不错',
    dateTime: '2015-10-16'
  },
  {
    name: '张三3',
    message: '今天天气不错',
    dateTime: '2015-10-16'
  },
  {
    name: '张三4',
    message: '今天天气不错',
    dateTime: '2015-10-16'
  },
  {
    name: '张三5',
    message: '今天天气不错',
    dateTime: '2015-10-16'
  },
  {
    name: '张三6',
    message: '今天天气不错',
    dateTime: '2015-10-16'
  },
]


http
    .createServer(function(req,res){
      var url = req.url
      if(url === '/'){
        fs.readFile('./views/index.html',function(err,data){
          if(err){
            return res.end('404 not found')
          }
          var htmlStr = template.render(data.toString(),{
            comments:comments
          })
          res.end(htmlStr)
        })
      }else if(url === '/post.html'){
        fs.readFile('./views/post.html',function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }else if (url.indexOf('/public/') === 0) {
        fs.readFile('.' + url,function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }else{
        // 其他的都处理成 404 找不到
        fs.readFile('./views/404.html',function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }
    })
    .listen(5000,function(){
      console.log('running');
    })

3. 处理表单get请求

  • 对于这种表单提交的请求路径,由于其中具有用户动态填写的内容
  • 所以你不可能通过去判断完整的 url 路径来处理这个请求
  • 结论:对于我们来讲,其实只需要判定,如果你的请求路径是 /pinglun 的时候,那我就认为你提交的表单已经过来了
  • 注意:这个时候无论 /pinglun?xxx 之后是什么,我都不用担心了,
    • 因为我的 pathname 是不包含 ? 之后的那个路径
    • 一次请求对应一次响应,响应结束这次请求也就结束了
    • 我们已经使用 url 模块的 parse 方法把请求路径中的查询字符串给解析成一个对象了
  • 所以接下来要做的就是:
    • 获取表单提交的数据 parseObj.query
    • 将当前时间日期添加到数据对象中,然后存储到数组中
    • 让用户重定向跳转到首页 /
    • 当用户重新请求 / 的时候,我数组中的数据已经发生变化了,所以用户看到的页面也就变了
http
    .createServer(function(req,res){ // 简写方式
      // 使用 url.parse 方法将路径解析为一个方便操作的对象
      // 第二个参数为 true 表示直接将查询字符串转为一个对象
      // 通过 (query)属性来访问
      var parseObj = url.parse(req.url,true)

      // 单独获取不包含查询字符串的路径部分(该路径不包含 ? 之后的内容)
      var pathname = parseObj.pathname


      if(pathname === '/'){ // 首页
        fs.readFile('./views/index.html',function(err,data){
          if(err){
            return res.end('404 not found')
          }
          var htmlStr = template.render(data.toString(),{
            comments:comments
          })
          res.end(htmlStr)
        })
      }else if(pathname === '/post'){ // 发表评论
        fs.readFile('./views/post.html',function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }else if (pathname.indexOf('/public/') === 0) {
        // 统一处理:
        //    如果请求路径是以 /public/ 开头的,则我认为你要获取 public 中的某个资源
        //    所以我们就直接可以把请求路径当作文件路径来直接进行读取
        fs.readFile('.' + pathname,function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }else if(pathname === '/pinglun'){
        var comment = parseObj.query
        comment.dateTime = '2020'
        comments.unshift(comment)
        // 服务器这个时候已经把数据存储好了,接下来就是让用户重新请求 / 首页,就可以看到最新的留言内容了

        // 通过响应头来实现服务端重定向
        res.writeHead(302,{
            'Location': '/'
        })
        res.end()
      }else{
        // 其他的都处理成 404 找不到
        fs.readFile('./views/404.html',function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }
    })
    .listen(5000,function(){
      console.log('running');
    })

4. 表单提交重定向

  • 如何通过服务器让客户端重定向?
    • 状态码设置为 302 临时重定向
      • statucCode
    • 在响应头中通过 Location 告诉客户端往哪儿重定向
      • setHeader
  • 如果客户端发现收到服务器的响应的状态码是 302 就会自动去响应头中找 Location
  • 然后对该地址发起新的请求
  • 所以你就能看到客户端自动跳转了
else if (pathname.indexOf('/public/') === 0) {
        // 统一处理:
        //    如果请求路径是以 /public/ 开头的,则我认为你要获取 public 中的某个资源
        //    所以我们就直接可以把请求路径当作文件路径来直接进行读取
        fs.readFile('.' + pathname,function(err,data){
          if(err){
            return res.end('404 not found')
          }
          res.end(data)
        })
      }else if(pathname === '/pinglun'){
        var comment = parseObj.query
        comment.dateTime = '2020'
        comments.unshift(comment)
        // 服务器这个时候已经把数据存储好了,接下来就是让用户重新请求 / 首页,就可以看到最新的留言内容了

        // 通过响应头来实现服务端重定向
        res.writeHead(302,{
            'Location': '/'
        })
        res.end()
      }

6. Node 中的模块系统

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

    • EcmaScript 语言
      • 和浏览器不一样,在 Node 中没有 BOM,DOM
    • 核心模块
      • 文件操作的 fs
      • http 服务的 http
      • url 路径操作模块
      • path 路径处理模块
      • os 操作系统信息
    • 第三方模块
      • art-template
      • 必须通过 npm 来下载才可以使用
    • 自定义模块
  • 在 Node 中没有全局作用域的概念

  • 在 Node 中,只能通过 require方法来加载执行多个 JavaScript 脚本文件

  • require 加载只能是执行其中的代码,文件与文件之间由于是模块作用域,所以不会有污染的问题

    • 模块完全是封闭的
    • 外部无法访问内部
    • 内部也无法访问外部
  • 模块作用域固然带来了一些好处,可以加载执行多个文件,可以完全避免变量命名冲突污染的问题

  • 但是某些情况下,模块与模块实需要进行通信的

  • 在每一个模块中,都提供了一个对象:exports

  • 该对象默认是一个空对象

  • 你要做的就是把需要被外部访问使用的成员手动的挂载到 exports 接口对象中

  • 然后谁来 require 这个模块,谁就可以得到模块内部的 exports 接口对象

1. 什么是模块化

  • 文件作用域
  • 通信规则
    • 加载 require
    • 导出

2. CommonJS 模块规范

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

  • 模块作用域
  • 使用 require 方法用来加载模块
  • 使用 exports 接口对象用来导出模块中的成员
1. 加载 require
  • 语法:
var 自定义变量名称 = require('模块')
  • 两个作用:
    • 执行被加载模块中的代码
    • 得到被加载模块中的 exports 导出接口对象
2. 导出 exports
  • Node 中是模块作用域,默认文件中所有的成员只在当前文件模块有效
  • 对于希望可以被其他模块访问的成员,我们就需要把这些公开的成员都挂载到 exports 接口对象中就可以了
  • 导出多个成员(必须在对象中)
exports.a = 123
exports.b = 'hello'
exports.c = function(){
	console.log('ccc')
}
exports.d = {
	foo: 'bar'
}
  • 导出多个成员也可以这么来写:
module.exports = {
	foo: 'bar'
	add: function(){}
}
  • 导出单个成员(拿到的就是:函数,字符串):
module.exports = 'hello'

以下情况会覆盖:

module.exports = 'hello'

// 以这个为准,后者会覆盖前者
module.exports = function(x,y){
	return x + y
}

也可以这样来导出多个成员:

module.exports = {
	add: function(){
		return x + y
	},
	str: 'hello'
}
3. 原理解析

exports 和 module.exports 的一个引用:

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

exports.foo = 'bar'

// 等价于
module.exports.foo = 'bar'
4. exports 和 module.exports 的区别
  • 每个模块中都有一个 module 对象
  • module 对象中有一个 exports 对象
  • 我们可以把需要导出的成员都挂载到 module.exports 接口对象中
  • 也就是:module.exports.xxx = xxx 的方式
  • 但是每次都 module.exports.xxx = xxx 很麻烦,点儿的太多了
  • 所以 Node 为了你方便,同时在每一个模块中都提供了一个成员叫:exports
  • exports === module.exports 结果为 true
  • 所以对于:module.exports.xxx = xxx 的方式完全可以:exports.xxx = xxx
  • 当一个模块需要导出单个成员的时候,这个时候必须使用:module.exports = xxx 的方式
  • 不要使用 exports = xxx 不管用
  • 因为每个模块最终向外 return 的是 module.exports
  • exports 只是 module.exports 的一个引用
  • 所以即便你为 exports = xxx 重新赋值,也不会影响 module.exports
  • 但是有一种赋值方式比较特殊:exports = module.exports 这个用来重新建立引用关系的
module.exports = {
	a: 123
}

// 重新建立 exports 和 module.exports 之间的引用关系
exports = module.exports

exports.foo = 'bar'
5. require 方法加载规则
  • 优先从缓存加载
  • 判断模块标识
    • 核心模块
    • 第三方模块
    • 自定义模块
// 如果是非路径形式的模块标识
// 路径形式的模块
// ./ 当前目录,不可省略
// ../ 上一级目录,不可省略
// /xxx 几乎不用
// d:/a/foo.js 绝对路径绝对不用
// 首位的 / 在这里表示的是当前文件模块所属磁盘根路径
// .js 后缀名可以省略
// require('./foo.js')

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

// 第三方模块
// 凡是第三方模块都必须通过 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
// 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 module xxx
//    一个项目有且只有一个 node_modules 而且是存放到项目的根目录

3. npm

  • node package manager
1. npm 网站
  • https://www.npmjs.com
2. npm 命令行工具
  • npm 的第二层含义就是一个命令行工具,只要安装了 node 就已经安装了 npm
  • npm 也有版本这个概念
  • 可以通过在命令行中输入:
npm --version
  • 升级 npm:
npm install --global npm
3. 常用命令
  • npm init
    • 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
    • 查看指定命令的使用帮助
    • 例如我忘记了 uninstall 命令的简写了,这个时候,可以输入 npm uninstall --help 来查看使用帮助
4. 解决 npm 被墙问题

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

  • http://npm.taobao.org/
  • 淘宝的开发团队把 npm 在国内做了一个备份
  • 安装淘宝的 cnpm
// 在任意目录执行都可以
// --global 表示安装到全局,而非前目录
// --global 不能省略,否则不管用
npm install --global cnpm
  • 接下来你安装包的时候把之前的 npm 替换成 cnpm
// 这里还是走国外的 npm 服务器,速度比较慢
npm install jquery

// 使用 cnpm 就会通过淘宝的服务器来下载 jquery
cnpm install jquery
  • 如果不想安装 cnpm 又想使用淘宝的服务器来下载:
npm install jquery --registr=https://registry.npm.taobao.org
  • 但是每一次手动这样加参数很麻烦,所以我们可以把这个选项加入配置文件中:
npm config set registry https://registry.npm.taobao.org

// 查看 npm 配置信息
npm config list
  • 只要经过了上面命令的配置,则你以后所有的 npm install 都会默认通过淘宝的服务器来下载。

4. package.json

  • 建议每一个项目都要有一个 package.json 文件(包描述文件,就像产品的说明书一样),给人踏实的感觉。

  • 这个文件可以通过 npm init 的方式来自动初始化出来。
    在这里插入图片描述

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

  • 如果你的 node_modules 删除了也不用担心,我们只需要:npm install 就会自动把 package.json 中的 dependencies 中所有的依赖项都下载回来

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

5. package-lock.json

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

7. Express

  • 原生的 http 在某些方面表现不足以应对我们的开发需求,所以我们就需要使用框架来加快我们的开发效率,框架的目的就是提高效率,让我们的代码更高度统一
  • 在 Node 中,有很多 Web 开发框架,我们这里以学习 express 为主
  • http://www.expressjs.com.cn

1. Express 起步

1. 安装
npm install --save express
2. hello world
const express = require('express')
const app = express()

app.get('/',(req,res) => res.send('hello world'))

app.listen(3000,() => console.log('Example app listening on port 3000!'))
3. 基本路由:

路由器

  • 请求方法

  • 请求路径

  • 请求处理函数

  • get:

// 当你以 GET 的方法请求 / 的时候,执行对应的处理函数
app.get('/',function(req,res){
	res.send('hello world')
})
  • post:
// 当你以 POST 的方法请求 / 的时候,指定对应的处理函数
app.post('/',function(req,res){
	res.send('Got a POST request')
}
4. static-server 静态服务
// /public资源
app.use(express.static('public'))

// /static资源
app.use(express.static('files'))

// /public/xxx
app.use('/public',express.static('public'))

// /static/xxx
app.use('/static',express.static('public'))

app.use('/static',express.static(path.join(_dirname,'public')))

2. Express 安装 art-template 模版引擎

  • art-template - GitHub仓库
  • art-template 官方文档
1. 安装
npm install --save art-template
npm install --save express-art-template
2. 相关配置
app.engine('art',require('express-art-template'))
3. 如何使用
app.get('/',function(req,res){
	// express 默认会去项目中的 views 目录找 index.html
	res.render('index.html'.{
		title: 'hello world'
	})
})
  • 如果希望修改默认的 views 视图渲染存储目录,可以:
// 注意:第一个参数 views 千万不要写错
app.set('views',目录路径)

3. 在 Express 中获取表单 GET 请求参数

Express 内置了一个 API ,可以直接通过 req.query 来获取

req.query

4. 在 Express 获取表单 POST 请求体数据

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

  • 安装:
npm install --save body-parser
  • 配置:
var express = require('express')
// 0. 引包
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 来获取表单 POST 请求体数据
	res.end(JSON.stringify(req.body,null,2))
})

5. 路由模块的提取

根据不同的请求方法+请求路径设置具体的请求处理函数

  • 入口模块
    app.js
var express = require('express')
var router = require('./router')

var app = express()

// 把路由容器挂载到 app 服务中
app.use(router)

app.listen(3000,function(){
  console.log('running 3000...');
})

module.exports = app
  • 路由模块
    router.js
var express = require('express')
var fs = require('fs')

// 使用 Express 中的 Router 方法
// 1. 创建一个路由容器
var router = express.Router()

// 2. 把路由都挂载到 router 路由容器中
  router.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.')
      }
      // console.log(data); // string
      // 从文件中读取到的数据一定是字符串
      // 所以这里一定要手动转成对象
      var students = JSON.parse(data).students
      students: students
      })

    })
  })
  
// 3. 把 router 导出
module.exports = router

8. Promise

  • 无法确定顺序的代码:
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);
})
  • 回调函数
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);
    })
  })
})
  • 为了解决以上编码方式带来的问题(回调地狱嵌套),所以在EcmaScript6 中新增了一个API:Promise

1. Promise 容器概念

在这里插入图片描述

2. Promise 基本语法

var fs = require('fs')

// 在 EcmaScript 6 中新增了一个 API Promise
// Promise 是一个构造函数

// 创建 Promise 容器
// 1. 给别人一个承诺 I promise you
//    Promise 容器一旦创建,就开始执行里面的代码
var p1 = new Promise(function(resolve,reject){
  fs.readFile('./data/a.txt','utf8',function(err,data){
    if(err){
      // 失败了,承诺容器中的任务失败了
      // console.log(err);
      // 把容器的 Pending 状态变为 Rejected

      // 调用 reject 就相当于调用了 then 方法的第二个参数函数
      reject(err)
    }else{
      // 承诺容器中的任务成功了
      // console.log(data);
      // 把容器的 Pending 状态变为成功 Resolved
      // 也就是说这里调用的 resolve 方法实际上就是 then 方法传递的那个 function
      resolve(data)
    }
  })
}) 

// p1 就是那个承诺
// 当 p1 成功了 然后(then)做指定的操作
// then 方法接收的 function 就是容器中的 resolve 函数
p1  
  .then(function(data){
    console.log(data);
  },function(err){
    console.log('读取文件失败了',err);
  })

3. Promise API

var fs = require('fs')

// 在 EcmaScript 6 中新增了一个 API Promise
// Promise 是一个构造函数

// 创建 Promise 容器
// 1. 给别人一个承诺 I promise you
//    Promise 容器一旦创建,就开始执行里面的代码
var p1 = new Promise(function(resolve,reject){
  fs.readFile('./data/a.txt','utf8',function(err,data){
    if(err){
      // 失败了,承诺容器中的任务失败了
      // console.log(err);
      // 把容器的 Pending 状态变为 Rejected

      // 调用 reject 就相当于调用了 then 方法的第二个参数函数
      reject(err)
    }else{
      // 承诺容器中的任务成功了
      // console.log(data);
      // 把容器的 Pending 状态变为成功 Resolved
      // 也就是说这里调用的 resolve 方法实际上就是 then 方法传递的那个 function
      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 就是那个承诺
// 当 p1 成功了 然后(then)做指定的操作
// then 方法接收的 function 就是容器中的 resolve 函数
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 代码图示
    在这里插入图片描述
    在这里插入图片描述

4. 封装 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);
  })

9. 中间件

  • http://expressjs.com/en/guide/using-middleware.html
  • 中间件的本质就是一个请求处理方法,我们把用户从请求到响应的整个过程分发到多个中间件中去处理,这样做的目的是提高代码的灵活性,动态可扩展的
  • 同一个请求所经过的中间件都是同一个请求对象和响应对象

1. 应用程序级别中间件

  • 万能匹配(不关心任何请求路径和请求方法):
app.use(function(req,res,next){
	console.log('Time:',Date.now())
	next()
})
  • 只要是以 '/xxx'开头的
app.use('/a',function(req,res,next){
	console.log('Time:',Date.now())
	next()
})

2. 路由级别中间件

  • get:
app.get('/',function(req,res){
	res.send('Hello World!')
})
  • post:
app.post('/',function(req,res){
	res.send('Got a POST request')
})
  • put:
app.put('/user',function(req,res){
	res.send('Got a PUT request at /user')
})
  • delete:
app.delete('/user'.function(req,res){
	res.send('Got a DELETE request at /user')
})

3. 错误处理中间件

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

4. 内置中间件

  • http://expressjs.com/en/guide/using-middleware.html

5. 第三方中间件

  • http://expressjs.com/en/guide/using-middleware.html

最后

以上就是淡然翅膀为你收集整理的Node.js基础笔记的全部内容,希望文章能够帮你解决Node.js基础笔记所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(65)

评论列表共有 0 条评论

立即
投稿
返回
顶部