day19JS-AJAX数据通信

1. 什么是AJAX

原生生js中有两种通信 ,一个ajax ,还有一个是fetch

AJAX 并不是编程语言,是一种从网页访问 Web 服务器的技术AJAX 代表异步 JavaScript 和 XML

AJAX 使用浏览器内建的 XMLHttpRequest 对象 从 web 服务器请求数据 ,在使用JavaScriptHTML DOM来显示或使用数据 。Ajax 允许通过与场景后面的 Web 服务器交换数据来异步更新网页。这意味着可以更新网页的部分,而不需要重新加载整个页面

2. AJAX如何工作?

  1. 网页中发生一个事件(页面加载、按钮点击)。
  2. 由 JavaScript 创建 XMLHttpRequest 对象
  3. XMLHttpRequest 对象向 web 服务器发送HttpRequest请求
  4. 服务器处理HttpRequest请求
  5. 服务器将响应发送回网页
  6. 由 JavaScript 读取响应
  7. 由 JavaScript 更新页面

3. XMLHttpRequest 对象详解

XMLHttpRequest 对象 用于与服务器交互数据。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据

3.1 AJAX使用步骤

所有现代浏览器都支持XMLHttpRequest 对象 。XMLHttpRequest 对象可用于在后台与 Web 服务器交换数据。这意味着可以更新网页的部分内容,而无需重新加载整个页面。

1. 创建 XMLHttpRequest 对象的语法:

var 实例化对象名 = new XMLHttpRequest();

javascript 复制代码
var xhr = new XMLHttpRequest();
  1. 定义回调函数 。回调函数是作为参数传递给另一个函数的函数。在这种情况下,回调函数应包含响应准备就绪时要执行的代码
javascript 复制代码
xhr.onload = function() {
  // 当响应准备就绪时要做什么
}

3. 发送请求。 向服务器发送请求,使用 XMLHttpRequest 对象的 open() 和**send()**方法。

案例:

javascript 复制代码
xhr.open("GET", "http://localhost:4000");
xhr.send();

3.2 跨域访问(Access Across Domains)

出于安全原因,现代浏览器一般不允许跨域访问。但是可以使用跨域的,可以把跨域写入响应头中。

跨域访问书写语法:响应对象.writeHead(响应码,{跨域设置})

案例:

javascript 复制代码
// 引入http
const http = require("http");
// 创建一个服务对象,req:请求对象, res:响应对象
http.createServer(function (req, res) {
    // 编写响应头,200时成功码,
    res.writeHead(200, {
        "Access-Control-Allow-Origin": "*"
    })
    res.end("a");
}).listen(process.env.PORT);//侦听来访问该端口的请求

"Access-Control-Allow-Origin":"*" :响应标头指定了该响应的资源是否被允许与给定的来源(origin)共享。

"Access-Control-Allow-Methods":"*":允许所有方法跨域

"Access-Control-Allow-Headers":"*":允许所有标头跨域。

3.3 XMLHttpRequest 对象的属性

|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 属性 | 描述 |
| onload | 定义接收到(加载)请求时调用的函数。使用 XMLHttpRequest 对象时,可以定义一个回调函数,以便在请求收到答复时执行。 |
| onreadystatechange | 定义当 readyState 属性发生变化时调用的函数。 |
| readyState | 保存 XMLHttpRequest 的状态。每当readyState 发生变化时就会调用 onreadystatechange 函数。 * 0:创建ajax并实例化对象。 * 1:服务器连接已建立(open打开),并且发送请求头。 * 2:send发送消息体,并且请求对象已收到响应头。 * 3:接收服务端发送的消息,正在接收处理请求。 * 4:接收服务端发送数据完成,并且响应已就绪。 |
| responseText | 是
服务器响应属性,
字符串形式返回响应数据。 |
| responseXML | 是服务器响应属性,XML 数据返回响应数据。 |
| status | 返回请求的状态号 * 200: "OK" * 403: "Forbidden" * 404: "Not Found" |
| statusText | 返回状态文本(比如 "OK" 或 "Not Found") |

3.4 XMLHttpRequest 对象的方法

