無.Flac's Blog

無.Flac

AJAX 「 ※ Learning ※ 」

2025-03-12

简介

AJAX 全称为 Asynchronous JavaScript And XML,就是异步的 JS 和 XML。

通过 AJAX 可以在浏览器中向服务器发送异步请求,最大的优势:无刷新获取数据

AJAX 不是新的编程语言,而是一种将现有的标准组合在一起使用的新方式。


【原生】AJAX请求

准备工作

1、前往 Node.js 官方网站 并下载适合你操作系统的 Node.js 安装程序。

2、前往并下载 Express 框架。

安装Node.js

node -v //检查Node.js版本
npm -v // 检查Npm包版本
npm install -g npm // 更细Npm

安装Express

npm init --yes
npm i express //mac用户前置sudo命令

HTTP协议请求

HTTP(hypertext transport protocol)协议『超文本传输协议』,协议详细规定了浏览器和万维网服务器之间互相通信的规则。

请求报文

格式

参数

请求行

POST /form.html HTTP/1.1

请求头

Host: www.example.com

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)

Content-Type: application/x-www-form-urlencoded

Content-Length: 27

空行

请求体

/username=admin&password=admin

响应报文

格式

参数

状态行

HTTP/1.1 200 OK

响应头

Date: Wed, 21 Oct 2015 10:55:34 GMT

Content-Type: text/html; charset=UTF-8

Content-Length: 1354

Content-encoding: gzip

空行

响应体

<html>
  <head> </head>
  <body>
    <h1>hello ,Ajax !</h1>
  </body>
</html>

状态码

状态码

类别

描述

100

信息性状态码

Continue:继续执行。

101

信息性状态码

Switching Protocols:切换协议。

200

成功状态码

OK:请求成功。

201

成功状态码

Created:请求成功并且服务器创建了新的资源。

202

成功状态码

Accepted:请求已接受,但处理尚未完成。

204

成功状态码

No Content:请求成功,但响应不包含内容。

301

重定向状态码

Moved Permanently:请求的资源已被永久移动到新位置。

302

重定向状态码

Found:请求的资源临时从不同的URI响应。

304

重定向状态码

Not Modified:资源未修改,可以使用客户端缓存的版本。

400

客户端错误状态码

Bad Request:请求无效或无法被服务器理解。

401

客户端错误状态码

Unauthorized:请求需要用户认证。

403

客户端错误状态码

Forbidden:服务器拒绝请求。

404

客户端错误状态码

Not Found:请求的资源在服务器上不存在。

500

服务器错误状态码

Internal Server Error:服务器遇到未知错误。

501

服务器错误状态码

Not Implemented:服务器不支持请求的功能。

502

服务器错误状态码

Bad Gateway:网关或代理服务器从上游服务器收到无效响应。

Express规则

// 引入express
const express = require('express');

// 创建应用对象
const app = express();

// 创建路由规则
// request是对请求报文的封装
// response是对响应报文的封装
app.get('/server',(request,response)=>{
    // 设置响应头,设置允许跨域
    response.setHeader('Access-Control-Allow-Origin','*')
    // 设置响应体
    response.send('Hello AJAX!')
})
app.post('/server', (request, response) => {
    // 设置响应头,设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*')
    // 设置响应体
    response.send('Hello AJAX!')
})
// 监听端口,启动服务
app.listen(8000, ()=>{
    console.log('服务器已启动,8000端口监听中...')
})

AJAX基本原理

XMLHttpRequest(XHR)对象用于与服务器交互。通过XMLhttpRequest可以再不刷新页面的情况下,请求特定URL获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest在AJAX编程中被大量使用。

使用XMLHttpRequest

创建XMLHttpRequest对象

const xhr = new XMLHttpRequest();

配置请求方法和请求url地址

xhr.open('请求方法','请求url地址')

监听loadend时间,接收响应结果

xhr.addEventListener('loadend',()=>{
  //响应结果
  console.log(xhr.response)
})

发送请求

xhr.send()

<script>
    const btn = document.getElementsByTagName('button')[0];
    const result = document.getElementById('result');
    btn.onclick = function() {
        // 1. 创建对象
        const xhr = new XMLHttpRequest();
        // 2. 初始化 设置请求方法和url
        xhr.open('GET','http://127.0.0.1:8000/server');
        // 3. 发送
        xhr.send(); 

        // 4. 事件绑定 处理服务端返回的结果
        // on when 当....时候

        // readystate 是xhr对象中的属性,表示状态 0-4
        // 0:xhr对象创建完成
        // 1:open方法调用完成
        // 2:send方法调用完成
        // 3:服务端返回部分结果
        // 4:服务端返回所有结果,可以在该阶段获取返回结果

        // change 改变
        xhr.onreadystatechange = function() {
            // 判断服务端返回所有结果
            if (xhr.readyState === 4) {
                // 判断响应状态码 200 404 403 401 500
                // 2xx 都为成功
                if (xhr.status >= 200 && xhr.status < 300) {
                    // 处理结果 行 头 空行 体
                    // 1. 响应行
                    // console.log(xhr.status); // 状态码
                    // console.log(xhr.statusText); // 状态字符串
                    // console.log(xhr.getAllResponseHeaders()); // 所有响应头
                    // console.log(xhr.response); // 响应体
                    // 设置result的文本
                    result.innerText = xhr.response;
                }
            }
        }
    }
