Nodejs自学笔记

最开始为基础部分,可以忽略
可以从模块系统部分开始查看

基础操作

  • 执行nodejs
    • 注意:再node.js中没有BOM和DOM
  • 在vscode中创建nodejs目录(项目)
    • 先创建一个文件夹,然后再vscode中将命令框进入到此文件夹下,再输入npm init – y
      PS F:\Nodejs\02\code> npm init -y
    • 此时文件夹下会自动创建一个json文件,这就是node的配置文件,其中main下面就是默认打开js的文件
        PS F:\Nodejs\02\code> npm init -y
        Wrote to F:\Nodejs\02\code\package.json:
    
        {
        "name": "code",
        "version": "1.0.0",
        "description": "",
        "main": "01-http-简易Apache.js",
        "dependencies": {
            "art-template": "^4.13.2"
        },
        "devDependencies": {},
        "scripts": {
            "test": "echo \"Error: no test specified\" && exit 1"
        },
        "keywords": [],
        "author": "",
        "license": "ISC"
        }
    
    • 在目录下创建一个js文件,此时想要运行它就要在命令行中输入node 文件名,如果要运行index.js则再命令行中输入node index.js,便执行了index.js文件
      PS F:\Nodejs\02\code> node .\06-在Apache目录中加入模板引擎.js

npm

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

npm网站

​ npmjs.com 网站 是用来搜索npm包的

npm命令行工具

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

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

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

npm install --global npm

常用命令

  • npm init(生成package.json说明书文件)
    • npm init -y(可以跳过向导,快速生成)
  • npm install
    • 一次性把dependencies选项中的依赖项全部安装
    • 简写(npm i)
  • npm install 包名
    • 只下载
    • 简写(npm i 包名)
  • npm install --save 包名
    • 下载并且保存依赖项(package.json文件中的dependencies选项)
    • 简写(npm i -S 包名)
  • npm uninstall 包名
    • 只删除,如果有依赖项会依然保存
    • 简写(npm un 包名)
  • npm uninstall --save 包名
    • 删除的同时也会把依赖信息全部删除
    • 简写(npm un -S 包名)
  • npm help
    • 查看使用帮助
  • npm 命令 --help
    • 查看具体命令的使用帮助(npm uninstall --help)

解决npm被墙问题

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

https://developer.aliyun.com/mirror/NPM?from=tnpm淘宝的开发团队把npm在国内做了一个镜像(也就是一个备份)。

安装淘宝的cnpm:

npm install -g cnpm --registry=https://registry.npm.taobao.org;
#在任意目录执行都可以
#--global表示安装到全局,而非当前目录
#--global不能省略,否则不管用
npm install --global cnpm

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

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

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

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

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

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

# 这个好像停止服务了
npm config set registry https://npm.taobao.org;
# 我使用的这种方法
npm config set registry https://registry.npm.taobao.org

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

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

package.json

每一个项目都要有一个package.json文件(包描述文件,就像产品的说明书一样)

这个文件可以通过npm init自动初始化出来


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

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

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

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

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


Is this OK? (yes) yes

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

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

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

package.json和package-lock.json

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

npm5以后才加入这个文件

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

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

模块系统

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

  • EcmaScript语言

    • 和浏览器一样,在Node中没有Bom和Dom
  • 核心模块

    • 文件操作的fs
    • http服务操作的http
    • url路径操作模块
    • path路径处理模块
    • os操作系统信息
  • 第三方模块

    • art-template
    • 必须通过npm来下载才可以使用
  • 自己写的模块

    • 自己创建的文件

模块没有全局作用域,只有模块作用域,也就是说a模块加载了b模块,但是b模块里面有a模块相同的变量名的变量,b模块不会去污染a模块,所以a模块不会有任何影响(外部访问不了内部,内部也访问不了外部)

用户自定义模块

加载require(): 它的作用就是用来加载模块的
在 Node 中,模块有三种:

  • 具名的核心模块,例如 fs、http
  • 用户自己编写的文件模块
    1. 相对路径必须加 ./
    2. 可以省略后缀名
    3. 相对路径中的 ./ 不能省略,否则报错
  • 在 Node 中,没有全局作用域,只有模块作用域
    1. 外部访问不到内部
    2. 内部也访问不到外部
    3. 默认都是封闭的

有时候,我们加载文件模块的目的不是为了简简单单的执行里面的代码,更重要是为了使用里面的某个成员,但是模块之间又不互相污染,所以我们需要使用exports来导出要使用的方法或者成员
导出exports
如果我们在a模块中导入了b模块,但是因为b模块和a模块互相不污染,所以我们想要在a模块中是不能使用b模块的,但是我们可以在b模块中设置要导出的变量、方法,然后在a模块中接收导入b模块的返回值,这样我们就可以在a模块中使用b模块的方法或者变量

a模块:

const b = require('./04-2模块的导入和导出');
// 使用b模块导出的add方法
b.add(20, 30);

b模块:

var add = (num1, num2) => {
    console.log(num1 + num2);
}
// 导出
exports.add = add;

CommonJS模块规范

JavaScript本身是不支持模块化的

  • 模块作用域

  • 使用require方法来加载模块

  • 使用exports接口对象来导出模板中的成员

加载require

语法:

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

作用:

  • 执行被加载模块中的代码
  • 得到被加载模块中的exports导出接口对象
导出exports
  • Node中是模块作用域,默认文件中所有的成员只在当前模块有效

  • 对于希望可以被其他模块访问到的成员,我们需要把这些公开的成员都挂载到exports接口对象中就可以了

导出多个成员(必须在对象中):

exports.a = 123;
exports.b = function(){
    console.log('bbb')
};
exports.c = {
    foo:"bar"
};
exports.d = 'hello'

通过以上的方法,我们想要使用模块中(假设载入模块的返回值为demoExports)的方法或者字符串就要这样写

console.log(demoExports.a) // 123
demoExports.b() // bbb

这种方法加载到的函数或者字符串都属于挂载(要通过xxx.变量名),而我们想要直接使用则需要使用module.exports来导出,但是注意是否被覆盖

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

module.exports = 'hello'

以下情况会覆盖:

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

通过以上方法导出可以直接通过加载的返回值调用

var demoExports = require('demo.js')
console.log(demoExports(1, 3)) // 4,注意,原本的'hello'被function add覆盖了

也可以通过以下方法来导出多个成员(这样也是导出一个对象,想要使用里面的方法或者字符串则也需要通过挂载的写法来使用):

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

模块原理

在每个模块里面,都默认有一个module对象,而在模块的最后,都会默认return module.exports

exports和module.exports的一个引用:

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

exports.foo = 'bar'

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

当给exports重新赋值后,exports!= module.exports

最终return的是module.exports,无论exports中的成员是什么都没用。

真正去使用的时候:
	导出单个成员:exports.xxx = xxx
	导出多个成员:module.exports 或者 modeule.exports = {}

require的加载规则:

  • 优先从缓存加载

  • 判断模块标识符

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

自定义模块

// 如果非路径形式的标识
require('fs')
// 路径形式的标识:
    // ./  当前目录 不可省略
    // ../  上一级目录  不可省略
    //  /xxx也就是D:/xxx  首位/表示的是当前文件模块所属磁盘根目录
    // 带有绝对路径几乎不用(D:/a/foo.js)
require('./a') 

核心模块

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

第三方模块

// 凡是第三方模块都必须通过npm下载(npm i node_modules),使用的时候就可以通过require('包名')来加载才可以使用
// 第三方包的名字不可能和核心模块的名字是一样的
// 既不是核心模块,也不是路径形式的模块
// 规则1:
//      先找到当前文所述目录的node_modules
//      然后找node_modules/art-template目录
//      node_modules/art-template/package.json
//      node_modules/art-template/package.json中的main属性
//      main属性记录了art-template的入口模块
//      然后加载使用这个第三方包
//      实际上最终加载的还是文件
//规则2:
//      如果package.json不存在或者mian指定的入口模块不存在
//      则node会自动找该目录下的index.js
//      也就是说index.js是一个备选项,如果main没有指定,则加载index.js文件
//      
        // 如果条件都不满足则会进入上一级目录进行查找
// 注意:一个项目只有一个node_modules,放在项目根目录中,子目录可以直接调用根目录的文件
var template = require('art-template')

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

文件操作路径:

// 咱们所使用的所有文件操作的API都是异步的
// 就像ajax请求一样
// 读取文件
// 文件操作中 ./ 相当于当前模块所处磁盘根目录
// ./index.txt    相对于当前目录
// /index.txt    相对于当前目录
// /index.txt   绝对路径,当前文件模块所处根目录
// d:express/index.txt   绝对路径
fs.readFile('./index.txt',function(err,data){
    if(err){
       return  console.log('读取失败')
    }
    console.log(data.toString())
})