|----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 方法名 | 描述 |
| new XMLHttpRequest() | 创建新的 XMLHttpRequest 对象。 |
| abort() | 取消当前请求。 |
| getAllResponseHeaders() | 是服务器响应方法 ,从服务器返回所有头部信息。 |
| getResponseHeader() | 是服务器响应方法, 从服务器返回特定的头部信息。 |
| open(method, url, async, user, password) | 设置请求访问服务器的各种参数。 参数: * method:请求类型 GET 或 POST。Method使用GET时,URL可以是本地路径,可以是网络路径http和https。 * url:被访问服务器的url(http://+ip+端口号)。 * async:是否异步 。true(异步)或 false(同步)。如需异步发送请求,open() 方法的 async参数必须设置为 true如果是false同步就不属于使用侦听事件了。 * user:可选的用户名。 * password:可选的密码。 |
| send() | 向服务器发送请求的方法。 该方法
无参数用于 GET 请求
。 |
| send(string) | 是向服务器发送请求的方法 。该方法有参数用于 POST 请求。 |
| setRequestHeader(header,value) | 向请求添加 HTTP 头部。将标签/值对添加到要发送的标头。 参数: * header **:**规定头部名称。 * value **:**规定头部值。 |

3.4.1 open()方法中的参数 method请求类型详解

  1. GET
  2. POST:主要用于向服务端发送消息,发送的内容通过send发送 ,可以发送文本、二进制流、大二进制流(Blob)、FormData、文档 。可以接收消息,不会调用缓存,POST发送时同域情况下会携带cookie,跨域时需要主动携带cookie。POST是先发起请求头,然后再发起消息体。
  3. PUT:目的是表意,向服务器添加数据,和post作用相同。
  4. DELETE:目的是表意,向服务器提交删除数据,和post作用相同。
  5. OPTIONS :在使用POST、PUT、DELETE跨域时都会触发。

使用非GET和POST,跨域需要服务器同意,因为Method也需要处理跨域。

3.4.2 请求头和响应头

1. 请求头的注意事项:

  • 请求头必须写在open之后,send之前。使用setRequestHeader()方法 设置请求头。
  • 请求头写法单词首字母大写,单词直接使用-分割,如果是自定义请求头使用X-开头
  • 例如:setRequestHeader("Content-Type","application/json")这行代码的作用是告诉服务器发送的请求体 中包含的是 JSON 格式的数据,这样服务器才能正确地解析这些数据。

2. 请求标头中的属性重点详解Content-Type:

Content-Type指定请求体的内容类型,用于告知服务器请求体的数据类型(如表单数据、JSON数据等)。

Content-Type的属性值可以是以下类型:

  • text/plain:纯文本类型,不包含任何格式控制字符,通常用于传输普通文本数据。
  • text/html:HTML文档类型,用于传输HTML格式的网页内容。
  • application/json:JSON数据类型,用于传输结构化的JSON数据。
  • application/xml:XML数据类型,用于传输结构化的XML数据。
  • multipart/form-data:用于通过HTTP POST方法传输表单数据,支持传输文件和文本数据,常用于文件上传。
  • application/x-www-form-urlencoded:用于通过HTTP POST方法传输表单数据,将表单字段以URL编码的形式包含在请求体中。
  • image/jpegimage/pngimage/gif:图片类型,用于传输图片文件的内容。
  • application/octet-stream:二进制数据流类型,表示不属于其他已知类型的任意二进制数据,例如传输文件时常用的类型。

其它属性如图所示:

3. 响应头的注意事项:

  • 获取所有的响应头使用getAllResponseHeaders()方法。

属性如图所示:

4. timeout超时

timeout是一个无符号长整型数 ,代表着一个请求在被自动终止前所消耗的毫秒数。默认值为 0,意味着没有超时

案列:

javascript 复制代码
    // timeout超时
    var xhr = new XMLHttpRequest();
    xhr.addEventListener("load", loadHandler);
    xhr.addEventListener("timeout", timeoutHandler);
    xhr.open("PUT", "http://localhost:4000");
    xhr.timeout = 1;//如果1毫秒之后服务器还没有返回数据就算超时
    xhr.send();

    function loadHandler(e) {
      console.log(xhr.response);
    }

    function timeoutHandler(e) {
      // console.log(e);
      xhr.abort();//断开xhr
      // 断开后重新调用ajax,3次
    }

超时过后会得到一个ProgressEvent事件

解决超时的方法:使用abort();断开请求,断开后重新调用ajax3次。

5. 服务器使用AJAX的案例

5.1 最简单的前端AJAX通信

  1. 创建server文件夹。

  2. 在集成终端中执行npm init -y 初始得到package.json文件。

  3. 编写package.json文件。

javascript 复制代码
{
  "name": "serverzixie",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "cross-env PORT=4000 nodemon"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "nodemon": "^3.1.4",
    "cross-env": "^7.0.3"
  }
}

4.在集成终端中执行npm i 。

  1. 编写server文件并在集成终端中使用npm start启动服务。