</script>

POST请求设置

/* 当鼠标移入#result,则发送Post请求... */
<div id="result" class="w-48 h-48 border solid black"></div>
<script>
    const result = document.getElementById('result');
    result.addEventListener('mouseover', function () {
        //1.创建对象
        const xhr = new XMLHttpRequest();
        //2.初始化
        xhr.open('POST', 'http://127.0.0.1:8000/server');
        xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); // 设置请求头信息(预定义)
        xhr.setRequestHeader('name','parlo'); //可以设置自定义请求头
        //3.发送
        xhr.send('a=100&b=200'); //请求体也可以为'number';'a:100&b:100..'
        //4.事件绑定
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    result.innerText = xhr.response;
                }
            }
        }
    });
</script>

当设定为自定义的请求头时,则需要在后端添加新的请求类型(一般不需要前端去操作)

// server.js

// 可以接受任意类型的请求
app.all('/server', (request, response) => {
    // 设置响应头,设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*')
    response.setHeader('Access-Control-Allow-Headers', '*')
    // 设置响应体
    response.send('Hello AJAX!')
})

服务端响应JSON数据

// server.js

// 可以接受任意类型的请求
app.all('/json-server', (request, response) => {
    // 设置响应头,设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*')
    response.setHeader('Access-Control-Allow-Headers', '*')
    // 响应一个数据
    const data = {
        name: 'parlo',
    }
    // 对象转换为字符串
    let str = JSON.stringify(data)
    // 设置响应体
    response.send(str)
})
/* 当键盘在浏览器视口按下,则发送请求 */
<div id="result" class="w-48 h-48 border solid black"></div>

手动转换数据

<script>
    //当键盘在浏览器视口下,按下键盘则发送POST请求
    const result = document.getElementById('result')
    window.onkeydown = function () {
        // 发送请求
        const xhr = new XMLHttpRequest();
        // 初始化
        xhr.open('get', 'http://127.0.0.1:8000/json-server');
        // 发送
        xhr.send();
        // 事件绑定
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    // 手动处理服务器返回的结果
                    let data = JSON.parse(xhr.response)
                    console.log(data)
                    result.innerText = data.name
                }
            }
        }
    }
</script>

自动转换数据

<script>
    //当键盘在浏览器视口下,按下键盘则发送POST请求
    const result = document.getElementById('result')
    window.onkeydown = function () {
        // 发送请求
        const xhr = new XMLHttpRequest();
        // 设置响应体类型
        xhr.responseType = 'json'
        // 初始化
        xhr.open('get', 'http://127.0.0.1:8000/json-server');
        // 发送
        xhr.send();
        // 事件绑定
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    // 自动处理服务器返回的结果
                    console.log(xhr.response)
                    result.innerText = xhr.response.name
                }
            }
        }
    }
</script>

Nodemon工具

安装Nodemon

npm install -g nodemon

运行服务端

nodemon server.js

IE缓存问题解决

解决全世界最好用的IE浏览器的缓存解决问题!需要注意的是,现代浏览器几乎不需要调整。

<button>点我发送请求</button>
<div id="result" class="w-48 h-48 border solid black"></div>
// server.js

app.get('/ie', (request, response) => {
    // 设置响应头,设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*');
    // 设置响应体
    response.send('HELLO IE! 2~~~');
});
<script>
    const btn = document.getElementsByTagName('button')[0];
    const result = document.getElementById('result');
    btn.addEventListener('click', () => {
        const xhr = new XMLHttpRequest();
        // 初始化
        xhr.open('GET', 'http://localhost:8000/ie?t=' + Date.now()); //为IE浏览器添加参数 + 时间戳
        // 发送
        xhr.send();
        // 绑定事件
        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    result.innerText = xhr.response;
                }
            }
        };
    });
</script>

超时与网络异常

增加友好的判定来告知AJAX请求的网络情况。

<button>点我发送请求</button>
<div id="result" class="w-48 h-48 border solid black"></div>
// server.js

app.get('/delay', (request, response) => {
    // 设置响应头,设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*');
    // 设置响应体
    setTimeout(() => {
        response.send('延时响应');
    }, 3000);
});
<script>
    const btn = document.getElementsByTagName('button')[0];
    const result = document.getElementById('result');
    btn.addEventListener('click', () => {
        // 创建对象
        const xhr = new XMLHttpRequest();
        // 设置超时时间
        xhr.timeout = 2000;
        // 设置超时回调
        xhr.ontimeout = function () {
            alert('网络超时,请稍后重试!');
        };
        // 设置网络异常回调
        xhr.onerror = function () {
            alert('网络异常,请稍后重试!');
        };
        // 初始化
        xhr.open('GET', 'http://localhost:8000/delay');
        // 发送
        xhr.send();
        // 事件绑定
        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    result.innerText = xhr.response;
                }
            }
        };
    });
</script>

取消请求

取消正在发送的AJAX请求。

<button>点击发送请求</button>
<button>点击取消请求</button>
<script>
    const btn = document.querySelectorAll('button');

    let xhr = null; // 作用域需放在外层

    btn[0].onclick = function () {
        xhr = new XMLHttpRequest();
        xhr.open('GET', 'http://localhost:8000/delay');
        xhr.send();
    };
    btn[1].onclick = function () {
        xhr.abort(); //取消请求
    };
</script>

重复请求问题