模块操作路径:

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

核心模块

Node为JS提供了很多服务器级别的API,这些API绝大多数都被包装到了一个具名的核心模块中。
例如:文件操作的fs核心模块,http服务构建的http模块,path路径操作模块,os操作系统信息模块…
只要是核心模块,我们就需要使用require()方法来加载核心模块,常用的就几个
nodejsAPI参考中文网址

下载

npm install moduleName # 安装模块到项目目录下
 
npm install moduleName -g # -g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置。
 
npm install moduleName -save # -save 的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。
 
npm install moduleName -save-dev # -save-dev 的意思是将模块安装到项目目录下,并在package.json文件的devDependencies节点写入依赖。

fs 文件系统

在浏览器中js没有文件操作的能力,但是在nodejs中可以操作文件的读写

  • fs 是 file-system 的简写,就是文件系统的意思
  • 在 Node 中如果想要进行文件操作,就必须引入 fs 这个核心模块
  • 在 fs 这个核心模块中,就提供了所有的文件操作相关的 API
    例如:fs.readFile 就是用来读取文件的
// 1.先使用require()方法将fs核心模块引入
const fs = require('fs');

读取文件
使用fs.readFile()

  • 第一个参数就是要读取的文件路径
  • [第二个参数是编码集]可选的,如果加了第二个参数就不用data.toString()了
  • 第三个参数是回调函数
    成功
        data 数据
        error null
    失败
        data undefined没有数据
        error 错误对象
// 2.读取文件使用fs.readFile(); 两个参数:路径、回调参数(接受两个对象 error,data,成功error就是null,data就是数据对象,失败error就是错误对象,data就是null)
fs.readFile("./txt/a.txt", (error, data) => {
    console.log(data); // 
    // 因为我们省略了第二个参数,所以data返回的是二进制数据, 我们可以使用toString()方法转换为常见的字符
    console.log(data.toString()); // this is a 
})

写入文件
使用fs.writeFile()

  • 第一个参数:文件路径
  • 第二个参数:文件内容
  • 第三个参数:回调函数
   成功:
     文件写入成功
     error 是 null
   失败:
     文件写入失败
     error 就是错误对象
// 3.写文件使用fs.writeFile();三个参数:文件路径,文件内容,回调函数(接受error对象,成功error是null,失败就是错误对象)
fs.writeFile("txt/c.txt", "this is c", error => {
    if(error){
        console.log("写入失败");
    } else {
        console.log("写入成功");
    }
});

art-template 模板引擎

模板引擎是第三方模块 官网
让开发者以更加友好的方式拼接字符串,使项目代码更加清晰、更加易于维护

下载

  1. 在命令行工具中使用 npm install art-template命令进行下载,他会将art-template下载到你运行代码的文件夹下
  2. 使用const template = require('art-template')引入模板
  3. 在浏览器中使用则需要通过src引入node_modules/art-template/lib/template-web.js,并且不能让浏览器将模板文本当成js来解析,所以要设置type="text/template"
    <script src="node_modules/art-template/lib/template-web.js">
    script>
    <script  type="text/template">
    模板内容
    script>
    
   npm install art-template
   该命令在哪执行就会把包下载到哪里。默认会下载到 node_modules 目录中
   node_modules 不要改,也不支持改。

表达式

{{ 与 }} 符号包裹起来的语句则为模板的逻辑表达式。并且模板引擎只关心内容,就算是在字符串里面的{{}}也会被解析