javascript 复制代码
// 引入http
const http = require("http");
// 创建一个服务对象,req:请求对象, res:响应对象
http.createServer(function (req, res) {
    // 编写响应头,200时成功码,
    res.writeHead(200, {
        "Access-Control-Allow-Origin": "*"
    })
    res.end("a");
}).listen(process.env.PORT);//侦听来访问该端口的请求
  1. 编写前端发送请求的html文件。
javascript 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
</head>

<body>
  <script>
    // 创建一个
    var xhr = new XMLHttpRequest();
    xhr.addEventListener("load", loadHandler);
    xhr.open("GET", "http://localhost:4000");
    xhr.send();

    function loadHandler(e) {
      console.log(xhr.response);
    }
 </script>
</body>
</html>
  1. 查看效果。

5.2 服务器使用AJAX与数据库通信

在5.1 代码的基础上继续编写代码

  1. 在server文件夹新创建一个MYSQL.js的文件。

  2. 重新编写package.json文件。

javascript 复制代码
{
  "name": "serverzixie",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "cross-env PORT=4000 nodemon"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mysql": "^2.18.1", //自己的mysql是低版本使用mysql 2.18.1
    "mysql2": "^3.11.0" ///自己的mysql是高版本使用mysql2 3.11.0
  },
  "devDependencies": {
    "nodemon": "^3.1.4",
    "cross-env": "^7.0.3"
  }
}

3.在集成终端中执行npm i 。

  1. 编写MYSQL.js文件。
javascript 复制代码
// 引入MySQL文件
const mysql = require("mysql");
// 创建一个连接
const connect = mysql.createConnection({
    host: "localhost",//IP地址
    port: 3306, //端口号
    database: "mysql_js",//连接的数据库名
    user: "root",//数据库用户名
    password: "123456" //数据库密码
})
console.log(connect);
  1. 在 server.js中引入数据库。并使用 npm start命令开启服务。
javascript 复制代码
// 引入数据库js文件
require("./MYSQL");
// 引入http
const http = require("http");
// 创建一个服务对象,req:请求对象, res:响应对象
http.createServer(function (req, res) {
    // 编写响应头,200时成功码,
    res.writeHead(200, {
        "Access-Control-Allow-Origin": "*"
    })
    res.end("a");
}).listen(process.env.PORT);//侦听来访问该端口的请求

没有报红说明连接数据库成功

6. 历史记录的相关知识

历史记录分为两种:hash历史记录history历史记录

6.1 hash历史记录

hash历史记录hash 不会刷新页面,但是会触发后产生历史记录历史记录回退时hash也会回退到上一次历史记录的hash值。 如果刷新页面hash历史记录就会没有了,因为刷新页面会重新初始化页面。

  • 访问地址**#后面的内容发生改变时就是hash在发生改变**。
  • hashchange:是一个hash事件,可以用来侦听hash值的变化。

案例:点击哪个下面的方框里就显示哪个。

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        ul {
            list-style: none;
            display: flex;
        }

        li {
            width: 160px;
            height: 30px;
            border: 1px solid #000;
        }

        li>a {
            text-decoration: none;
            display: block;
            width: 100%;
            height: 100%;
            text-align: center;
            line-height: 30px;
            color: #000;
            border-left: none;
        }

        li>a>:first-child {
            border-left: 1px solid #000;
        }

        .content {
            width: 1400px;
            height: 500px;
            border: 1px solid #000;
        }
    </style>
</head>

<body>
    <ul>
        <li><a href="#vegetable">蔬菜</a></li>
        <li><a href="#fruits">水果</a></li>
        <li><a href="#meat">肉禽</a></li>
        <li><a href="#oil">米油</a></li>
    </ul>
    <div class="content"></div>

    <script>
        let list = {
            vegetable: ["白菜", "菠菜", "胡萝卜", "油麦菜", "娃娃菜"],
            fruits: ["苹果", "香蕉", "西瓜", "桃子", "榴莲"],
            meat: ["猪肉", "牛肉", "羊肉", "鸡肉", "鸭肉"],
            oil: ["菜籽油", "橄榄油", "花生油", "大米", "小米"]
        }

        // hash 不会刷新页面,但是会触发后产生历史记录
        var content;
        init();
        function init() {
            // 获取content选择器
            content = document.querySelector(".content");
            // 侦听hash事件
            window.addEventListener("hashchange", hashChangeHandler);
            //location.hash :获取到本地的hash 
            // 点击哪个li便签就会获取a标签href属性的值
            console.log(location.hash);//例如在页面中点击蔬菜就会得到#vegetable
            // 有hash历史记录时的情况
            if (location.hash) {
                // 调用侦听的函数
                hashChangeHandler();

                // 没有有hash历史记录时的情况
            } else {
                location.href += "#vegetable"
            }
        }

        // 侦听的函数
        function hashChangeHandler(e) {
            // 获取到的hash包括#,使用slice方法获取到#后面的内容
            let name = location.hash.slice(1);
            // 把点击对应的内容写入到div盒子中
            content.innerHTML = list[name];
        }

    </script>