当多次发送AJAX请求时,取消之前的请求。这样可以减少服务器的性能冲突。

<script>
    const btn = document.querySelectorAll('button');
    let xhr = null;
    //标识变量
    let isSending = false; // 是否正在发送AJAX请求
    btn[0].onclick = function () {
        if (isSending) xhr.abort();//判断!如果正在发送,则取消本次发送
        xhr = new XMLHttpRequest();
        //修改 标识变量的值
        isSending = true;
        xhr.open('GET', 'http://localhost:8000/delay');
        xhr.send();
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                //修改表示变量
                isSending = false;
            }
        };
    };
    btn[1].onclick = function () {
        xhr.abort(); // 取消请求
    };
</script>

jQuery方法

<button>GET请求</button>
<button>POST请求</button>
<button>通用AJAX请求</button>
// jquery
app.all('/jquery-server', (request, response) => {
    // 设置响应头,设置允许跨域
    response.setHeader('Access-Control-Allow-Origin', '*');
    // 设置响应体
    //response.send('HELLO jquery AJAX');
    const data = {name:'parlo'};
    response.send(JSON.stringify(data));
});

GET请求

<script>
    $('button').eq(0).click(function () {
        $.get('http://127.0.0.1:8000/jquery-server', {name: 'parlor', age: 18}, function (data) {
            console.log(data);
        }, 'json');
    });
</script>

POST请求

<script>
    $('button').eq(1).click(function () {
        $.post('http://127.0.0.1:8000/jquery-server', {name: 'parlor', age: 18}, function (data) {
            console.log(data);
        }, 'json');
    });
</script>

通用方法

$('button').eq(2).click(function () {
    $.ajax({
        // url
        url: 'http://127.0.0.1:8000/jquery-server',
        // 参数
        data: {name: 'parlo', age: 18},
        // 请求类型
        type: 'GET',
        // 响应体结果
        dataType: 'json',
        // 成功的回调
        success: function (data) {
            console.log(data);
        },
        // 超时时间
        timeout: 2000,
        // 失败的回调
        error: function () {
            console.log('出错了');
        },
        headers: {
            a: 300,
            b: 400
        }
    });
});

Axiso方法

基本语法

axuis({
  url:`目标资源地址`
}).then(result=>{
//对服务器是返回的数据做后续处理
})

参数查询

axuis({
  url:`目标资源地址`
  params:{
    参数名:值
  }
}).then((result)=>{
//对服务器是返回的数据做后续处理
})
<p></p>
<script>
    axios({
        url: 'https://hmajax.itheima.net/api/city',
        params: {
            pname: '山东省'
        }
    }).then(result => {
        console.log(result.data.list);
        document.querySelector('p').innerHTML = result.data.list.join('<br>');
    });
</script>
<form>
    <label>省份名字
        <input name="province" placeholder="请输入省份名字" type="text" value="北京">
    </label>
    <label>城市名字
        <input name="city" placeholder="请输入城市名字" type="text" value="北京市">
    </label>
</form>
<button type="button">查询</button>
<p>地区列表</p>
<ul>
    <li></li>
</ul>
<script>
    <!--创建绑定事件-->
    document.querySelector('button').addEventListener('click', function () {
        // 获取用户输入的省份值
        let pname = document.querySelectorAll('input')[0].value;
        let cname = document.querySelectorAll('input')[1].value;
        console.log(pname, cname);
        // 使用axios发送请求
        axios({
            url: 'https://hmajax.itheima.net/api/area',
            params: {
                pname,
                cname
            }
        }).then(result => {
            // 打印并检查数据
            console.log(result.data.list);
            let list = result.data.list;
            // ele.map()方法是根据当前数组生成一个新数组
            let theLi = list.map(ele => `<li>${ele}</li>`).join('');
            console.log(theLi);
            // 将生成的li标签添加到ul中
            document.querySelector('ul').innerHTML = theLi;
        });
    });
</script>

请求配置

axuis({
  url:`目标资源地址`,
  method:'请求方法',
  data:{
    参数名:值
  }
  params:{
    参数名:值
  }
}).then(result=>{
//对服务器是返回的数据做后续处理
})

错误处理

axuis({
  //请求选项
}).then(result=>{
  //处理数据
}).catch(error=>{
  //处理错误
})
<button id="btn">点击注册用户</button>
<script>
    document.getElementById('btn').addEventListener('click', () => {
        axios({
            url: 'https://hmajax.itheima.net/api/register',
            method: 'post',
            data: {
                username: 'parlo0532',
                password: '12345678'
            }
        }).then(result => {
            console.log(result);
        }).catch(error => {
            console.log(error);
            alert(error.response.data.message);
        });
    });
</script>

Form-serialize(插件)

引入插件 Form-serialize

<script src="path/to/form-serialize.js"></script>

使用serialize函数快速手机表单元素的值

参数

详情

获取表单的数据

元素表单设置name属性,值会作为对象的属性名

配置对象

hash , empty

<script>
  document.querySelector('.btn').addEventListener('click',(){
    const form = document.querySelector('.example-form')
    /*
      hash:设置获取对象的结构
        - true:JS对象(推介)一般请求体里提交给服务器 
        - false:查询字符串
      empty:设置是否获取空值
        - true:获取空值(推介)一般请求体里提交给服务器
        - false:不获取空值
    */
    const data = serialize(form,{hash:true,empty:true})
    console.log(data)
  })