输出表达式
对内容编码输出:
{{content}}
不编码输出:
{{#content}}
编码可以防止数据中含有 HTML 字符串,避免引起 XSS 攻击。

条件表达式

{{if admin}}
	<p>adminp>
{{else if code > 0}}
	<p>masterp>
{{else}}
    <p>error!p>
{{/if}}

遍历表达式
无论数组或者对象都可以用 each 进行遍历。

{{each list as value index}}
    <li>{{index}} - {{value.user}}li>
{{/each}}

亦可以被简写:

{{each list}}
    <li>{{$index}} - {{$value.user}}li>
{{/each}}

模板包含表达式
用于嵌入子模板。
{{include 'template_name'}}
子模板默认共享当前数据,亦可以指定数据:
{{include 'template_name' news_list}}
辅助方法
使用template.helper(name, callback)注册公用辅助方法:

template.helper('dateFormat', function (date, format) {
    // ..
    return value;
});

模板中使用的方式:

{{time | dateFormat:'yyyy-MM-dd hh:mm:ss'}}
支持传入参数与嵌套使用:

{{time | say:'cd' | ubb | link}}

在浏览器中使用

  • 表达式是要写在
  • 要在js语句中通过template('script标签的id', {数据对象})调用模板引擎,并返回模板值
    <!-- 模板引擎文件 -->
    <script src="node_modules/art-template/lib/template-web.js"></script>
    <!-- 确定模板 -->
    <script type='text/template' id="tpl">
            <p>大家好,我叫:{{ name }}</p>
            <p>性别:{{ sex }}</p>
            <p>我喜欢:{{each list}} {{$value}} {{/each}}</p>
    </script>
    <!-- 使用模板引擎 -->
    <script>
        let res = template('tpl', {
            name : "张三",
            sex : '男',
            list : ['c', 't', 'r', 'l']
        })
        console.log(res)
        document.getElementsByTagName('body')[0].innerHTML = res;
    </script>

在服务器中使用

核心方法

// 立即渲染模板
template.render(`模板字符串`, 替换对象)
// 引入模板引擎
const template = require('art-template')
const fs = require('fs')
// 最简单用法
let str = template.render(`hello {{name}}`, {
  name: `node.js`
})
console.log(str); // hello node.js

// 结合fs综合案例,先创建一个html文件,里面放的是模板
fs.readFile('./tpl.html', (error, data) => {
  if (error) {
    return console.log('文件读取失败')
  }
  // 默认读取到的 data 是二进制数据
  // 而模板引擎的 render 方法需要接收的是字符串
  // 所以我们在这里需要把 data 二进制数据转为 字符串 才可以给模板引擎使用
  let tplstr = template.render(data.toString(), {
    name: 'jack',
    sex: 'nan',
    like: [
      'c',
      't',
      'r',
      'l'
    ]
  })
  console.log(tplstr);
})

模板继承

当我们制作网页时,多张网页的头部和尾部都是一样的,只有中间不一样,我们就可以使用模板继承来实现

include语法

在模板引擎中也可以使用include语法,在模板一中加载模板二
语法

{{include './header.art'}}
{{include './header.art' data}}

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模板demo1title>
head>
<body>
    {{ title }}
    
    {{ include './demo2.html'}}
body>
html>

<div>
    <h2>demo2里面的内容h2>
div>

继承

这时,我们创建一个layout.html文件(layout:布局),include和block语法搭配使用

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
head>
<body>
    
    {{ include './header.html' }}

    
    {{block 'name'}}

        
        <h1>layout默认内容h1>
        
    {{/block}}

    
    {{ include './footer.html' }}

body>
html>
  • 创建index.html文件,文件中什么都不写只使用extend语法

不填坑


{{extend './layout.html'}}

填坑

{{extend './layout.html'}}


{{ block 'name'}}
    <h1>index中的填坑内容h1>
{{ /block }}

扩展:如果想要加载什么资源的话,我们只需要在layout中使用link/script标签来加载就可以了,并且可以留多个坑,而且可以没有默认内容

http 服务器

在Node中可以通过http模块非常轻松的创建一个服务器,注意不能关闭命令窗口,不然就会关闭服务器,如果想不关闭命令窗口的情况下关闭服务器按ctrl+c,可以开启多个服务,但是要保证端口号不同

最简单的http服务

// 核心模块http的职责就是用来创建服务器
const http = require('http')

// 使用http.createServer()方法返回一个Web服务器
const server = http.createServer();

// 注册request事件
// request 请求事件处理函数,需要接收两个参数:
//    Request 请求对象
//        请求对象可以用来获取客户端的一些请求信息,例如请求路径
//    Response 响应对象
//        响应对象可以用来给客户端发送响应消息
server.on("request", (request, response) => {
    console.log("收到服务器请求,请求路径是:" + request.url);// request.url是当前的请求路径
    // 设置请求头,以防止中文乱码
    response.setHeader("Content-Type", "text/plain; charset=utf-8");
    // response响应,可以多次响应,但是最后一定要使用end结束响应,不然客户端会一直等待
    // response.write("this is my response");
    // response.end();
    // 上面的方式比较麻烦,推荐使用更简单的方式,直接 end 的同时发送响应数据
    response.end('this is my response')
})

// 给服务器绑定端口号,启动服务器
server.listen(3000, function () {
    console.log("服务器启动成功,可以通过localhost:3000来进行访问");
})
  • 响应对象只能是字符串或者二进制数据,不然就会报错,如果想要将一个数组或者对象转换成字符串使用JSON.stringify(),反之则使用JSON.parse();
  • 还有请求头设置的格式:
    • text/html : HTML格式
    • text/plain :纯文本格式
    • text/xml : XML格式
    • image/gif :gif图片格式
    • image/jpeg :jpg图片格式
    • image/png:png图片格式
    • 更多可在网页查看:https://tool.oschina.net/commons

fs和http小案例

通过判断请求路径来返回不同的请求内容

const fs = require('fs');
const http = require('http');
// 创建server
const server = http.createServer();
server.on("request", function (req, resp) {
    const url = req.url;
    // 如果文件路径是/plain则返回文本内容
    if(url === '/plain'){
        resp.setHeader("content-type", "text/plain; charset=utf-8");
        fs.readFile('./txt/plain.txt', function (error, data) {
            if(error){
                console.log("文件读取失败");
            } else {
                resp.end(data)
            }
        })
    // 如果文件路径是/html则返回html内容
    } else if(url === '/html'){
        resp.setHeader("content-type", "text/html; charset=utf-8");
        fs.readFile('./txt/html.html', function (error, data) {
            if(error){
                console.log("文件读取失败");
            } else {
                resp.end(data);
            }
        })
    // 除了/plain和/html其他都返回404
    } else {
        resp.end("404");
    }
})
// 启动服务器
server.listen(3000, function () {
    console.log("服务器启动成功");
})

仿照Apache

以前使用过 Apache 服务器软件,这个软件默认有一个 www 目录,所有存放在 www 目录中的资源都可以通过网址来浏览,默认访问index.html
简易逻辑仿写

const http = require('http')
const fs = require('fs')

// 创建server
const server = http.createServer()

// 监听server的request请求,并设置处理函数
// 创建服务器的根目录
const wwwDir = 'F:/Nodejs/02/videos'
server.on('request', (req, resp) => {
    const url = req.url
    // 当访问路径为'/'或者'/index.html',则都进入目录下index.html
    if (url === '/' || url === '/index.html') {
        fs.readFile(wwwDir + '/index.html', (error, data) => {
            if (error) {
                resp.end('404')
            } else {
                resp.end(data)
            }
        })
    // 假设服务器只有index.html
    } else {
        resp.end('404')
    }
})

//绑定端口号,启动服务器
server.listen('3000', () => {
    console.log('服务器启动')
}) 

简易功能仿写

const http = require('http')
const fs = require('fs')

const server = http.createServer()

// 服务器根目录
const wwwDir = 'F:/Nodejs/02/videos'
server.on('request', (req, res) => {
    const url = req.url
    let filePath = '/index.html'
    // 判断是否为/
    if (url !== '/') {
        // 如果是/则直接访问/index.html,不是则访问指定路径
        filePath = url
    }

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

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

Apache目录列表
以下只能将所有文件识别成文件夹,就算是index.html也会识别成index.html/文件夹

// 简易实现Apache功能
const http = require('http')
const fs = require('fs')
const server = http.createServer()
// 服务器根目录
const wwwDir = 'F:/Nodejs/02/www'
server.on('request', (req, resp) => {
    const url = req.url
    // 首先读取查看目录的html
    fs.readFile('./template.html', (error, data) => {
        if (error) {
            return resp.end('404 Not Found.')
        }
        // 使用fs.readdir读取目录
        fs.readdir(wwwDir, (error, files) => {
            // 如果报错则则直接 Can not find www dir
            if (error) {
                return resp.end('Can not find www dir.')
            }
            // 生成需要替换的内容
            let content = ``
            // 使用foreach和模板字符串生成需要拼接的内容
            files.forEach(item => {
                content += `
                    
                        ${item}/
                        
                        2017/11/2 上午10:32:47
                    
                `
            });
            // 替换,先将二进制toString一下,^_^是替换标记
            data = data.toString();
            data = data.replace('^_^', content)
            // 响应数据
            resp.end(data)
        })
    })
})
// 3. 绑定端口号,启动服务
server.listen(3000, function () {
    console.log('running...')
})

使用模板引擎替换字符串拼接

// 简易实现Apache功能
const http = require('http')
const fs = require('fs')
const template = require('art-template')

const server = http.createServer()
// 服务器根目录
const wwwDir = 'F:/Nodejs/02/www'
server.on('request', (req, resp) => {
    const url = req.url
    // 首先读取查看目录的html
    fs.readFile('./template-tpl.html', (error, data) => {
        if (error) {
            return resp.end('404 Not Found.')
        }
        // 使用fs.readdir读取目录
        fs.readdir(wwwDir, (error, files) => {
            // 如果报错则则直接 Can not find www dir
            if (error) {
                return resp.end('Can not find www dir.')
            }
            // 使用模板引擎
            data = template.render(data.toString(), {
                title : wwwDir,
                files : files
            })
            // 响应数据
            resp.setHeader("content-type", "text/html; charset=utf-8")
            resp.end(data)
        })
    })
})
// 3. 绑定端口号,启动服务
server.listen(3000, function () {
    console.log('running...')
})

http简写

// http简写方式
http.createServer(function(req, resp){ // 默认会注册request请求
    resp.end("fangwen")
}).listen(3000, function () {
    console.log('running...');
})

路径问题

浏览器收到 HTML 响应内容之后,就要开始从上到下依次解析,
当在解析的过程中,如果发现:
    link
    script
    img
    iframe
    video
    audio
等带有 src 或者 href(link) 属性标签(具有外链的资源)的时候,浏览器会自动对这些资源发起新的请求。
    注意:在服务端中,文件中的路径就不要去写相对路径了。
    因为这个时候所有的资源都是通过 url 标识来获取的
    我的服务器开放了 /public/ 目录
    所以这里的请求路径都写成:/public/xxx
    / 在这里就是 url 根路径的意思。
    浏览器在真正发请求的时候会最终把 http://127.0.0.1:3000 拼上
    不要再想文件路径了,把所有的路径都想象成 url 地址
    

现在假设index.html在views文件里,但是访问的资源在public文件夹里,不在服务器中则可以使用…/public/index.css来进行访问,但是在服务器中需要使用/public/index.css来访问,浏览器在真正发请求的时候会最终把 http://127.0.0.1:3000 拼上

重定向

  1. 将状态码设置成302
    • statusCode = 302
  2. 在响应头中设置服务器往哪重定向
    • setHeader()
// 如果客户端发现收到服务器的响应的状态码是 302 就会自动去响应头中找 Location ,然后对该地址发起新的请求
// 所以你就能看到客户端自动跳转了
res.statusCode = 302
// 第二个参数是设置重定向去哪,/默认代表重定向到本服务器的/,如果想要重定向去百度则将'/'改为'https://www.baidu.com/'
res.setHeader('Location', '/')
res.end()

url 网址

创建方法
new URL(input[, base])

  • input 要解析的绝对或相对的输入 URL。 如果 input 是相对的,则需要 base。 如果 input 是绝对的,则忽略 base。
  • base | 如果 input 不是绝对的,则为要解析的基本 URL。

通过相对于 base 解析 input 来创建新的 URL 对象。 如果 base 作为字符串传入,则其将被解析为等效于 new URL(base)。

const myURL = new URL('/foo', 'https://example.org/');
// https://example.org/foo

URL 构造函数可作为全局对象的属性访问。 也可以从内置的 url 模块中导入:

console.log(URL === require('url').URL); // 打印 'true'.

如果 input 或 base 不是有效的 URL,则将抛出 TypeError。 注意,会将给定的值强制转换为字符串。 例如:

const myURL = new URL({ toString: () => 'https://example.org/' });
// https://example.org/

出现在 input 的主机名中的 Unicode 字符将使用 Punycode 算法自动转换为 ASCII。

const myURL = new URL('https://測試');
// https://xn--g6w251d/

只有在启用 ICU 的情况下编译 node 可执行文件时,此功能才可用。 如果不是,则域名将原封不动地传入,如果事先不知道 input 是否是绝对 URL 并且提供了 base,则建议验证 URL 对象的 origin 是否符合预期。

let myURL = new URL('http://Example.com/', 'https://example.org/');
// http://example.com/

myURL = new URL('https://Example.com/', 'https://example.org/');
// https://example.com/

myURL = new URL('foo://Example.com/', 'https://example.org/');
// foo://Example.com/

myURL = new URL('http:Example.com/', 'https://example.org/');
// http://example.com/

myURL = new URL('https:Example.com/', 'https://example.org/');
// https://example.org/Example.com/

myURL = new URL('foo:Example.com/', 'https://example.org/');
// foo:Example.com/

实际情况使用

// new一个url实例,第一个参数是请求的url,第二个参数是服务器信息端口号
let urldata = new URL(req.url, 'http://localhost:3000/')
// 获取请求路径,就算后面带有参数也只获取请求路径
let pathname = urldata.pathname 

let urldata = new URL('/foo?name=张三&age=18', 'http://localhost:3000/');
// 获取发送请求时url栏夹带的参数对象
console.log(urldata.searchParams);//URLSearchParams { 'name' => '张三', 'age' => '18' }
// 使用get()获取夹带的name值
console.log(urldata.searchParams.get('name')) // 张三

path 路径操作模块

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

  • path.basename(path, [ext]):获取路径的文件名,默认包含扩展名
    • path:文件路径
    • 扩展名,要是有参数就会删除默认的文件扩展名
  • path.dirname:获取路径中的目录部分
  • path.extname:获取一个路径中的扩展名部分
  • path.parse(path):把路径转换为对象
    • path为传输的路径,以下为返回的对象
    • root:根路径
    • dir:目录
    • base:包含后缀名的文件名
    • ext:后缀名
    • name:不包含后缀名的文件名
  • path.join(path1, path2, …):拼接路径
    • 将多个路径拼接再一起
  • path.isAbsolute:判断一个路径是否为绝对路径
    • 返回值true/false

Node中的其它成员(__dirname,__filename)

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

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

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

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

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

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

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

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

假设有一个文件夹(demo)里有a.txt和main.js,main.js中书写了文件读取a.txt文件

var fs = require('fs');

fs.readFile('./a.txt','utf8',function(err,data){
	if(err){
		throw err
	}
	console.log(data);
});

此时我们再控制台中运行main.js

PS F:\\Nodejs\\06\\demo> node main.js
# 此时我们可以读取到a.txt里面的内容
但是我们使用以下方法就不可以读取到文件内容
PS F:\\Nodejs\\06\\> node demo/main.js

这就是node中文件操作的路径是被设计为相对于执行node命令所处的位置,这时候'./a.txt'就不是06/demo/a.txt了,而是06/a.txt

所以我们可以使用Node中的__dirname和path模块中的join方法搭配使用来避免这种错误

console.log(__dirname); // 不包含文件名的绝对路径 F:\Nodejs\06
console.log(__filename); // 包含文件名的绝对路径 F:\Nodejs\06\01-__dirname和__filename.js
var fs = require('fs');
var path = require('path');

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

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

注意:

模块中的路径标识和文件操作中的相对路径标识不一致

模块中的路径标识就是相对于当前文件模块,不受node命令所处路径影响

Express

原生的http模块在某些方面表现不足以应对我们的开发需求,所以就需要使用框架来加快我们的开发效率,框架的目的就是提高效率,让我们的代码高度统一。Express首页,主要封装http核心模块

安装

# 通过npm来安装express,保存到依赖文件中
npm i -S express

基础操作HelloWorld

// 1.导包
const express = require('express')

// 2.创建服务器
//      也就是原来的http.createServer()
const app = express()

// 公开指定目录,此时当访问/public/时,当前文件夹下的public文件夹就会被公开
app.use('/public/', express.static('./public/'))

// 3.设置请求
// 当服务器收到 get 请求的 / 的时候,执行回调处理函数
app.get('/' , (req , res)=>{
    res.send('HelloWorld')
})
// 当服务器收到 get 请求的 /about 的时候,执行回调处理函数
app.get('/about' , (req , res)=>{
    // 可以直接通过req.query获得参数对象
    console.log(req.query)
    res.send('关于我') // express已经处理好了字符编码的问题,但是要使用res.send(),使用原先的end()则还是需要自己设置
})
app.get('/cdx', (req, res) => {
    // 使用express中的重定向至localhost:3000/about
    res.redirect('/about')
})

// 4.注册端口号
//      相当于server.listen()
app.listen(3000, () => {
    console.log('app running...');
})

基本路由

get:

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

post:

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

send()和end()的区别

// 1, 参数类型的区别:
res.send() // 参数可以是 buffer, string, object , array
res.end() // 参数类型只能buffer对象或者字符串
// 2, res.send() 响应报文头不同
res.end() //只有Content-Type: text/html; charset=utf-8
res.send() //可以自动加响应头

静态资源API

// app.use不仅仅是用来处理静态资源的,还可以做很多工作(body-parser的配置)
app.use(express.static('public'));

app.use(express.static('files'));

app.use('/stataic',express.static('public'));
// 引入express
var express = require('express');

// 创建app
var app = express();

// 开放静态资源
// 1.当以/public/开头的时候,去./public/目录中找对应资源
// 访问:http://127.0.0.1:3000/public/login.html
app.use('/public/',express.static('./public/')); 

// 2.当省略第一个参数的时候,可以通过省略/public的方式来访问
// 访问:http://127.0.0.1:3000/login.html
// app.use(express.static('./public/'));   

// 3.访问:http://127.0.0.1:3000/a/login.html
// a相当于public的别名
// app.use('/a/',express.static('./public/')); 

//  
app.get('/',function(req,res){
    res.end('hello world');
});

app.listen(3000,function(){
    console.log('express app is runing...');
});

在Express中使用art-template

art-template官网中介绍Express的用法
安装
先要安装好express,然后再安装以下两个包

npm install --save art-template
npm install --save express-art-template
# 也可以简写成
npm i art-template express-art-template

配置
注意,如果第一个参数是'art',这时候模板的后缀一定得是.art,不能使用.html;要是不想使用以art结尾的文件的话,就需要修改engine()的第一个参数了

// 配置使用art-template模板引擎
// 第一个参数,表示,当渲染以 .art 结尾的文件的时候,使用 art-template 模板引擎
// express-art-template 是专门用来在 Express 中把 art-template 整合到 Express 中
// 虽然外面这里不需要引入 art-template 但是也必须安装,原因就在于 express-art-template 依赖了 art-template
app.engine('art', require('express-art-template'))

// 如果想要修改默认的 views 目录,则可以使用以下方法,使用此方法需要导入path核心模板
// 参数1:代表将要修改views,第二个参数表示将原本指:项目/views/资源,换成其他的文件夹
// app.set('views',  path.join(__dirname, render函数的默认路径))
app.set('views', path.join(__dirname, 'mobanwenjian'))// 将原本从项目目录下的views目录找资源换成了从项目目录下的mobanwenjian找资源

使用
注意:render方法的第一个参数是不能写路径的,默认会去项目中的views目录查找以.art结尾的模板文件,也就是说使用Express有一个约定就是开发人员要将所有的视图文件都放在views目录中

// 使用
app.get('/', (req, res) => {
    // Express 为 Response 相应对象提供了一个方法:render
    // render 方法默认是不可以使用,但是如果配置了模板引擎就可以使用了
    // res.render('html模板名', {模板数据})
    // 第一个参数不能写路径,默认会去项目中的 views 目录查找该模板文件
    // 也就是说 Express 有一个约定:开发人员把所有的视图文件都放到 views 目录中
    res.render('./moban.art', {// 由于一开始就是从views文件夹下查找,所以./moban.art就代表是views/moban.art文件
        name: 'demoName',
        sex: 'nan',
        age: '10',
        likes: ['HTML', 'css', 'javascript']
    })
})

获取请求体数据

get
get的请求体数据可以直接通过req.query获取

app.get('/' , (req , res)=>{
//    get方法可以直接使用req.query获取请求数据对象
    console.log(req.query)
})

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

安装:

npm install --save body-parser

配置:

// 配置解析表单 POST 请求体插件(注意:一定要在 app.use(router) 之前 )
var express = require('express')
// 引包
var bodyParser = require('body-parser')

var app = express()

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

使用:

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

案例

const express = require('express')
const bodyParser = require('body-parser')
const app = express()

// 配置body-parser
// 只要加入这个配置,则在req请求对象上会多出来一个属性:body
// 也就是说可以直接通过req.body来获取表单post请求数据
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use('/public/', express.static('./public/'))
// 获取post请求体数据
app.post('/demo' , (req , res)=>{
    // req.body获取对象,req.body.属性名,获取具体属性值
    console.log(req.body)
    res.send()
})

// 获取get请求体数据
app.get('/demo' , (req , res)=>{
    //    get方法可以直接使用req.query获取请求数据对象
    console.log(req.query)
    res.send()
})

app.get('/' , (req , res)=>{
   res.redirect('/public/qingqiu.html')
})

app.listen(3000, () => console.log('服务器运行'))

express中的增删改查

我们通过一个案例来学习如何设计路由
在这里我们通过JSON来模拟查询数据库文件
读取数据

const express = require('express')
const fs = require('fs')

const app = express()
app.engine('html', require('express-art-template'))
app.use('/public/', express.static('./public/'))
app.use('/node_modules/', express.static('../node_modules/'))

app.get('/', (req, res) => {
    fs.readFile('./db.json', (err, data) => {
        if (err) {
            res.send('没有数据')
        }
        // 使用parse将String转换成Object
        let jsondata = JSON.parse(data.toString())
        let students = jsondata.students
        res.render('./index.html', {
            shuiguo: ['苹果', '香蕉'],
            students: students
        })
    })

})
app.listen(3000, () => console.log('服务器启动'))

路由设计

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

router 路由

// 在不使用router模块的情况下,用以下方法写路由
module.exports = (app) => {
    // /students 渲染首页
    app.get('/students', (req, res) => {

    })
}

当我们将路由设计写成一个核心模块的时候,我们想要将router模块加载到入口模块的时候就比较麻烦,所以express给我们提供了第三方模块router

// router.js写法
// 0. 加载核心模块express
var express = require('express')
// 1. 创建一个路由容器
var router = express.Router()
// 2.将所有的路由都挂载到router下
// /students 渲染首页
router.get('/students', (req, res) => {

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


// app.js写法
const router = require('./router')
// 将router挂载到app服务器上
app.use(router)

文件的修改

由于每次添加学生或者修改学生会经常使用到readFile和writeFile方法,所以我们可以将这些方法封装成一个核心模块students.js

// student.js  对db.json进行增删改查
const fs = require('fs')

// 获取学生列表
exports.find = () => {
}

// 增加学生
exports.add = () => {
}

// 更新学生列表
exports.updata = () => {
}

// 删除学生
exports.delete = () => {
}

由于在函数外部是无法获取里面的值的,所以我们需要使用回调函数来进行处理

案例代码

注意:

  • 由于文件操作是异步请求,所以需要使用回调函数,而数据如何去使用,则调用方法的时候在回调函数中设置
  • 获取请求数据GET是用req.query,POST是用req.body
    服务器入口代码
const express = require('express')
const bodyParser = require('body-parser')
const router = require('./router')

const app = express()

// 配置模板引擎
app.engine('html', require('express-art-template'))
// 配置bodyParser
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

// 公开资源
app.use('/public/', express.static('./public/'))
app.use('/node_modules/', express.static('../node_modules/'))

// 将router挂载到app服务器上
app.use(router)

app.listen(3000, () => console.log('服务器启动'))

路由代码

// 路由处理模块
// Express 提供了一种更好的方式
// 专门用来包装路由的
const express = require('express')
const students = require('./students.js')

// 1. 创建一个路由容器
const router = express.Router()

// 2.将所有的路由都挂载到router下
router.get('/', (req, res) => {
    res.redirect('/students')
})
// /students 渲染首页
router.get('/students', (req, res) => {
    students.find((err, data) => {
        res.render('./index.html', {
            students: data
        })
    })
})

// /students/new 渲染添加学生页面
router.get('/students/new', (req, res) => {
    res.render('./new.html')
})

// /students/new 处理添加学生请求
router.post('/students/new', (req, res) => {
    // 获取post数据
    let student = req.body
    // 处理数据:将数据追加到db.json中,先读取文件,将文件转换成对象,往对象中push数据,在将对象写入文件
    students.add(student, (err) => {
        if (err) {
            return res.status(500).send('Server error')
        }
        res.redirect('/students')
    })
})

// /students/edit 处理编辑请求
router.post('/students/edit', (req, res) => {
    students.updata(req.body, (err) => {
        if (err) {
            return res.status(500).send('Server error')
        }
        res.redirect('/students')
    })
})

// /students/edit  渲染编辑页面
router.get('/students/edit', (req, res) => {
    students.findById(parseInt(req.query.id), (err, data) => {
        if (err) {
            return res.status(500).send('Server error')
        }
        res.render('./edit.html', {
            student : data
        })
    })
})


// /students/delete 处理删除请求
router.get('/students/delete', (req, res) => {
    students.deleteById(parseInt(req.query.id), (err) => {
        if (err) {
            return res.status(500).send('Server error')
        }
        res.redirect('/students')
    })
})

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

操作文件代码

// 对db.json进行增删改查
const fs = require('fs')
const dbPath = './db.json'

/**
 * 获取学生列表
 * @param {Function}} callback(err, data) 等到调用的时候我们就需要往里传递一个函数,这个传进去的函数里面再定义处理数据的方法,并且由于是异步请求,如果想在函数体外获得函数体内部的数据,就需要使用回调函数,如果直接将数据return出来,便会是undefined
 */
exports.find = (callback) => {
    fs.readFile(dbPath, 'utf-8', (err, data) => {
        if (err) {
            return callback(err)
        }
        callback(null, JSON.parse(data).students)
    })
}

/**
 * 通过id查找对应的学生
 * @param {Number} id 学生id
 * @param {Function} callback(err, data) 回调函数
 */
exports.findById = (id, callback) => {
    fs.readFile(dbPath, 'utf-8', (err, data) => {
        if (err) {
            return callback(err)
        }
        const students = JSON.parse(data).students
        const stu = students.find((item) => {
            return parseInt(item.id) === id
        })
        callback(null, stu)
    })
}


/**
 * 保存增加学生
 * @param {Object} student 
 * @param {Function} callback(err) 回调函数
 */
exports.add = (student, callback) => {
    fs.readFile(dbPath, 'utf-8', (err, data) => {

        if (err) {
            return callback(err)
        }

        // 将JSON字符串转换成对象
        let students = JSON.parse(data).students

        // 处理id
        student.id = parseInt(students[students.length - 1].id) + 1

        students.push(student)

        // 将数据转换成json格式
        const fileData = JSON.stringify({
            students: students
        })

        // 写入数据
        fs.writeFile(dbPath, fileData, (err) => {
            if (err) {
                return callback(err)
            }
            callback(null)
        })
    })
}

/**
 * 更新学生列表
 * @param {Object} student 需要修改的学生对象
 * @param {Function} callback 回调函数
 */
exports.updata = (student, callback) => {
    fs.readFile(dbPath, 'utf-8', (err, data) => {
        if (err) {
            return callback(err)
        }

        let students = JSON.parse(data).students

        // 使用ES6里面的find方法
        let stu = students.find((item) => {
            return parseInt(item.id) === parseInt(student.id)
        })

        // 更新数据
        for (const key in stu) {
            stu[key] = student[key]
        }

        // 将数据转换成json格式
        const fileData = JSON.stringify({
            students: students
        })

        // 写入数据
        fs.writeFile(dbPath, fileData, (err) => {
            if (err) {
                return callback(err)
            }
            callback(null)
        })
    })
}

/**
 * 删除学生
 * @param {Number} id 学生id
 * @param {Function} callback(err) 回调函数
 */
exports.deleteById = (id, callback) => {
    fs.readFile(dbPath, 'utf-8', (err, data) => {
        if (err) {
            return callback(err)
        }
        const students = JSON.parse(data).students

        let index = students.findIndex((item) => {
            return parseInt(item.id) === parseInt(id)
        })

        // 使用splice删除
        students.splice(index, 1)

        // 将数据转换成json格式
        const fileData = JSON.stringify({
            students: students
        })

        // 写入数据
        fs.writeFile(dbPath, fileData, (err) => {
            if (err) {
                return callback(err)
            }
            callback(null)
        })
    })
}

express和Ajax

在浏览器默认情况下,表单的提交是默认行为(设置method、action),浏览器会锁死(转圈),等待服务器端响应,但是通过ajax可以使表单的的提交行为为异步请求。同步表单提交的请求浏览器会将服务器返回的内容直接渲染到页面上

普通的get/post请求为同步请求,刷新整个页面,而Ajax是异步请求,可以局部刷新页面,在此处随便书写两个示范

  • html页面示范
DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>异步请求title>
head>

<body>
    <form id="demoform">
        <input type="text" name="name" id="name">
        <input type="text" name="password" id="password">
        <input type="submit" value="提交">
    form>
    <script src="/node_modules/jquery/dist/jquery.js">script>
    <script>
        $("#demoform").on('submit', (e) => {
            // 禁止浏览器默认的表单同步提交方式
            e.preventDefault()
            // 提取表单数据
            const name = $('#name').val()
            const password = $('#password').val()
            // 像服务器发送url为/ajaxdemo的post请求,并且传递数据data
            $.ajax({
                type: "post",
                url: "/ajaxdemo",
                // 将表单数据封装为一个对象,传递给服务器
                data: {
                    name: name,
                    passowrd: password
                },
                // 接受json数据
                dataType: "JSON",
                success: (response) => {
                    // 再此处只是随意做了一下返回值的处理,不符合逻辑
                    if (response.success === true) {
                        alert(response.message)
                    }
                    if (response.success === false) {
                        alert(response.message)
                    }
                    // 异步请求中服务端的重定向无效,需要使用客户端重定向
                    // window.location.href = '/'
                }
            })
        })
    script>
body>
html>
  • node代码
const express = require('express')
const path = require('path')
const bodyParser = require('body-parser')

const app = new express()

// 开放资源
app.use('/node_modules/', express.static(path.join(__dirname, '../node_modules/')))
// 配置art-template
app.engine('html', require('express-art-template'))
app.set('views', path.join(__dirname, './views/')) 

// 设置post
// 配置解析表单 POST 请求体插件(注意:一定要在 app.use(router) 之前 )
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({
    extended: false
}))
// parse application/json
app.use(bodyParser.json())

app.get('/', (req, res) => {
    res.render('demo.html')
})
app.post('/ajaxdemo', (req, res) => {
    if (req.body.name === 'admin') {
        // 响应请求,先将状态码设置200,因为设置接受的是json数据,所以我们要将数据改为json
        return res.status(200).send(JSON.stringify({
            success: true,
            message: '您输入的name为admin'
        }))
    }
    if (req.body.password === '123456') {
        // res.status(200).send(JSON.stringify({对象}))还是比较麻烦的,所以express提供了json的方法
        return res.status(200).json({
            success: true,
            message: '您输入的password为123456'
        })
    }
    res.status(200).json({
        success: false,
        message: '没有符合要求数据'
    })
})

app.listen(3000, console.log('服务器运行中...'))

Session

在Express中默认是没有Session这个功能的,需要使用第三方插件来完成

注意:Session的配置文件要放在路由之前,我们可以使用template来搭配session从而进行页面的内容修改

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

下载

npm i express-session

配置

const session = require('express-session')
const express = require('express')

const app = new express()
// 配置session:
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}))

之前学习JSP中,服务器会默认在浏览器中添加SESSIONID,在nodejs中也是这样

可以配置的值:

  • secret 一个 String 类型的字符串,作为服务器端生成独特的 session 的签名,防止签名冲突,增加安全性。
  • name 返回客户端的 key 的名称,默认为 connect.sid,也可以自己设置。
  • resave 强制保存 session 即使它并没有变化,。默认为 true。建议设置成 false。
  • saveUninitialized 强制将未初始化的 session 存储(在浏览器中添加Cookie,默认值:true)。当选择true时,无论是否使用session都会添加cookie;选择false时,只有使用session时服务器才会添加cookie
  • cookie 设置返回到前端 key 的属性,默认值为{ path: ‘/’, httpOnly: true, secure: false, maxAge: null }。
  • rolling 在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false)

操作值

// 设置值
req.session.username = "张三"; 
// 获取值 
console.log(req.session.username)
//删除值
req.session.username = null;
delete req.session.username

注销

app.get("/out",function(req,res){
  //注销session
  req.session.destroy()
  res.redirect("/")		//重定向定位到指定内容
})

将session存入mysql
网址

小案例

const express = require('express')
const path = require('path')
const bodyParser = require('body-parser')
const session = require('express-session')

const app = new express()

app.use('/node_modules/', express.static(path.join(__dirname, '../node_modules/')))

app.engine('html', require('express-art-template'))
app.set('views', path.join(__dirname, './views/')) // 默认就是 ./views 目录

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())