</body>

</html>

点击

历史记录回退:

6.2 history历史记录

  • history.back() : 回退

  • history.forward():前进

  • history.go(1) :刷新

  • history.pushState(数据,无用的标识,url(可以添加hash或者search)):参数数据不能是DOM对象,可以是字符串或者对象、数组。当使用pushState后,会向历史栈中添加一个历史数据,在使用popstate事件后,可以在不同历史中拿到这个数据。

  • history.replaceState(数据,无用的标识,url(可以添加hash或者search)) :替换历史数据。

案例:

javascript 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    ul {
      list-style: none;
      display: flex;
    }

    li {
      width: 160px;
      height: 30px;
      border: 1px solid #000;
      border-left: none;
      text-align: center;
      line-height: 30px;
    }

    li:first-child {
      border-left: 1px solid #000;
    }

    .content {
      width: 1400px;
      height: 500px;
      border: 1px solid #000;
    }
  </style>
</head>

<body>
  <ul>
    <li id="vegetable">蔬菜</li>
    <li id="fruits">水果</li>
    <li id="meat">肉禽</li>
    <li id="oil">米油</li>
  </ul>
  <div class="content"></div>
  <script>
    let list = {
      vegetable: ["白菜", "菠菜", "胡萝卜", "油麦菜", "娃娃菜"],
      fruits: ["苹果", "香蕉", "西瓜", "桃子", "榴莲"],
      meat: ["猪肉", "牛肉", "羊肉", "鸡肉", "鸭肉"],
      oil: ["菜籽油", "橄榄油", "花生油", "大米", "小米"],
    };

    let ul, content;
    init();
    function init() {
      // 获取ul
      ul = document.querySelector("ul");
      // 获取content选择器
      content = document.querySelector(".content");
      // 给ul添加侦听事件
      ul.addEventListener("click", clickHandler);
      // 当历史发生改变时(点击了前进或者回退按钮时触发)
      window.addEventListener("popstate", popStateHandler);
      if (!history.state) {
        history.replaceState("vegetable", "vegetable")
      }
      popStateHandler();
    }

    function clickHandler(e) {
      // 如果不是li,return结束
      if (e.target.nodeName !== "LI") return;
      // 向div盒子中写入被点击对象的内容
      content.innerHTML = list[e.target.id];
      // 向历史栈中添加一个历史数据
      history.pushState(e.target.id, e.target.id)
    }

    function popStateHandler(e) {
      // 获取当前的历史数据
      // console.log(history.state);
      content.innerHTML = list[history.state];
    }

  </script>
</body>

</html>

7. 编写服务器与客户端通信案例

7.1 注册功能、注册页面切换到登录页面

  1. 创建一个template的文件夹,里面有list.js、login.js、register.js、index.html文件。

  2. 再创建一个js文件夹,里面有ajax.js文件。

  3. 编写ajax.js文件。

javascript 复制代码
export default class AJAX {
    // IP地址
    static URL = "169.254.28.173";
    // 端口号
    static PORT = 4003;
    // 使用的协议
    static PROTOCOL = "http://";
    // post请求
    static POST = "post";
    // get请求
    static GET = "get";

    constructor() {

    }

    // 处理get请求的方法
    // 参数 router:对应接口路由,query:访问地址?号后面的内容, headers:请求头
    static async get(router, query = {}, headers = {}) {
        // 调用将对象转换为字符串的方法
        query = AJAX.queryStringify(query);
        // 判断路由的第一位是不是/,字符串的方法startWith接收判断字符串的第一个字符是否是某个字符
        // 不是的情况,就拼接上/
        if (!router.startsWith("/")) router = "/" + router;

        // 是的情况,拼接url。如果query没有传参则传一个空对象
        let url = AJAX.PROTOCOL + AJAX.URL + ":" + AJAX.PORT + router + (query.trim().length === 0 ? "" : "?" + query);

        // 查看一下url的拼接情况
        // console.log(url);//测试4

        // 调用ajax的方法发送请求
        return await AJAX.ajax(url, AJAX.GET, headers);
    }