</script>

案例一:登录表单

<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta content="IE=edge" http-equiv="X-UA-Compatible">
        <meta content="width=device-width, initial-scale=1.0" name="viewport">
        <title>案例_登录</title>
        <!-- 引入bootstrap.css -->
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet">
        <!-- 公共 -->
        <style>
            html,
            body {
                background-color: #EDF0F5;
                width: 100%;
                height: 100%;
                display: flex;
                justify-content: center;
                align-items: center;
            }

            .container {
                width: 520px;
                height: 540px;
                background-color: #fff;
                padding: 60px;
                box-sizing: border-box;
            }

            .container h3 {
                font-weight: 900;
            }
        </style>
        <!-- 表单容器和内容 -->
        <style>
            .form_wrap {
                color: #8B929D !important;
            }

            .form-text {
                color: #8B929D !important;
            }
        </style>
        <!-- 提示框样式 -->
        <style>
            .alert {
                transition: .5s;
                opacity: 0;
            }

            .alert.show {
                opacity: 1;
            }
        </style>
        <script src="../node_modules/axios/dist/axios.js"></script>
        <script src="form-serialize.js"></script>
    </head>

    <body>
        <div class="container">
            <h3>欢迎-登录</h3>
            <!-- 登录结果-提示框 -->
            <div class="alert alert-success" role="alert">
                提示消息
            </div>
            <!-- 表单 -->
            <div class="form_wrap">
                <form class="login-form">
                    <div class="mb-3">
                        <label class="form-label" for="username">账号名</label>
                        <input autocomplete="current-username" class="form-control username" name="username"
                               type="text">
                    </div>
                    <div class="mb-3">
                        <label class="form-label" for="password">密码</label>
                        <input autocomplete="current-password" class="form-control password" name="password"
                               type="password">
                    </div>
                    <button class="btn btn-primary btn-login" type="button"> 登 录</button>
                </form>
            </div>
        </div>
        <script>
            //目标2:将错误或登录信息放在前端显示
            const message = document.querySelector('.alert')

            function alertFn(msg, isSuccess) {
                //显示提示框
                message.classList.add('show')
                //放入提示消息
                message.innerText = msg
                //设置样式
                const className = isSuccess ? 'alert-success' : 'alert-danger'
                message.classList.add(className)
                //过两秒隐藏
                setTimeout(() => {
                    message.classList.remove('show')
                    message.classList.remove(className)
                }, 2000)
            }


            // 目标1:点击登录时,用户名和密码长度判断,并提交数据和服务器通信
            document.querySelector('.btn-login').addEventListener('click', () => {
                // 使用serialize函数收集表单里用户名和密码
                const form = document.querySelector('.login-form')
                const data = serialize(form, {hash: true, empty: true})
                console.log(data);
                const {username, password} = data;
                console.log(username, password)
                // const username = document.querySelector('.username').value
                // const password = document.querySelector('.password').value
                if (username.length < 6) {
                    console.log('用户名长度不能小于6位');
                    alertFn('用户名长度不能小于6位', false)
                    return
                }
                if (password.length < 8) {
                    console.log('密码长度不能小于8位')
                    alertFn('密码长度不能小于8位', false)
                    return
                }
                axios({
                    url: 'https://hmajax.itheima.net/api/login',
                    method: 'post',
                    data: {
                        username,
                        password,
                    },
                }).then(result => {
                    console.log(result);
                    console.log(result.data.message);
                    alertFn(result.data.message, true)
                }).catch(error => {
                    console.log(error);
                    console.log(error.response.data.message);
                    alertFn(error.response.data.message, false)
                });
            });


        </script>

    </body>

</html>

案例二:图书管理

01>使用属性控制弹窗

Bootstrap

含义

data-bs-toggle="modal"

用于触发模态框(Modal)的显示。

data-bs-target="#myModal"

查找具有相应NAME模态框,并显示或操作该模态框。

data-bs-dismiss="modal"

关闭对应的模态框

<!-- 使用属性控制弹窗显示
    1、引入bootstrap.css和bootstrap.js
    2、准备弹窗标签,确认结构
    3、通过自定义属性,控制弹窗的显示和隐藏

    data-bs-toggle="modal" 用于触发模态框(Modal)的显示。
    data-bs-target="#myModal" 查找具有相应NAME模态框,并显示或操作该模态框。
    data-bs-dismiss="modal" 关闭对应的模态框
-->

<button class="btn btn-primary" data-bs-target=".my-modal" data-bs-toggle="modal" type="button">显示弹窗
</button>

<div class="modal my-modal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Modal title</h5>
                <button aria-label="Close" class="btn-close" data-bs-dismiss="modal" type="button"></button>
            </div>
            <div class="modal-body">
                <p>Modal body text goes here.</p>
            </div>
            <div class="modal-footer">
                <button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Close</button>
                <button class="btn btn-primary" type="button">Save changes</button>
            </div>
        </div>
    </div>
</div>

02>使用JS控制弹窗

Bootstrap

方法

创建弹窗对象

const modaDom = document.querySelector('.modal')
const modal = new bootstrap.Modal(modaDom)

调用弹窗对象内置方法

.show() 显示弹窗
.hide() 隐藏弹窗
<!--
目标:使用JS控制弹窗,显示和隐藏
1. 创建弹窗对象
   const modaDom = document.querySelector('.modal')
   const modal = new bootstrap.Modal(modaDom)