app.use(session({
    // 配置加密字符串,它会在原有加密基础之上和这个字符串拼起来去加密
    // 目的是为了增加安全性,防止客户端恶意伪造
    secret: 'SessionDemo',
    resave: false,
    saveUninitialized: false // 无论你是否使用 Session ,我都默认直接给你分配一把钥匙
}))


app.get('/', (req, res) => {
    // 如果没有则自动跳转登录页面
    if (!req.session.username) {
        return res.redirect('/login')
    }
    // 有则跳转至主页,并且显示用户名
    res.render('./index.html', {
        username: req.session.username
    })
})

app.get('/login', (req, res) => {
    res.render('./login.html')
})

app.post('/login', (req, res) => {
    // 直接将对象创建到session中
    req.session.username = req.body.username
    // 跳转至主页
    res.redirect('/')
})

app.listen(3000, console.log('running...'))

中间件

参考文档:http://expressjs.com/en/guide/using-middleware.html

中间件:把很复杂的事情分割成单个,然后依次有条理的执行。就是一个中间处理环节,有输入,有输出。

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

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

var http = require('http');
var url = require('url');

var cookie = require('./expressPtoject/cookie');
var query = require('./expressPtoject/query');
var postBody = require('./expressPtoject/post-body');

var server = http.createServer(function(){
	// 解析请求地址中的get参数
	// var obj = url.parse(req.url,true);
	// req.query = obj.query;
	query(req,res);	//中间件
	
	// 解析请求地址中的post参数
	req.body = {
		foo:'bar'
	}
});