    // 处理post请求的方法
    // 参数 router:对应接口路由,body:请求传输过来的数据,headers:请求头
    static async post(router, body = {}, headers = {}) {
        // 判断路由的第一位是不是/,字符串的方法startWith接收判断字符串的第一个字符是否是某个字符
        // 不是的情况,就拼接上/
        if (!router.startsWith("/")) router = "/" + router;

        // 是的情况,拼接url。如果query没有传参则传一个空对象
        let url = AJAX.PROTOCOL + AJAX.URL + ":" + AJAX.PORT + router;

        // 调用ajax的方法发送请求
        return await AJAX.ajax(url, AJAX.GET, headers, body);
    }

    //上面的get、post方法调用ajax
    // 参数 url:请求路由,method:用于判断是上面方法, headers:请求头, body:请求传输过来的数据
    static ajax(url, method, headers, body) {
        return new Promise(function (resolve, reject) {
            // XMLHttpRequest 对象可用于在后台与 Web 服务器交换数据。
            var xhr = new XMLHttpRequest();
            // 规定请求
            xhr.open(method, url);
            for (var key in headers) {
                // 设置发送的标头
                xhr.setRequestHeader(key, headers[key]);
            }
            // 如果是get请求直接发送,如果是post请求把将 JavaScript 对象转换为字符串。
            method === AJAX.GET ? xhr.send() : xhr.send(JSON.stringify(body));
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4 && xhr.status < 300 && xhr.status >= 200) {
                    let headers = AJAX.getHeaders(xhr)
                    if (headers["content-type"]?.trim() === "text/html;charset=utf-8") {
                        resolve(xhr.response)
                    } else {
                        let data;
                        try {
                            data = JSON.parse(xhr.response)
                        } catch (e) {
                            data = AJAX.getQuery(xhr.response)
                        }
                        resolve({ data, headers });
                    }
                } else if (xhr.readyState === 4) {
                    reject(xhr.status);
                }
            }
        })
    }

    // url传参是对象就将对象转换为字符串的方法。
    static queryStringify(query) {
        // 如果query是字符串直接返回query
        if (typeof query === "string") return query;
        // 如果query不是字符串先进行遍历
        let str = "";
        for (var key in query) {
            // 判断遍历的每一个元素是否是数组,这里是数组的情况
            if (Array.isArray(query[key])) {
                str += "&" + query[key].map(function (item) {
                    // 打印一下查看效果
                    // console.log(key, item);

                    // 返回结果
                    return key + "=" + item;
                }).join("&")
                // 测试1:打印查看字符串是否拼接成功
                // console.log(str);

                // query中的元素是一个对象的情况
            } else if (typeof query[key] === "object") {
                // 将对象解析成Json字符串
                str += "&" + key + "=" + JSON.stringify(query[key]);

                // 其它情况 
            } else {
                str += "&" + key + "=" + query[key];
            }
        }
        //去掉字符串第一个&符号
        str = str.slice(1);
        // 测试2、3:打印查看一下字符串
        // console.log(str);
        return str;
    }

    // 请求头
    static getHeaders(xhr) {
        return xhr.getAllResponseHeaders().split(/\n/).reduce((value, item) => {
            if (item.trim().length === 0) return value;
            let arr = item.trim().replace(/\r/, "").split(":");
            try {
                value[arr[0]] = JSON.parse(arr[1])
            } catch (e) {
                value[arr[0]] = arr[1];
            }
            return value
        }, {})
    }

    static getQuery(str) {
        if (!/.*?=.*?/.test(str)) return str;
        return str.split("&").reduce((value, item) => {
            let arr = item.split("=");
            try {
                value[arr[0]] = JSON.parse(arr[1])
            } catch (e) {
                value[arr[0]] = arr[1];
            }
            return value
        }, {})
    }
}
  1. 在index.html文件中编写测试ajax.js的代码。
html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script type="module">
        import AJAX from "./js/ajax.js";
        // AJAX.queryStringify({ a: [1, 2, 3] });//测试1
        // AJAX.queryStringify({ a: [1, 2, 3], b: { a: 1, b: 2 } });//测试2
        // AJAX.queryStringify({ a: [1, 2, 3], b: { a: 1, b: 2 }, c: 3, d: 4 });//测试3

        //测试4 ==> http://169.254.28.173:4003/login?a=1&b=2
        // AJAX.get("/login", { a: 1, b: 2 });
        //测试4 ==> http://169.254.28.173:4003/login
        AJAX.get("/login");
    </script>
</body>

</html>
  1. 在集成终端中下载jquery和bootstrap@3

执行命名:

npm i jquery