2. 调用弹窗对象内置方法
   .show() 显示弹窗
   .hide() 隐藏弹窗
-->
<button class="btn btn-primary edit-btn" type="button">
    编辑姓名
</button>

<div class="modal name-box" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">请输入姓名</h5>
                <button aria-label="Close" class="btn-close" data-bs-dismiss="modal" type="button"></button>
            </div>
            <div class="modal-body">
                <form action="">
                    <span>姓名:</span>
                    <input class="username" type="text">
                </form>
            </div>
            <div class="modal-footer">
                <button class="btn btn-secondary" data-bs-dismiss="modal" type="button">取消</button>
                <button class="btn btn-primary save-btn" type="button">保存</button>
            </div>
        </div>
    </div>
</div>

<!-- 引入bootstrap.js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js"></script>
<script>
    // 1. 创建弹窗对象
    const modaDom = document.querySelector('.name-box')
    const modal = new bootstrap.Modal(modaDom)
    // 编辑姓名->点击->弹窗显示
    document.querySelector('.edit-btn').addEventListener('click', () => {
        document.querySelector('.username').value = '张三'
        modal.show()
    })
    // 保存按钮->点击->获取姓名打印->弹窗隐藏
    document.querySelector('.save-btn').addEventListener('click', () => {
        const username = document.querySelector('.username').value
        console.log('模拟把姓名保存到服务器上', username)
        modal.hide()
    })
</script>

03>完整代码

<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta content="IE=edge" http-equiv="X-UA-Compatible">
        <meta content="width=device-width, initial-scale=1.0" name="viewport">
        <title>案例-图书管理</title>
        <!-- 字体图标 -->
        <link href="https://at.alicdn.com/t/c/font_3736758_vxpb728fcyh.css" rel="stylesheet">
        <!-- 引入bootstrap.css -->
        <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
        <!-- 核心样式 -->
        <link href="CSS/index.css" rel="stylesheet">
    </head>

    <body>
        <!-- 主体区域 -->
        <div class="container">
            <!-- 头部标题和添加按钮 -->
            <div class="top">
                <h3>图书管理</h3>
                <button class="btn btn-primary plus-btn" data-bs-target=".add-modal" data-bs-toggle="modal"
                        type="button"> + 添加
                </button>
            </div>
            <!-- 数据列表 -->
            <table class="table">
                <thead class="table-light">
                    <tr>
                        <th style="width: 150px;">序号</th>
                        <th>书名</th>
                        <th>作者</th>
                        <th>出版社</th>
                        <th style="width: 180px;">操作</th>
                    </tr>
                </thead>
                <tbody class="list">
                    <tr>
                        <td>1</td>
                        <td>JavaScript程序设计</td>
                        <td>马特·弗里斯比</td>
                        <td>人民邮电出版社</td>
                        <td>
                            <span class="del">删除</span>
                            <span class="edit">编辑</span>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
        <!-- 新增-弹出框 -->
        <div class="modal fade add-modal">
            <!-- 中间白色区域 -->
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header top">
                        <span>添加图书</span>
                        <button aria-label="Close" class="btn-close" data-bs-dismiss="modal" type="button"></button>
                    </div>
                    <div class="modal-body form-wrap">
                        <!-- 新增表单 -->
                        <form class="add-form">
                            <div class="mb-3">
                                <label class="form-label" for="bookname">书名</label>
                                <input class="form-control bookname" name="bookname" placeholder="请输入书籍名称"
                                       type="text">
                            </div>
                            <div class="mb-3">
                                <label class="form-label" for="author">作者</label>
                                <input class="form-control author" name="author" placeholder="请输入作者名称"
                                       type="text">
                            </div>
                            <div class="mb-3">
                                <label class="form-label" for="publisher">出版社</label>
                                <input class="form-control publisher" name="publisher" placeholder="请输入出版社名称"
                                       type="text">
                            </div>
                        </form>
                    </div>
                    <div class="modal-footer btn-group">
                        <button class="btn btn-primary" data-bs-dismiss="modal" type="button"> 取消</button>
                        <button class="btn btn-primary add-btn" type="button"> 保存</button>
                    </div>
                </div>
            </div>
        </div>
        <!-- 编辑-弹出框 -->
        <div class="modal fade edit-modal">
            <!-- 中间白色区域 -->
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header top">
                        <span>编辑图书</span>
                        <button aria-label="Close" class="btn-close" data-bs-dismiss="modal" type="button"></button>
                    </div>
                    <div class="modal-body form-wrap">
                        <!-- 编辑表单 -->
                        <form class="edit-form">
                            <input class="id" name="id" type="hidden">
                            <div class="mb-3">
                                <label class="form-label" for="bookname">书名</label>
                                <input class="form-control bookname" name="bookname" placeholder="请输入书籍名称"
                                       type="text">
                            </div>
                            <div class="mb-3">
                                <label class="form-label" for="author">作者</label>
                                <input class="form-control author" name="author" placeholder="请输入作者名称"
                                       type="text">
                            </div>
                            <div class="mb-3">
                                <label class="form-label" for="publisher">出版社</label>
                                <input class="form-control publisher" name="publisher" placeholder="请输入出版社名称"
                                       type="text">
                            </div>
                        </form>
                    </div>
                    <div class="modal-footer btn-group">
                        <button class="btn btn-primary" data-bs-dismiss="modal" type="button"> 取消</button>
                        <button class="btn btn-primary edit-btn" type="button"> 修改</button>
                    </div>
                </div>
            </div>
        </div>
        <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.0/axios.min.js"></script>
        <script src="./lib/form-serialize.js"></script>
        <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/js/bootstrap.min.js"></script>
        <!-- 核心逻辑 -->
        <script src="JS/index.js"></script>
    </body>