if(req.url === 'xxx'){
	// 处理请求
	...
}

server.listen(3000,function(){
	console.log('3000 runing...');
});

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

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

应用程序级别的中间件

万能匹配(不关心任何请求路径和请求方法的中间件):

// 中间件本身是一个方法,该方法接收三个参数:
//    Request 请求对象
//    Response 响应对象
//    next     匹配的下一个中间件
app.use(function(req,res,next){
    console.log('Time',Date.now());
    // 默认情况下不会自动添加next()
    // 不添加next()就会导致多个中间件只会加载第一个中间件,也就是说有三个中间件a、b、c,如果a加了next()则可以执行b,但是b不加next()就不会执行c
    next();
});

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

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

注意: next()是指匹配的下一个中间件,在万能匹配中代表的是紧挨的中间件,因为万能匹配所有都会匹配,但是关心请求路径和请求方法中的中间件就需要匹配路径了

app.use(function(req,res,next){
    console.log('万能中间件')
    next()
})
app.use('/a',function(req,res,next){
    console.log('请求路径为/a的中间件')
})
app.use('/b',function(req,res,next){
    console.log('请求路径为/b的中间件')
})

假设现在有一个请求路径为/b/ac的请求,由于第一个中间件是万能中间件,所以肯定会打印万能中间件,并且万能中间件还加入了next(),因为next()是下一个匹配的中间件,但是又由于第二个中间件/a开头不匹配,所以这里的next()是指/b,所以最后打印的内容为万能中间件 请求路径为/b的中间件