npm i bootstrap@3

  1. 整理项目结构。
  • 把node_modules --> jquery --> dist --> jquery.js移动到js文件夹下。
  • 把node_modules --> bootstrap--> dist --> js -->bootstrap.js 移动到js文件夹下。
  • 把node_modules --> bootstrap--> fonts文件夹 移动到最外层文件夹 下。template
  • 在新建一个css文件夹, node_modules --> bootstrap--> css -->bootstrap.css 移动到新建的css文件夹
  • 拉完以后把node_modules文件夹删掉 ,把package-lock.json和package.json也删掉

项目结构如下:

  1. 在js文件夹中新创建并编写VerifyForm.js表单验证类
javascript 复制代码
// 验证表单是否合法的类
export default class VerifyForm {
    static setForm(elem) {
        // 根据表单验证的结果添加样式
        if (VerifyForm.verify(elem.name, elem.value)) {
            // 从元素的父元素的选择器集合中移除has-error类选择器
            elem.parentElement.classList.remove("has-error")
            // 向元素的下一个兄弟元素的选择器集合中删除glyphicon-remove类选择器
            elem.nextElementSibling.classList.remove("glyphicon-remove")
            // 向元素的父元素的选择器集合中添加has-success类选择器
            elem.parentElement.classList.add("has-success")
            // 向元素的下一个兄弟元素的选择器集合中添加glyphicon-ok类选择器
            elem.nextElementSibling.classList.add("glyphicon-ok")
        } else {
            // 从元素的父元素的选择器集合中移除has-success类选择器
            elem.parentElement.classList.remove("has-success")
            // 向元素的下一个兄弟元素的选择器集合中删除glyphicon-ok类选择
            elem.nextElementSibling.classList.remove("glyphicon-ok")
            // 从元素的父元素的选择器集合中添加has-error类选择器
            elem.parentElement.classList.add("has-error")
            // 向元素的下一个兄弟元素的选择器集合中添加glyphicon-remove类选择器
            elem.nextElementSibling.classList.add("glyphicon-remove")
        }
    }
    // 传入form表单input标签的name属性和value属性验证输入是否合法
    static verify(name, value) {
        switch (name) {
            case "user":
                return /^\w{8,16}$/.test(value);
            case "password":
                return /^(?=\D+\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9_$#@!~]{8,16}$/.test(value);
            case "name":
                return /^[\u4e00-\u9fd5]{2,4}$/.test(value);
            case "age":
                return /^\d$|^[1-9]\d$|^1[0-2]\d$/.test(value);
            case "tel":
                return /^1[3-9]\d{9}$/.test(value);
            case "email":
                return /^\w+\@\w+\.\w+(\.\w+)?$/.test(value);
            default:
                return true;
        }
    }
}

8.编写注册页面的模板 register.js

注意!!!

自学一下bootstrap前端框架

网址 :全局 CSS 样式 · Bootstrap v3 中文文档 | Bootstrap 中文网 (bootcss.com)

javascript 复制代码
// 注册模板
// 引入ajax的类
import AJAX from "../js/ajax.js";
// 引入验证表单的类
import VerifyForm from "../js/VerifyForm.js";
const registerHTML = `
    <h3 class="text-center">注册用户</h3>
    <div class="row">
      <form action="" class="col-xs-8 col-xs-offset-2">
        <!-- <div class="form-group has-success has-feedback">
          <label class="control-label" for="user">用户名</label>
          <input type="text" class="form-control" id="user" />
          <span class="glyphicon glyphicon-ok form-control-feedback"></span>
        </div> -->
        <div class="form-group has-feedback">
          <label class="control-label" for="user">用户名:</label>
          <input type="text" class="form-control" id="user" name="user" />
          <span class="glyphicon form-control-feedback"></span>
        </div>
        <div class="form-group has-feedback">
          <label class="control-label" for="password">密码:</label>
          <input
            type="password"
            class="form-control"
            id="password"
            name="password"
            autocomplete
          />
          <span class="glyphicon form-control-feedback"></span>
        </div>
        <div class="form-group has-feedback">
          <label class="control-label" for="name">姓名:</label>
          <input type="text" class="form-control" id="name" name="name" />
          <span class="glyphicon form-control-feedback"></span>
        </div>
        <div class="form-group has-feedback">
          <label class="control-label" for="name">性别:</label>
          <label class="radio-inline">
            <input type="radio" name="sex" id="gentleman" value="男" checked/> 男士
          </label>
          <label class="radio-inline">
            <input type="radio" name="sex" id="lady" value="女" /> 女士
          </label>
        </div>
        <div class="form-group has-feedback">
            <label class="control-label" for="age">年龄:</label>
            <input type="text" class="form-control" id="age" name="age" />
            <span class="glyphicon form-control-feedback"></span>
          </div>
          <div class="form-group has-feedback">
            <label class="control-label" for="tel">电话:</label>
            <input type="text" class="form-control" id="tel" name="tel" />
            <span class="glyphicon form-control-feedback"></span>
          </div>
          <div class="form-group has-feedback">
            <label class="control-label" for="email">邮箱:</label>
            <input type="text" class="form-control" id="email" name="email" />
            <span class="glyphicon form-control-feedback"></span>
          </div>
          <div >
            <button type="submit" class="btn btn-primary col-xs-offset-4">注册</button>
            <button type="button" class="btn btn-success col-xs-offset-1 loginbn">登录</button>
          </div>
      </form>
    </div>
    `
export default function (content) {
  // 当点击注册按钮时,将注册模板注入到注册页面中
  content.innerHTML = registerHTML;
  // 获取表单
  var form = content.querySelector("form");
  // 侦听登录的点击事件,调用jumpLogin函数
  form.querySelector(".loginbn").addEventListener("click", jumpLogin);
  // 侦听表单的提交事件
  form.addEventListener("submit", submitHandler);
  // 侦听表单中的input是否有填写
  form.addEventListener("input", inputHandler);
}

function submitHandler(e) {
  // 阻止表单的默认提交行为
  e.preventDefault();
  // 获取表单提交的数据
  var fd = new FormData(this);

  // 判断表单中的input框是否输入完成
  var data = {};
  for (var [key, value] of fd) {
    if (!VerifyForm.verify(key, value)) {
      // 没有输入数据的input框会进行集焦显示提醒
      this.querySelector("[name=" + key + "]").focus();
      return;
    }
    // 每一个input验证完毕并且都填写了合法的内容再放入data对象中
    data[key] = value;
  }
  // 调用提交注册表单的函数
  registerForm(data)
}

function inputHandler(e) {
  // 表单中不是input框和input框中的name属性=sex的不进行表单验证
  if (e.target.nodeName !== "INPUT" || e.target.name === "sex") return;
  // age的input框只能输入数字
  if (e.target.name === "age") {
    e.target.value = e.target.value.replace(/\D/g, "");
  }
  // 调用验证表单的类
  VerifyForm.setForm(e.target);
}

// 提交表单的函数
async function registerForm(data) {
  // 调用ajax通信,发送请求
  let result = await AJAX.post("/register", data);
  // 弹出警示框返回表单是否提交成功的信息
  alert(result.data.result.msg)
  // 如果提交不成功,调用jumpLogin函数,重新提交表单
  if (!result.data.err) {
    jumpLogin();
  }
}

// 侦听登录的点击事件就跳转到登录页面
function jumpLogin() {
  var evt = new MouseEvent("click", { bubbles: true });
  loginform.firstElementChild.children[1].firstElementChild.dispatchEvent(evt);

}

9.编写登录页面的模板 login.js

javascript 复制代码
// 登录模板
import VerifyForm from "../js/VerifyForm.js";
import AJAX from "../js/ajax.js";

const loginText = `
         <h3 class="text-center">登录</h3>
    <div class="row">
      <form action="" class="col-xs-8 col-xs-offset-2">
        <!-- <div class="form-group has-success has-feedback">
          <label class="control-label" for="user">用户名</label>
          <input type="text" class="form-control" id="user" />
          <span class="glyphicon glyphicon-ok form-control-feedback"></span>
        </div> -->
        <div class="form-group has-feedback">
          <label class="control-label" for="user">用户名:</label>
          <input type="text" class="form-control" id="user" name="user" />
          <span class="glyphicon form-control-feedback"></span>
        </div>
        <div class="form-group has-feedback">
          <label class="control-label" for="password">密码:</label>
          <input
            type="password"
            class="form-control"
            id="password"
            name="password"
            autocomplete
          />
          <span class="glyphicon form-control-feedback"></span>
        </div>
          <div >
            <button type="submit" class="btn btn-primary col-xs-offset-4">登录</button>
            <button type="button" class="btn btn-success col-xs-offset-1">注册</button>
          </div>
      </form>
    </div>
    `
export default function (content) {
  // 当点击登录按钮时,将登录模板注入到登录页面中
  content.innerHTML = loginText;
  // 获取表单
  var form = content.querySelector("form");
  // 侦听表单的提交事件
  form.addEventListener("submit", submitHandler);
  // 侦听表单中的input是否有填写
  form.addEventListener("input", inputHandler);
}

function submitHandler(e) {
  // 阻止表单的默认提交行为
  e.preventDefault();
  // 获取表单提交的数据
  var fd = new FormData(this);
  var data = {};
  // 判断表单中的input框是否输入完成
  for (var [key, value] of fd) {
    if (!VerifyForm.verify(key, value)) {
      // 没有输入数据的input框会进行集焦显示提醒
      this.querySelector("[name=" + key + "]").focus();
      return;
    }
    // 每一个input验证完毕并且都填写了合法的内容再放入data对象中
    data[key] = value;
  }
  // 调用提交登录表单的函数
  login(data);
}

function inputHandler(e) {
  // 表单中不是input框不进行表单验证
  if (e.target.nodeName !== "INPUT") return;
  // 调用验证表单的类
  VerifyForm.setForm(e.target);
}

async function login(data) {
  // 调用ajax通信,发送请求
  let result = await AJAX.post("/login", data);
  // 弹出警示框返回表单是否提交成功的信息
  alert(result.data.result.msg);
  // 如果提交不成功的情况
  if (!result.data.err) {
    // console.log(result.data.result);
    // 存储用户名
    localStorage.user = result.data.result.user;
    // 存储token令牌
    localStorage.token = result.data.result.token;
  }
}
  1. 清除index.html 文件的内容,重新编写index.html 文件。
html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入文件 -->
    <link rel="stylesheet" href="./css/bootstrap.css">
    <script src="./js/jquery.js"></script>
    <script src="./js/bootstrap.js"></script>
</head>

<body>
    <div class="container">
        <!-- 导航条 -->
        <div class="row">
            <p class="navbar-text navbar-right">
                张三<a href="#" style="margin-left: 10px" class="navbar-link">退出登录</a>
            </p>
        </div>