</html>
<script>

  /**
   * 目标1:渲染图书列表
   * 1.1 获取数据
   * 1.2 渲染数据
   */
  const creator = '张三';
  
  function getBookList() {
      // 1.1 获取数据
      axios({
          url: 'https://hmajax.itheima.net/api/books',
          params: {
              creator,
          },
      }).then(result => {
          console.log(result)
          const bookList = result.data.data;
          console.log(bookList)
          // 1.2 渲染数据
          const htmlStr = bookList.map((item, index) => {
              return `<tr>
                  <td>${index + 1}</td>
                  <td>${item.bookname}</td>
                  <td>${item.author}</td>
                  <td>${item.publisher}</td>
                  <td data-id="${item.id}">
                      <span class="del">删除</span>
                      <span class="edit">编辑</span>
                  </td>
              </tr>`
          }).join('')
          console.log(htmlStr)
          document.querySelector('.list').innerHTML = htmlStr
      })
  }
  
  //网页加载运行时,获取并渲染列表一次
  getBookList();
  
  /**
   * 目标2:新增图书
   * 2.1 新增弹窗-->显示和隐藏
   * 2.2 收集表单数据并提交到服务器保存
   * 2.3 刷新图书列表
   */
  // 2.1 新增弹窗-->显示和隐藏
  const addModalDom = document.querySelector('.add-modal')
  const addModal = new bootstrap.Modal(addModalDom)
  // 保存按钮-->点击->隐藏弹窗
  document.querySelector('.add-btn').addEventListener('click', () => {
      // 2.2 收集表单数据
      const addForm = document.querySelector('.add-form')
      const bookObj = serialize(addForm, {hash: true, empty: true})
      console.log(bookObj)
      //提交到服务器保存
      axios({
          url: 'https://hmajax.itheima.net/api/books',
          method: 'post',
          data: {
              ...bookObj,
              creator,
          },
      }).then(result => {
          console.log(result)
          // 2.3 添加成功后,重新请求并渲染图书列表
          getBookList();
          // 重置表单
          addForm.reset()
          // 隐藏弹窗
          addModal.hide()
      })
  })
  
  /**
   * 目标3:删除图书
   * 3.1 删除元素绑定点击事件->获取图书ID
   * 3.2 调用删除接口
   * 3.3 刷新图书列表
   */
  
  // 3.1 删除元素->(事件委托)
  document.querySelector('.list').addEventListener('click', event => {
      // console.log(event.target)
      if (event.target.classList.contains('del')) {
          // console.log('删除')
          const theID = event.target.parentNode.dataset.id
          console.log(theID)
          // 3.2 删除元素
          axios({
              url: `https://hmajax.itheima.net/api/books/${theID}`,
              method: 'delete',
          }).then(() => {
              // 3.3 刷新图书列表
              getBookList()
          })
      }
  })
  
  /**
   * 目标4:编辑图书
   * 4.1 编辑弹窗->显示和隐藏
   * 4.2 获取当前编辑图书数据->回显到编辑表单中
   * 4.3 提交保存修改,并刷新列表
   */
  
  // 4.1 编辑弹窗->显示和隐藏
  const editDom = document.querySelector('.edit-modal')
  const editModal = new bootstrap.Modal(editDom)
  // 编辑元素->点击->显示弹窗
  document.querySelector('.list').addEventListener('click', event => {
      // 判断点击的是否为编辑元素
      if (event.target.classList.contains('edit')) {
          // console.log('编辑')
          // 4.2 获取当前编辑图书数据->回显到编辑表单中
          const theID = event.target.parentNode.dataset.id
          console.log(theID)
          axios({
              url: `https://hmajax.itheima.net/api/books/${theID}`,
          }).then(result => {
              // 查看数据
              console.log(result)
              const bookObj = result.data.data
              // 遍历数据对象,使用属性去获取对应的标签
              const keys = Object.keys(bookObj)
              keys.forEach(key => {
                  document.querySelector(`.edit-form .${key}`).value = bookObj[key]
              })
          })
          editModal.show()
      }
  })
  // 修改按钮->点击->隐藏弹窗
  document.querySelector('.edit-btn').addEventListener('click', () => {
      // 4.3 提交保存修改,并刷新列表
      const editForm = document.querySelector('.edit-form')
      const {id, bookname, author, publisher} = serialize(editForm, {hash: true, empty: true})
      axios({
          url: `https://hmajax.itheima.net/api/books/${id}`,
          method: 'put',
          data: {
              bookname,
              author,
              publisher,
              creator,
          },
      }).then(() => {
          getBookList()
          editModal.hide()
      })
  })  

</script>

图片上传

使用 FormData 携带图片文件