路由级别的中间件

严格匹配请求路径和请求方法的中间件
严格匹配:请求路径不仅要完全等于设置的路径,方法也要符合,而上面的方法只要是请求路径的开头包含就可以了

get:

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

post:

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

put:

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

delete:

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

var express = require('express');
var app = express();

// 中间件:处理请求,本质就是个函数
// 在express中,对中间件有几种分类

// 1 不关心任何请求路径和请求方法的中间件
// 也就是说任何请求都会进入这个中间件
// 中间件本身是一个方法,该方法接收三个参数
// Request 请求对象
// Response 响应对象
// next 下一个中间件
// // 全局匹配中间件
// app.use(function(req, res, next) {
// 	console.log('1');
// 	// 当一个请求进入中间件后
// 	// 如果需要请求另外一个方法则需要使用next()方法
// 	next();
// 	// next是一个方法,用来调用下一个中间件
//  // 注意:next()方法调用下一个方法的时候,也会匹配(不是调用紧挨着的哪一个)
// });
// app.use(function(req, res, next) {
// 	console.log('2');
// });

// // 2 关心请求路径的中间件
// // 以/xxx开头的中间件
// app.use('/a',function(req, res, next) {
// 	console.log(req.url);
// });

// 3 严格匹配请求方法和请求路径的中间件
app.get('/',function(){
	console.log('/');
});
app.post('/a',function(){
	console.log('/a');
});

// 同一个请求所经过的中间件都是同一个请求对象和响应对象
// 注意是同一个请求所经过的中间件,万能匹配也可以有以下操作
app.get('/abc', function (req, res, next) {
  console.log('abc')
  req.body = {
    foo: 'bar'
  }
  next()
})
app.get('/abc', function (req, res, next) {
  console.log(req.body.foo) // bar
  console.log('abc 2')
})


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

错误处理中间件

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

配置使用404中间件:注意要放在路由之后,因为是全局没有任何一个能匹配到的路由就会匹配到这个

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

配置全局错误处理中间件:处理错误正确写法

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

内置中间件

  • express.static(提供静态文件)
    • http://expressjs.com/en/starter/static-files.html#serving-static-files-in-express

第三方中间件

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

  • body-parser
  • compression
  • cookie-parser
  • mogran
  • response-time
  • server-static
  • session

MongoDB

MongoDB参考手册

关系型和非关系型数据库

关系型数据库

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

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

非关系型数据库

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