        <!-- 切换注册和登录页面 -->
        <div class="col-xs-6 col-xs-offset-3 row" id="loginform">
            <ul class="nav nav-tabs nav-justified">
                <li class="active"><a href="javascript:void(0)" id="register-tab">注册</a></li>
                <li><a href="javascript:void(0)" id="login-tab">登录</a></li>
            </ul>
            <div class="content"></div>
        </div>
        <!-- 表单 -->
        <div id="list"></div>
    </div>
    <script type="module">
        import register from "./template/register.js";
        import login from "./template/login.js";
        // loginform:登录的表单,content:登录表单中的内容,prev:切换
        var loginform, content, prev;
        init();
        function init() {
            // 获取登录的表单
            loginform = document.querySelector("#loginform");
            // 获取登录表单中的内容
            content = loginform.querySelector(".content");
            // 侦听点击事件
            loginform.addEventListener("click", tabClickHandler);

            // 抛发事件
            var evt = new MouseEvent("click", { bubbles: true });
            loginform.querySelector("#register-tab").dispatchEvent(evt);
        }

        function tabClickHandler(e) {
            // 判断是否点击了a标签,这里是没有点击a标签
            if (e.target.nodeName != "A") return;
            // 点击后切换,prev有值是显示的
            if (prev) {
                // 将prev对应的页面隐藏到
                prev.className = "";
            }
            // 被点击元素的父元素
            prev = e.target.parentElement;
            // 给父元素添加类选择器,将另一个页面显示出来
            prev.className = "active";
            // 根据切换的页面使用对应的页面表单
            if (e.target.id === "register-tab") {
                register(content);
            } else {
                login(content);
            }
        }