const fd = new FormData()
fd.append(参数名,值)
<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
              name="viewport">
        <meta content="ie=edge" http-equiv="X-UA-Compatible">
        <title>图片上传</title>
        <script src="../dist/http_cdn.bootcdn.net_ajax_libs_axios_1.2.0_axios.js"></script>
    </head>
    <body>
        <input class="upload" type="file">
        <img alt="" class="my-img" src="">

        <script>
            /**
             * 目标: 图片上传,显示在网页上
             * 1. 获取图片文件
             * 2. 使用 FormData 将图片文件传递给后端
             * 3. 提交到服务器,获取图片url网址并使用
             */
            // 文件选择元素 -> change改变事件
            document.querySelector('.upload').addEventListener('change', e => {
                // 获取文件对象
                console.log(e.target.files[0])
                // 创建FormData对象
                const fd = new FormData()
                // 将文件对象添加到FormData中
                fd.append('img', e.target.files[0])
                // 发送请求
                axios({
                    url: 'https://hmajax.itheima.net/api/uploadimg',
                    method: 'post',
                    data: fd,
                }).then(result => {
                    console.log(result)
                    // 将图片地址显示在网页上
                    const imgUrl = result.data.data.url
                    document.querySelector('.my-img').src = imgUrl
                })
            })
        </script>
    </body>
</html>

案例三:更换背景

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
              name="viewport">
        <meta content="ie=edge" http-equiv="X-UA-Compatible">
        <title>Document</title>
        <style>
            .upload {
                display: none;
            }

            .bq {
                width: fit-content;
                padding: 10px;
                background: #000;
                color: #fff;
                border-radius: 10px;
            }
        </style>
        <script src="../dist/http_cdn.bootcdn.net_ajax_libs_axios_1.2.0_axios.js"></script>
    </head>
    <body>
        <div class="bq">
            <label for="upload">更换背景</label>
            <input class="upload" id="upload" type="file">
        </div>

        <script>
            document.querySelector('.upload').addEventListener('change', e => {
                // 选择图片上传,设置body背景
                const formData = new FormData()
                formData.append('img', e.target.files[0])
                axios({
                    url: 'https://hmajax.itheima.net/api/uploadimg',
                    method: 'post',
                    data: formData,
                }).then(result => {
                    // 获取上传服务器后的返回地址
                    const imgUrl = result.data.data.url
                    // 设置body背景
                    document.body.style.backgroundImage = `url(${imgUrl})`
                    // 上传成功时保存在本地
                    localStorage.setItem('bg', imgUrl)
                })
            })
            // 页面加载时,如果本地有背景,则设置body背景
            const bgUrl = localStorage.getItem('bg')
            document.body.style.backgroundImage = `url(${bgUrl})`
        </script>
    </body>
</html>

案例四:个人信息设置

<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta content="IE=edge" http-equiv="X-UA-Compatible">
        <meta content="width=device-width, initial-scale=1.0" name="viewport">
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
        <!-- 核心样式 -->
        <link href="./css/index.css" rel="stylesheet">
        <title>个人信息设置</title>
    </head>

    <body>
        <!-- toast 提示框 -->
        <div class="toast my-toast" data-bs-delay="1500">
            <div class="toast-body">
                <div class="alert alert-success info-box">
                    操作成功
                </div>
            </div>
        </div>
        <!-- 核心内容区域 -->
        <div class="container">
            <ul class="my-nav">
                <li class="active">基本设置</li>
                <li>安全设置</li>
                <li>账号绑定</li>
                <li>新消息通知</li>
            </ul>
            <div class="content">
                <div class="info-wrap">
                    <h3 class="title">基本设置</h3>
                    <form action="javascript:;" class="user-form">
                        <div class="form-item">
                            <label for="email">邮箱</label>
                            <input autocomplete="off" class="email" id="email" name="email" placeholder="请输入邮箱"
                                   type="text">
                        </div>
                        <div class="form-item">
                            <label for="nickname">昵称</label>
                            <input autocomplete="off" class="nickname" id="nickname" name="nickname" placeholder="请输入昵称"
                                   type="text">
                        </div>
                        <div class="form-item">
                            <label>性别</label>
                            <label class="male-label"><input class="gender" name="gender" type="radio"
                                                             value="0">男</label>
                            <label class="male-label"><input class="gender" name="gender" type="radio"
                                                             value="1">女</label>
                        </div>
                        <div class="form-item">
                            <label for="desc">个人简介</label>
                            <textarea autocomplete="off" class="desc" cols="20" id="desc" name="desc"
                                      placeholder="请输入个人简介" rows="10"></textarea>
                        </div>
                        <button class="submit">提交</button>
                    </form>
                </div>
                <div class="avatar-box">
                    <h4 class="avatar-title">头像</h4>
                    <img alt="" class="prew" src="./img/头像.png">
                    <label for="upload">更换头像</label>
                    <input class="upload" id="upload" type="file">
                </div>

            </div>
        </div>
        <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js"></script>
        <script src="./lib/form-serialize.js"></script>
        <!-- 核心逻辑 -->
        <script src="./js/index.js"></script>
    </body>

</html>

01>信息渲染

/**
 * 目标1:信息渲染
 *  1.1 获取用户的数据
 *  1.2 回显数据到标签上
 * */