一个数据库中可以有多个数据库,一个数据库中可以有多个集合(数组),一个集合中可以有多个文档(表记录)

{
    qq:{
       user:[
           {},{},{}...
       ]
    }
}
  • 也就是说你可以任意的往里面存数据,没有结构性这么一说

下载

下载数据库
mongoDB下载官网
注意:选择community版本
安装教程
下载好并安装,还需要配置环境变量,需要配置到bin目录下(如:D:\MongoDB\5.0\bin),此时cmd输入mongod --version便可以查看是否安装成功

node中安装

npm i mongoose

启动和关闭数据库

启动:

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

如果想要修改默认的数据存储目录,可以(不推荐,我没有修改成功):

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

停止:

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

连接数据库

先开启数据库,然后重新打开一个cmd窗口

连接:

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

退出:

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

基本命令

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

在Node中操作MongoDB数据库

使用官方的MongoDB包来操作

​ http://mongodb.github.io/node-mongodb-native/

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

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

​ https://mongoosejs.com/

官方学习文档:https://mongoosejs.com/docs/index.html
具体可以查看:CSDN中mongoose应用

基本概念

  • 可以有多个数据库
  • 一个数据库中可以有多个集合(表)
  • 一个集合中可以有多个文档(表记录)
  • 文档结构很灵活,没有任何限制
{
    qq: {
        users: [
            {name: '张三', age: 15},
            {name: '李四', age: 15},
            {name: '王五', age: 15}
        ],
        products: [

        ],
        ...
    },
    taobao: {},
    baidu: {}
}

这里的qq、taobao、baidu就是数据库,users、products是表,表里面的数据是文档

基本操作

var mongoose = require('mongoose');

// 连接 MongoDB 数据库,mongodb5.0之后就不支持useMongoClient,需要将useMongoClient都改为useNewUrlParser,也可以不用加
// mongoose.connect('mongodb://localhost/test', { useNewUrlParser: true });
mongoose.connect('mongodb://localhost/test');

mongoose.Promise = global.Promise;

// 创建一个模型
// 就是在设计数据库
// MongoDB 是动态的,非常灵活,只需要在代码中设计你的数据库就可以了
// mongoose 这个包就可以让你的设计编写过程变的非常的简单
var Cat = mongoose.model('Cat', { name: String });

for (var i = 0; i < 100; i++) {
  // 实例化一个 Cat
  var kitty = new Cat({ name: '喵喵' + i });

  // 持久化保存 kitty 实例
  kitty.save(function (err) {
    if (err) {
      console.log(err);
    } else {
      console.log('meow');
    }
  });
}

此时可以将数据库切换到test,便可以看到cats表,然后使用db.cats.find()查看数据,一次只会显示25条数据,输入it可以显示更多

设计Scheme 发布Model (创建表)
const mongoose = require('mongoose')

// 拿到设计结构
const Schema = mongoose.Schema

// 连接test数据库
mongoose.connect('mongodb://localhost/test')

// 设计集合结构(表结构)
// 字段名称就是表结构中的属性名称,值为字段要求为什么属性
// 约束的目的就是为了保证数据的完整性,不要有脏数据
var userSchema = new Schema({
    username: {
        type: String,
        required: true // 非空的
    },
    password: {
        type: String,
        required: true
    },
    email: {
        type: String
    }
})

// 将文档结构发布为模型
//    mongoose.model 方法就是用来将一个架构发布为 model
//    第一个参数:传入一个大写名词单数字符串用来表示你的数据库名称
//                 mongoose 会自动将大写名词的字符串生成 小写复数 的集合名称
//                 例如这里的 User 最终会变为 users 集合名称
//    第二个参数:架构 Schema
//   
//    返回值:模型构造函数
const User = mongoose.model('User', userSchema)

到了这时候我们就已经完成了数据结构的设计,我们就可以操作数据库进行增删改查

添加数据(增)
// 增加一条数据,里面传递的对象需要严格按照Schema标准来
const admin = new User({
    username: 'admin',
    password: '123456',
    email: '[email protected]'
})

// 使用User.save(callback)方法存储数据
// callback有两个参数,err为错误信息,ret为返回值,要是存储成功err为null
admin.save((err, ret) => {
    if (err) {
        console.log('存储失败')
    } else {
        console.log('保存成功')
        console.log(ret)
    }
})
删除(删)
// 使用Schema.remove(Obj, callback),删除所有符合条件的数据
User.remove({
    username: 'admin'
}, (err, ret) => {
    if (err) {
        console.log('删除失败')
    } else {
        console.log('删除成功')
        console.log(ret)
    }
})

根据条件删除一个:

Model.findOneAndRemove(conditions,[options],[callback]);

根据id删除一个:

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

更新所有:

User.remove(conditions,doc,[options],[callback]);

根据指定条件更新一个:

User.FindOneAndUpdate([conditions],[update],[options],[callback]);

通过id修改

// 使用Schema.findByIdAndUpdate(id, updateObj, callback)通过id来修改
User.findByIdAndUpdate('60f52262ff45f649805b707b', {
    username: '张三' // 将原先的username改为现在的‘张三’
}, (err, ret) => {
    if (err) {
        console.log('更新失败')
    } else {
        console.log('更新成功')
        console.log(ret)
    }
})
查询(查)
  • 查询有或,并的关系,具体可以参考mongodb查询
// 使用Schema.find(callback)查询所有数据
// callback两个参数,err为错误信息,ret为返回值,要是查询成功,ret为一个数组
User.find((err, ret) => {
    if (err) {
        console.log('查询全部失败')
    } else {
        console.log('查询全部成功')
        console.log(ret)
    }
})
// 使用Schema.find(Obj, callback)条件查询所有
// 此处查询username为admin的所有数据
User.find({
    username: 'admin'
}, (err, ret) => {
    if (err) {
        console.log('条件查询失败')
    } else {
        console.log('条件查询成功')
        console.log(ret)
    }
})
// 使用Schema.findOne(Obj, callback)条件查询单个,返回一个对象,没有条件则返回一个查询到的数据
User.findOne({
    username: 'admin',
    password: '123456'
}, (err, ret) => {
    if (err) {
        console.log('条件查询失败')
    } else {
        console.log('条件查询成功')
        console.log(ret)
    }
})
综合使用
const mongoose = require('mongoose')

// 拿到设计结构
const Schema = mongoose.Schema

// 连接test数据库
mongoose.connect('mongodb://localhost/test')

// 设计集合结构(表结构)
// 字段名称就是表结构中的属性名称,值为字段要求为什么属性
// 约束的目的就是为了保证数据的完整性,不要有脏数据
var userSchema = new Schema({
    username: {
        type: String,
        required: true // 非空的
    },
    password: {
        type: String,
        required: true
    },
    email: {
        type: String
    }
})

// 将文档结构发布为模型
//    mongoose.model 方法就是用来将一个架构发布为 model
//    第一个参数:传入一个大写名词单数字符串用来表示你的数据库名称
//                 mongoose 会自动将大写名词的字符串生成 小写复数 的集合名称
//                 例如这里的 User 最终会变为 users 集合名称
//    第二个参数:架构 Schema
//   
//    返回值:模型构造函数
const User = mongoose.model('User', userSchema)


// 增加一条数据,里面传递的对象需要严格按照Schema标准来
const admin = new User({
    username: 'zs',
    password: '123456',
    email: '[email protected]'
})

// 使用User.save(callback)方法存储数据
// callback有两个参数,err为错误信息,ret为返回值,要是存储成功err为null
admin.save((err, ret) => {
    if (err) {
        console.log('存储失败')
    } else {
        console.log('保存成功')
        console.log(ret)
    }
})


// 查询
// 使用Schema.find(callback)查询所有数据
// callback两个参数,err为错误信息,ret为返回值,要是查询成功,ret为一个数组
User.find((err, ret) => {
    if (err) {
        console.log('查询全部失败')
    } else {
        console.log('查询全部成功')
        console.log(ret)
    }
})
// 使用Schema.find(Obj, callback)条件查询所有
// 此处查询username为admin的所有数据
// User.find({
//     username: 'admin'
// }, (err, ret) => {
//     if (err) {
//         console.log('条件查询失败')
//     } else {
//         console.log('条件查询成功')
//         console.log(ret)
//     }
// })
// 使用Schema.findOne(Obj, callback)条件查询单个,返回一个对象,没有条件则返回一个查询到的数据
// User.findOne({
//     username: 'admin'
// }, (err, ret) => {
//     if (err) {
//         console.log('条件查询失败')
//     } else {
//         console.log('条件查询成功')
//         console.log(ret)
//     }
// })


// 删除
// 使用Schema.remove(Obj, callback),删除所有符合条件的数据
User.remove({
    username: 'admin'
}, (err, ret) => {
    if (err) {
        console.log('删除失败')
    } else {
        console.log('删除成功')
        console.log(ret)
    }
})