    </script>
</body>

</html>

7.2 登录页面切换到注册页面、登录功能

  1. 继续完善login.js 。

  2. 在js文件夹下新建并编写一个Hash.js的文件,用路由方法来实现登录页面和注册页面的跳转。

相关推荐
铅华尽3 分钟前
前端---HTML(一)
前端
无限大.4 分钟前
0基础学前端系列 -- 深入理解 HTML 布局
前端·html
珹洺4 分钟前
从 HTML 到 CSS:开启网页样式之旅(开篇之一)——CSS 初体验与网页样式新征程
前端·javascript·css·前端框架·html
前端Hardy12 分钟前
HTML&CSS:翻书加载效果
前端·javascript·css·3d·html·css3
问道飞鱼14 分钟前
【前端知识】SCSS(Sassy CSS)是一种CSS预处理器语言
前端·css·less·scss
snow@li30 分钟前
vue3 + ts:开发插件 / Plugins / 注册全局实例 / 在 template 与 setup 中使用 / provide、inject
前端·javascript·vue.js
命运之光1 小时前
【经典】高级动态抽奖系统(HTML,CSS、JS)
前端·css·html
დ旧言~1 小时前
实战项目 Boost 搜索引擎
服务器·c语言·前端·网络·汇编·c++
森屿Serien1 小时前
Javaweb关于web.xml的相关配置信息
xml·前端
2301_801074151 小时前
初始ArkUI
javascript·css·html5·arkts