const creator = 'parlocc'
axios({
    url: 'https://hmajax.itheima.net/api/settings',
    params: {
        creator,
    },
}).then(result => {
    const userObj = result.data.data
    console.log(userObj)
    Object.keys(userObj).forEach(key => {
        if (key === 'avatar') {
            document.querySelector('.prew').src = userObj[key];
        } else if (key === 'gender') {
            const gRadio = document.querySelectorAll('.gender')
            const gNum = userObj[key]
            console.log(userObj[key])
            gRadio[gNum].checked = true
        } else {
            document.querySelector(`.${key}`).value = userObj[key]
        }
    })
})

02>修改头像

/**
 * 目标2:修改头像
 *  2.1 获取头像文件
 *  2.2 提交服务器并更新头像
 * */
document.querySelector('.upload').addEventListener('change', e => {
    console.log(e.target.files[0])
    const fd = new FormData()
    fd.append('avatar', e.target.files[0])
    fd.append('creator', creator)

    axios({
        url: 'https://hmajax.itheima.net/api/avatar',
        method: 'put',
        data: fd,
    }).then(result => {
        console.log(result)
        const imgUrl = result.data.data.avatar
        document.querySelector('.prew').url = imgUrl
    })
})

03>提交表单

/**
 * 目标3:提交表单
 *  3.1 收集表单信息
 *  3.2 提交服务器保存
 * */

document.querySelector('.submit').addEventListener('click', () => {
    const userFrom = document.querySelector('.user-form')
    const userObj = serialize(userFrom, {hash: true, empty: true})
    userObj.creator = creator
    // 性别数字字符串,转换成数字类型
    userObj.gender = +userObj.gender
    console.log(userObj)
    axios({
        url: 'https://hmajax.itheima.net/api/settings',
        method: 'put',
        data: userObj,
    }).then(result => {
        const toastDom = document.querySelector('.my-toast')
        const toast = new bootstrap.Toast(toastDom)
        toast.show()
    })
})

Fetch函数

<button>AJAX</button>
<script>
    const btn = document.querySelector('button');
    btn.onclick = function () {
        fetch('http://127.0.0.1:8000/fetch-server?parlo=31', {
            //请求方法
            method: 'POST',
            //请求头
            headers: {
                name: 'parlo'
            },
            //请求体
            body: 'username=admin&password=123456'
        }).then(response => {
            return response.json();
        }).then(response => {
            console.log(response);
        });
    };
</script>

跨域

同源策略

同源策略(Same-Origin Policy)最早由 Netscape 公司提出,是浏览器的一种安全策略。

同源: 协议、域名、端口号 必须完全相同。

而违背同源策略就是跨域。

JSONP

JSONP(JSON with Padding),是一个非官方的跨域解决方案,纯粹凭借程序员的聪明

才智开发出来,只支持 get 请求。

用户名:<input id="username" type="text"/>
<p></p>
// 用户名检测是否存在
app.all('/check-username', (request, response) => {
    const data = {
        exist: 1,
        msg: '用户名已经存在'
    };
    let str = JSON.stringify(data);
    response.send(`handle(${str})`);
});
<script>
    //获取input元素
    const input = document.querySelector('input');
    const p = document.querySelector('p');

    // 声明handle函数
    function handle(data) {
        input.style.border = '1px solid red';
        //修改p标签的提示文本
        p.innerHTML = data.msg;
    }

    //绑定事件
    input.onblur = function () {
        //获取用户输入的值
        let username = this.value;
        //向服务器发送请求 检测用户名是否存在
        //1、创建script标签
        const script = document.createElement('script');
        //2、设置script标签的src属性
        script.src = 'http://127.0.0.1:8000/check-username';
        //3、将script标签添加到页面中
        document.body.appendChild(script);
    };
</script>

jQuery发送JSONP

app.all('/jquery-jsonp-server', (request, response) => {
    // response.send('HELLO IE! 2~~~');
    const data = {
        name: '中国',
        city: ['北京', '上海', '青岛']
    };
    let str = JSON.stringify(data);
    // 接受callback参数
    let cb = request.query.callback;
    // 拼接数据
    response.send(`${cb}(${str})`);
});
<button>点击发送jsonp请求</button>
<div id="result"></div>
<script>
    $('button').eq(0).click(function () {
        $.getJSON('http://127.0.0.1:8000/jquery-jsonp-server?callback=?', function (data) {
            // console.log(data);
            $('#result').html(
                `
                 名称:${data.name}<br>
                 城市: ${data.city}
                `
            );
        });
    });
</script>

CORS

CORS(Cross-Origin Resource Sharing),跨域资源共享。

CORS 是官方的跨域解决方案,它的特点是不需要在客户端做任何特殊的操作,完全在服务器中进行处理,支持 get 和 post 请求。跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些 源站通过浏览器有权限访问哪些资源。

<button>点击发送jsonp请求</button>
<div id="result"></div> 
javas//cors
app.all('/cors-server', (request, response) => {
    //设置响应头
    response.setHeader('Access-Control-Allow-Origin', '*');
    response.setHeader('Access-Control-Allow-Headers', '*');
    response.setHeader('Access-Control-Allow-Method', '*');
    response.send('hello cors');
});
<script>
    const btn = document.querySelector('button');

    btn.onclick = function () {
        //1. 创建对象
        const xhr = new XMLHttpRequest();
        //2. 设置请求方法和url
        xhr.open('GET', 'http://127.0.0.1:8000/cors-server');
        //3. 发送
        xhr.send();
        //4. 监听事件
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    console.log(xhr.response);
                }
            }
        };
    };
</script>