// 更新数据
// 使用Schema.findByIdAndUpdate(id, updateObj, callback)通过id来修改
User.findByIdAndUpdate('60f52262ff45f649805b707b', {
    username: '张三' // 将原先的username改为现在的‘张三’
}, (err, ret) => {
    if (err) {
        console.log('更新失败')
    } else {
        console.log('更新成功')
        console.log(ret)
    }
})

在Node中操作Mysql

Node中有专门的模块(mysql)来操作Mysql
文档:https://www.npmjs.com/package/mysql

可以参考CSDN文档使用Node操作Mysql

安装

npm i mysql

基础操作

// 导入核心模块
var mysql = require('mysql');
// 1. 创建连接,需要传递一个对象,对象有host、user、password、database
// host:连接数据库地址、user:用户名、password:密码、database:选择数据库
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'root',
    database: 'nodejs'
});

// 2. 使用connect()方法连接数据库
connection.connect();

// 3. 执行数据操作(增删改查都是使用query方法),两个参数,一个执行语句,一个回调函数
// 回调函数两个参数:error表示错误信息,成功为null,失败为错误对象;result表示返回结果

// 创建表
let createTable = `CREATE TABLE  USER(
    user_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    user_name VARCHAR(20) NOT NULL,
    user_password VARCHAR(20) NOT NULL
)`
connection.query(createTable, function(err, result){
    if(err){
        console.error('create failed and err is ', err)
    }
    console.log('create success and the result is ', result)
})

// 插入数据
connection.query('INSERT INTO user VALUES(NULL, "admin", "123456")', function (error, results) {
    if (error) {
        return console.log(error);
    }
    console.log('The solution is: ', results);
});

// 查询
connection.query('SELECT * FROM `user`', function (error, results) {
    if (error) {
        return console.log(error);
    }
    console.log('The solution is: ', results);
});

// 4. 关闭连接
connection.end();

其他

修改完代码自动重启服务器

可以使用一个第三方命名行工具:nodemon来帮助我们解决频繁修改代码重启服务器的问题。

nodemon是一个基于Node.js开发的一个第三方命令行工具,我们使用的时候需要独立安装:

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

#如果安装不成功的话,可以使用cnpm安装
cnpm install -g nodemon

安装完毕之后使用:

// 不使用nodemon就和原先一样使用node
node ./app.js

// 使用nodemon就是将原先的node换成nodemon
nodemon ./app.js

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

异步编程

具体浏览器的事件循环可以参考浏览器事件循环

回调函数

只要是涉及到异步请求的都需要使用回调函数

  • 同步请求:
  • 异步请求:由于js文件执行是一步一步的,假设函数体内有一个定时器,函数是不会等到定时器完成在执行下一步的,而是直接跳过定时器,执行后面的代码,等到定时器计时完成在执行定时器内部代码,这就是异步请求(类似于:setTimeout,readFile,writeFile,ajax,readdir)

不成立的情况下:

function add(x,y){
    console.log(1);
    setTimeout(function(){
        console.log(2);
        var ret = x + y;
        return ret;
    },1000);
    console.log(3);
    //到这里执行就结束了,不会i等到前面的定时器,所以直接返回了默认值 undefined
}

console.log(add(2,2));
// 结果是 1 3 undefined 4

使用回调函数解决:

回调函数:通过一个函数,获取函数内部的操作。(根据输入得到输出结果)

var ret;
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);
    console.log(3);
}
add(10,20,function(ret){
    console.log(ret);
});

ajax:

基于原生XMLHttpRequest封装get方法:

var oReq = new XMLHttpRequest();
// 当请求加载成功要调用指定的函数
oReq.onload = function(){
    console.log(oReq.responseText);
}
oReq.open("GET", "请求路径",true);
oReq.send();
function get(url,callback){
    var oReq = new XMLHttpRequest();
    // 当请求加载成功要调用指定的函数
    oReq.onload = function(){
        //console.log(oReq.responseText);
        callback(oReq.responseText);
    }
    oReq.open("GET", url,true);
    oReq.send();
}
get('data.json',function(data){
    console.log(data);
});

Promise

callback hell(回调地狱):一个异步请求依赖于另外一个异步请求的结果
文件的读取无法判断执行顺序(文件的执行顺序是依据文件的大小来决定的)(异步api无法保证文件的执行顺序)

// 无法保证读取顺序,a->b->c、a->c->b、c->b->a都有可能
fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
    if (err) {
        // 抛出异常
        throw err
    }
    console.log(data)
})
fs.readFile('./data/b.txt', 'utf-8', (err, data) => {
    if (err) {
        // 抛出异常
        throw err
    }
    console.log(data)
})
fs.readFile('./data/c.txt', 'utf-8', (err, data) => {
    if (err) {
        // 抛出异常
        throw err
    }
    console.log(data)
})

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

// 通过回调嵌套保证读取顺序,这样读取就一定是a->b->c
// 这也就是callback hell,回调地狱
fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
    if (err) {
        // 抛出异常
        throw err
    }
    console.log(data)
    fs.readFile('./data/b.txt', 'utf-8', (err, data) => {
        if (err) {
            // 抛出异常
            throw err
        }
        console.log(data)
        fs.readFile('./data/c.txt', 'utf-8', (err, data) => {
            if (err) {
                // 抛出异常
                throw err
            }
            console.log(data)
        })
    })
})

为了解决以上编码方式带来的问题(回调地狱嵌套),所以在EcmaScript6新增了一个API:Promise

  • Promise:承诺,保证
  • Promise本身不是异步的,但往往都是内部封装一个异步任务

基本语法:

console.log(1)
// 创建Promise容器,容器当中一般存放一个异步任务,并且返回一个实例,Promise容器一旦创建,就会开始执行里面的方法
// 容器接受两个参数,resolve:解决,就是成功、reject:驳回,就是失败
const p1 = new Promise((resolve, reject) => {
    console.log(2)
    // 异步任务
    fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
        if (err) {
            // 失败了,Promise容器中的任务失败了
            // console.log(err)
            // 将容器的Pending状态改为reject
            reject(err)
        } else {
            console.log(3)
            // Promise容器中的任务成功了
            // console.log(data)
            // 将容器的Pending状态改为resolve
            resolve(data)
        }
    })
})
console.log(4)
// 打印顺序为1243,因为Promise本身不是异步的,但是里面会有异步的任务

// 使用p1.then()方法来做指定的操作,方法两个回调函数,一个是resolve情况下的处理方法,一个是reject情况下的处理方法
p1
    .then((data) => {
        console.log('文件读取成功:' + data)
    }, (err) => {
        console.log('文件读取失败:' + err)
    })

链式循环:
封装Promise的readFile

const fs = require('fs')

let pReadFile = (filePath) => {
    return new Promise((resolve, reject) => {
        fs.readFile(filePath, 'utf-8', (err, data) => {
            if (err) {
                reject(err)
            } else {
                resolve(data)
            }
        })
    })
}

pReadFile('./data/a.txt')
    .then((data) => {
        console.log(data)
        // 在返回一个pReadFile方法并且设置读取b.txt文件,这样就从pReadFile('./data/a.txt')方法变成了pReadFile('./data/b.txt')
        return pReadFile('./data/b.txt')
    })
    .then((data) => {
        console.log(data)
        // 在返回一个pReadFile方法并且设置读取c.txt文件
        return pReadFile('./data/c.txt')
    })
    .then((data) => {
        console.log(data)
    })

jQuery的ajax方法也支持Promise

var data = {}
// path是请求路径
$.get(path1)
    .then((userData) => {
        data.userData = userData
        return $.get(path2)
    })
    .then((jobData) => {
        // 在这里是不能拿到userData的数据的,只能在外面再声明一个变量
        data.jobData = jobData
    })

mongoose所有的API都支持Promise:

// 查询所有
User.find()
	.then(function(data){
        console.log(data)
    })

注册:

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

Generator

async函数

密码加密

md5是一种常见的加密方式,不可被反编译
常见的还有:

  • sha1
  • base64 是可以被反编译的

在node中使用:
下载

cnpm install md5
# 也有的地方使用blueimp-md5插件,其实结果是一样的

引入

const md5 = require('md5')

使用

const passWord = md5("加密的字符");   //将加密过后的字符赋值给 passWord

案例

const md5 = require('md5')
const password = '123456'
// 对密码进行污染,增加难度
const password1 = md5(password + '#@')
// 除了对密码污染之外还可以对密码多层加密
const password2 = md5(md5(password))

想要验证密码,就将输入的密码使用同样的规则加密,然后和数据库中查询的做对比

你可能感兴趣的:(ajax,javascript,json,node.js,es6)