Ajax编程

Ajax 基础

传统网站中存在的问题

  • 网速慢的情况下,页面加载时间长,用户只能等待
  • 表单提交后,如果一项内容不合格,需要重新填写所有表单内容
  • 页面跳转,重新加载页面,造成资源浪费,增加用户等待时间

Ajax 概述

Ajax,中文音译:阿贾克斯

它是浏览器提供的一套方法,可以实现页面无刷新更新数据,提高用户浏览网站应用的体验。

Ajax 的应用场景

  1. 页面上拉加载更多数据
  2. 列表数据无刷新分页
  3. 表单项离开焦点数据验证
  4. 搜索框提示文字下拉列表

Ajax 的运行环境

Ajax 技术需要运行在网站环境中才能生效,当前课程会使用 Node 创建的服务器作为网站服务器


Ajax 运行原理及实现

Ajax 运行原理

Ajax 相当于浏览器发送请求与接收响应的代理人,以实现不影响用户浏览页面的情况下,局部更新页面数据,从而提高用户体验

Ajax 的实现步骤

  1. 创建 Ajax 对象
	const xhr = new XMLHttpRequest()
  1. 告诉 Ajax 请求地址以及请求方式
	xhr.open('get', 'http://www.example.com')
  1. 发送请求
	xhr.send()
  1. 获取服务器端给予客户端的响应数据
	xhr.onload = function () {
        console.log(xhr.responseText)
    }

服务器响应的数据格式

在真实的项目中,服务器端大多数情况下会以JSON对象作为响应数据的格式。当客户端拿到响应数据时,要将JSON数据和HTML字符串进行拼接,然后将拼接的结果展示在页面中

在 http 请求与响应的过程中, 无论是请求参数还是响应内容,如果是对象类型,最终都会被转换为对象字符串进行传输

	JSON.parse() // 将 JSON 字符串转换为 JSON 对象

请求参数传递

传统网站表单提交

	<form method="get" action="http://www.example.com">
        <input type="text" name="username">
        <input type="password" name="password">
	</form>

	<!-- http://www.example.com?username=zhangsan&password=123456 -->
  • GET 请求方式
	xhr.open('get', 'http://www.example.com?name=zhangsan&age=20')
  • POST 请求方式
	xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
	xhr.send('name=zhangsan&age=20')

请求报文

在 HTTP 请求和响应的过程中传递的数据库就叫报文,包括要传送的数据和一些附加信息,这些数据和信息要遵守规定好的格式

请求参数的格式

  1. application/x-www-form-urlencoded
	name=zhangsan&age=20&sex=
  1. appclication/json
	{name: 'zhang', age: '20', sex: '男'}

在请求头中指定 Content-Type 属性的值是 application/json,告诉服务器当前请求参数的格式是json

	JSON.stringify() // 将JSON对象转换为json字符串

获取服务器端的响应

Ajax 状态码

在创建 Ajax 对象,配置 Ajax 对象,发送请求,以及接收完服务器端响应数据,这个过程中的每一个步骤都会对应一个数值,这个数值就是 Ajax 状态码

0:请求未初始化(还没调用open())

1:请求已经建立,但是还没有发送(还有调用send())

2:请求已经发送

3:请求正在处理中,通常响应中已经有部分数据可以用了

4:响应已经完成,可以获取并使用服务器的响应了

	xhr.readyState // 获取 Ajax 状态码

获取服务器端的响应

onreadystatechange 事件

当 Ajax 状态码发生变化时将自动触发该事件

注意:该事件需要写在发送请求(xhr.send())之前

两种获取服务器端响应方式的区别

区别描述onload事件onreadystatechange事件
是否兼容ie低版本不兼容兼容
是否需要判断Ajax状态码不需要需要
被调用次数一次多次

Ajax 错误处理

  1. 网络通畅,服务器端能接收到请求,服务器端返回的结果不是预期结果。可以判断服务器端返回的状态码,分别进行处理。xhr.status 获取http状态码
  2. 网络通畅,服务器没有接收到请求,返回404状态码。检查请求地址是否错误
  3. 网络通畅,服务器端能接收到请求,服务器端返回500状态码。服务器端错误,找后端程序员进行沟通
  4. 网络中断,请求无法发送到服务器端。会触发xhr对象下面的onerror事件,在onerror事件处理函数中对错误进行处理

低版本 ie 浏览器的缓存问题

问题:在低版本的 ie 浏览器中,Ajax请求有严重的缓存问题,即在请求地址不发生变换的情况下,只有第一次请求会真正发送到服务器端,后续的请求都会从浏览器的缓存中获取结果。即使服务器端的数据更新了,客户端依赖拿到的是缓存中的旧数据

解决方案:在请求地址的后面加请求参数,保证每一次请求中的请求参数的值不相同

	xhr.open('get', `http://www.example.com?t=${Math.random()}`)

Ajax 异步编程

同步异步概述

同步

  • 一个人同一时间只能做一件事情,只有一件事情做完,才能做另外一件事情。
  • 落实到代码中,就是上一行代码执行完成后,才能执行下一行代码,即代码逐行执行。
	console.log('before')
	console.log('after')

异步

  • 一个人一件事情做了一半,转而去做其他事情,当其他事情做完以后,再回过头来继续做之前未完成的事情。
  • 落实到代码上,就是异步代码虽然需要花费时间去执行,但程序不会等待异步代码执行完成后再继续执行后续代码,而是直接执行后续代码,当后续代码执行完成后再回头看异步代码是否返回结果,如果已有返回结果,再调用事先准备好的回调函数处理异步代码执行的结果
	console.log('before')
	setTimeout(() => {
        console.log('last')
    }, 2000)
	console.log('after')

Ajax 封装

问题:发送一次请求代码过多,发送多次请求代码冗余且重复

解决方案:将请求代码封装到函数中,发请求时调用函数即可

	ajax({
        method: 'get',
        url: 'http://www.example.com',
        data: {
            name: 'zhangsan',
            age: 20,
        },
        header: { // 改进版本可传递多个请求头
            'Content-Type': 'application/json',
            Accept: '/',
        },
        success: function (data) {
            console.log(data)
        },
        error: function (data) {
            console.log(data)
        }
    })
	 function ajax(options) {
        let defaults = {
            method: 'get',
            url: '',
            data: {},
            header: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            success: function() {},
            error: function() {},
        }
        // 使用options对象中的属性覆盖defaults对象中的属性
        Object.assign(defaults, options)
        // 创建 Ajax 对象
        const xhr = new XMLHttpRequest()
        let params = ''
        // 循环用户传递进来的对象格式参数
        for (var attr in options.data) {
            params += attr + '=' + options.data[attr] + '&'
        }
        // 将参数最后面的&截取掉
        // 将截取的结果重新赋值给params变量
        params = params.slice(0, params.length - 1)
        // 判断请求方式
        if (options.method == 'get') {
            options.url = options.url + '?' + params
        }
        // 配置 Ajax 对象
        xhr.open(options.method, options.url)
        // 如果请求方式为post
        if (options.method == 'post') {
            // 用户希望向服务器端传递的请求参数
            let contentType = options.header['Content-Type']
            // 设置请求参数格式的类型
            xhr.setRequestHeader('Content-Type', contentType)
            if (contentType == 'application/json') {
                xhr.send(JSON.stringify(params))
            } else {
                xhr.send(params)
            }
        } else {
            // 发送请求
            xhr.send()
        }
        
        // 监听 xhr 对象下面的 onload 事件
        // 当 xhr 对象接收完响应数据后触发
        xhr.onload = function() {
            // 获取响应头中的数据
            let contentType = xhr.getResponseHeader('Content-Type')
            // 服务器端返回的数据
            const responseText = xhr.responseText
            // 如果响应类型中包含application/json
            if (contentType.includes('application/json')) {
                // 将 json 字符串转换为 JSON 对象
                responseText = JSON.parse(responseText)
            }
            // 当http状态码等于200的时候
            if (xhr.status == 200) {
                // 请求成功 调用处理成功情况的函数
                options.success(responseText)
            } else {
                // 请求失败 调用处理失败情况的函数
                options.error(responseText, xhr)
            }
        }
    }

改进之后的版本

function ajax (options) {
    return new Promise((resolve, reject) => {
		const xhr = new XMLHttpRequest();
	    // 默认配置项
	    const default_options = {
	        method: 'get',      // 默认为 get 请求
	        url: '',
	        body: {},
	        headers: {},
	        // success: function () { },
	        // error: function () { },
	        progress: function () { },
	    }
	    // 使用 options 对象中的属性覆盖 default_options 对象中的属性
	    options = Object.assign(default_options, options);
	    // 定义接收参数字符串
	    let params = '';
	    // 判断需要发送的数据是否为一个对象, 再进行循环
	    if (Object.prototype.toString.call(options.body) === '[object Object]') {
	        // 循环需要传递的参数 name=zs&age=20
	        for (let k in options.body) {
	            params += `${ k }=${ options.body[k] }&`;
	        }
	    }
	    // 截取掉参数最后面的 & 
	    params = params.slice(0, params.length - 1);
	    // headers 配置参数 keys values
	    const keys = Object.keys(options.headers);
	    const values = Object.values(options.headers);
	    // 请求方式
	    const method = options.method.toLowerCase();
	    // 请求地址
	    let url = options.url;
	    // 判断请求方式
	    if (method === 'get') {
	        // 解决低版本 ie 缓存问题
	        url = url + '?' + params + '&t=" + Math.random();
	    } else {
	    	url = `${url}?t=${Math.random()}`;
	    }
	    // 配置 xhr
	    xhr.open(method, url);
	    // 如果请求头存在,设置请求头
	    if (keys.length > 0) {
	        for (let i = 0; i < keys.length; i++) {
	            xhr.setRequestHeader(keys[i], values[i]);
	        }
	    }
	    xhr.upload.onprogress = options.progress;
	    // 如果请求方式为 get head 以外的方式
	    if (method !== 'get' && method !== 'head') {
	        // 判断发送数据是否为一个对象
	        if (Object.prototype.toString.call(options.data) === '[object Object]') {
	            for (let i = 0; i < keys.length; i++) {
	                // 用户希望向服务器端传递的请求参数
	                if (keys[i] === 'Content-Type') {
	                    if (values[i].includes('application/json')) {
	                        xhr.send(JSON.stringify(options.body))
	                    } else {
	                        xhr.send(params);
	                    }
	                }
	            }
	        } else {
	            xhr.send(options.body);
	        }
	    } else {
	        xhr.send();
	    }
	    xhr.onload = function () {
	        // 响应状态码
	        const status = xhr.status;
	        // 获取响应头中的数据
	        const header = xhr.getResponseHeader('Content-Type');
	        // 服务器响应的消息内容
	        let response = xhr.response;
	        // 如果响应类型中包含application/json
	        if (header.includes('application/json')) {
	            response = JSON.parse(response);
	        }
	        // 请求成功
	        if (status >= 200 && status < 300 || status === 304) {
	            // options.success(response)
	            resolve(response);
	        } else {
	            // options.error(response, xhr);
	            reject(response, xhr);
	        }
	    }
	})
}

案例

验证表单内容

<!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>Document</title>
    <style>
        * {
            box-sizing: border-box;
        }

        .box {
            display: flex;
            flex-direction: column;
            width: 400px;
            height: 400px;
            margin: 100px auto;
        }

        input {
            outline: none;
        }

        .box input,
        .msg {
            width: 100%;
            height: 40px;
            padding-left: 12px;
        }

        .msg {
            display: none;
            height: 60px;
            line-height: 60px;
            padding-left: 24px;
            font-size: 18px;
        }

        .error {
            background-color: #f5e0df;
        }

        .success {
            background-color: #deecd8;
        }
    </style>
</head>

<body>
    <div class="box">
        <input type="email"
            name=""
            id="email"
            placeholder="请输入邮箱地址"
            autocomplete="off">
        <p class="msg"></p>
    </div>
</body>

</html>
<script src="./ajax.js"></script>
<script>
    let ipt = document.querySelector('input');
    let msg = document.querySelector('.msg');
    const em_reg = /^\w{6,}(@\w*[-.]?\w{2,}[.]\w{2,})$/;
    ipt.addEventListener('blur', function () {
        if (this.value.trim() !== '') {
            if (em_reg.exec(this.value)) {
                ajax({
                    url: 'http://localhost:8080/api/email',
                    header: {
                        'Content-Type': 'application/json',
                    },
                    data: {
                        email: this.value,
                    },
                    success: (result) => {
                        msg.style.display = 'block';
                        msg.classList.remove('error');
                        msg.classList.add('success');
                        msg.innerHTML = result.msg;

                    },
                    error: function (err) {
                        msg.style.display = 'block';
                        msg.classList.remove('success');
                        msg.classList.add('error');
                        msg.innerHTML = err.msg;

                    }
                })
            } else {
                msg.style.display = 'block';
                msg.classList.remove('success');
                msg.classList.add('error');
                msg.innerHTML = '邮箱输入有误,请重新输入!';
            }
        } else {
            msg.style.display = 'block';
            msg.classList.remove('success');
            msg.classList.add('error');
            msg.innerHTML = '请输入邮箱地址';
        }
    })
</script>
const express = require('express');
const app = express();

app.get('/email', (req, res) => {
    if (req.query.email === '1400612901@qq.com') {
        res.status(400).send({
            msg: '邮箱地址已存在,请修改'
        })
    } else {
        res.send({
            msg: '恭喜您!邮箱地址可用!'
        })   
    }
})

app.listen(8080, () => {
	console.log('http://localhost:8080');
})

搜索框内容自动提示

<!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>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        .box {
            width: 400px;
            height: 500px;
            background-color: #f9f8f9;
            margin: 100px auto;
        }

        .box #search {
            width: 100%;
            height: 40px;
            outline-color: pink;
            padding-left: 12px;
        }

        .box .list-box {
            display: none;
        }

        ul.list-box li {
            list-style: none;
            width: 100%;
            height: 50px;
            line-height: 50px;
            padding-left: 12px;
            margin-top: -1px;
            border: 1px solid #ccc;
            border-radius: 6px;
            cursor: pointer;
        }

        ul.list-box li:hover {
            background-color: rgba(255, 255, 255, .7);
        }
    </style>
    <script src="./ajax.js"></script>
    <script src="./art-template.js"></script>
</head>

<body>
    <div class="box">
        <input type="search"
            name=""
            id="search">
        <ul class="list-box"></ul>
    </div>
</body>

</html>
<script type="text/html"
    id="tpl">
    {{each result}}
        <li>{{$value}}</li>
    {{/each}}
</script>

<script>
    const search = document.querySelector('#search');
    const listBox = document.querySelector('.list-box');
    search.addEventListener('input', f(function () {
        if (this.value.trim().length === 0) {
            listBox.style.display = 'none';
            return false;
        }
        ajax({
            url: 'http://localhost:8080/api/search',
            data: {
                key: this.value,
            },
            success: function (data) {
                let html = template('tpl', { result: data.message });
                listBox.innerHTML = html;
                listBox.style.display = 'block';
            }
        })
    }, 400))

    function f (fn, delay) {
        let timer;
        return function () {
            if (timer) clearTimeout(timer);
            timer = setTimeout(() => {
                fn && fn.call(this);
            }, delay);
        }
    }

    document.onchange = function () {
        const lis = listBox.querySelectorAll('li');
        lis.forEach(item => {
            item.addEventListener('click', function () {
                search.value = this.innerText;
            })
        })
    }
</script>
app.get('/search', (req, res) => {
    const arr = ['黑马学院', '黑马程序员', '黑马程序员官网', '黑马程序员报名系统'];
    if (req.query.key === '黑马') {
        res.send({
            status: 0,
            message: arr,
        });
    } else {
        res.send({
            status: 1,
            message: ['没有找到对应内容']
        })
    }
})

省市三级联动

<!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>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        .box {
            display: flex;
            justify-content: space-between;
            width: 400px;
            height: 500px;
            background-color: #f9f8f9;
            margin: 100px auto;
        }

        .box .list-box {
            display: none;
        }

        select {
            list-style: none;
            height: 30px;
            line-height: 30px;
            padding: 0 12px;
            border: 1px solid #ccc;
            border-radius: 6px;
            cursor: pointer;
            outline: none;
        }

        ul.list-box li:hover {
            background-color: rgba(255, 255, 255, .7);
        }
    </style>
    <script src="./ajax.js"></script>
    <script src="./art-template.js"></script>
</head>

<body>
    <div class="box">
        <select name=""
            id="province">
        </select>
        <select name=""
            id="city">
            
        </select>
        <select name=""
            id="area">
            
        </select>
    </div>
</body>

</html>
<!-- 省份模板 -->
<script type="text/html"
    id="provinceTpl">
    <option value="" selected>请选择省份</option>
    {{each province}}
        <option value="{{$value.id}}">{{$value.name}}</option>
    {{/each}}
</script>
<!-- 城市模板 -->
<script type="text/html"
    id="cityTpl">
    <option value="" selected>请选择城市</option>
    {{each city}}
        <option value="{{$value.id}}">{{$value.name}}</option>
    {{/each}}
</script>
<!-- 区域/县城模板 -->
<script type="text/html"
    id="areaTpl">
    <option value="" selected>请选择县城/区域</option>
    {{each area}}
        <option value="{{$value.id}}">{{$value.name}}</option>
    {{/each}}
</script>
<script>
    const province = document.querySelector('#province');
    const city = document.querySelector('#city');
    const area = document.querySelector('#area');
    ajax({
        url: 'http://localhost:8080/api/province',
        success: function (data) {
            const html = template('provinceTpl', { province: data })
            province.innerHTML = html;
        }
    })

    province.addEventListener('change', function () {
        const pid = this.value;
        ajax({
            url: 'http://localhost:8080/api/city',
            data: {
                id: pid
            },
            success: function (data) {
                const html = template('cityTpl', { city: data })
                city.innerHTML = html;
            }
        })
    })

    city.addEventListener('change', function () {
        const cid = this.value;
        ajax({
            url: 'http://localhost:8080/api/area',
            data: {
                id: cid
            },
            success: function (data) {
                const html = template('areaTpl', { area: data })
                city.innerHTML = html;
            }
        })
    })
</script>
app.get('/province', (req, res) => {
    res.send([{
        id: '001',
        name: '湖北省'
    },{
        id: '002',
        name: '湖南省',
    }, {
        id: '003',
        name: '江苏省'
    }, {
        id: '004',
        name: '浙江省'
    }, {
        id: '005',
        name: '黑龙江省'
    }])
})

app.get('/city', (req, res) => {
    const id = req.query.id;
    if (id === '001') {
        res.send([{
            id: '101',
            name: '武汉',
        }, {
            id: '102',
            name: '恩施'
        }, {
            id: '103',
            name: '黄石'
        }, {
            id: '104',
            name: '荆州',
        }, {
            id: '105',
            name: '宜昌'
        }])
    } else if (id === '002') {
        res.send([{
            id: '201',
            name: '长沙',
        }])
    } else if (id === '003') {
        res.send([{
            id: '301',
            name: '南京',
        }, {
            id: '302',
            name: '无锡'
        }, {
            id: '303',
            name: '徐州'
        }, {
            id: '304',
            name: '苏州'
        }])
    } else if (id === '004') {
        res.send([{
            id: '401',
            name: '宁波'
        }])
    } else {
        res.send([{
            id: '501',
            name: '哈尔滨'
        }])
    }
})

app.get('/area', (req, res) => {
    const id = req.query.id;
})


模板引擎

模板引擎

模板引擎概述

作用:使用模板引擎提供的模板语法,可以将数据和HTML拼接起来

官方地址:https://aui.github.io/art-template/zh-cn/index.html

使用步骤

  1. 下载 art-template模板引擎库文件并在HTML页面中引入库文件
	<script src="./js/template-web.js"></script>
  1. 准备 art-template 模板
	<script id="tpl" type="text/html">
		<div class="box"></div>
	</script>
  1. 告诉模板引擎将哪一个模板和哪个数据进行拼接
	// 参数1) 模板id 参数2) 数据 对象类型
	// 方法的返回值就是拼接好的HTML字符串
	var html = template('tpl', {username: 'zhangsan', age: '20'})
  1. 将拼接好的html字符串添加到页面中
	document.querySelector('#container').innerHTML = html
  1. 通过模板语法告诉模板引擎,数据和HTML字符串要如何拼接
	<script id="tpl" type="text/html">
		<div class="box"> {{username}} </div>
	</script>

FormData

FormData 对象的使用

  1. 准备 HTML 表单
	<form id="form">
        <input type="text" name="username">
        <input type="password" name="password">
        <input type="button">
	</form>
  1. 将 HTML 表单转化为 FormData 对象
	let form = document.querySelector('#form')
    const formData = new FormData(form)
  1. 提交表单对象
	xhr.send(formData)

FormData 对象的实例方法

  1. 获取表单对象中属性的值
	formData.get('key')
  1. 设置表单对象中属性的值
	formData.set('key', 'value')
  1. 删除表单对象中属性的值
	formData.delete('key')
  1. 向表单对象中追加属性值
	formData.append('key', 'value')

注意:set 方法与 append 方法的区别是,在属性名已存在的情况下,set 会覆盖已有键名的值,append会保留两个值

FormData 二进制文件上传

	<input type="file" id="file"/>
	const file = document.querySelector('#file')
    // 当用户选择文件的时候
    file.onchange = function () {
        // 创建空表单对象
        const formData = new FormData()
        // 将用户选择的二进制文件追加到表单对象中
        formData.append('arrtName', this.files[0])
        // 配置 Ajax 对象,请求方式必须为post
        xhr.open('post', 'www.example.com')
        xhr.send(formData)
    }

FormData 文件上传进度展示

	// 当用户选择文件的时候
	file.onchange = function () {
        // 文件上传过程中持续触发 onprogress 事件
        xhr.upload.onprogress = (ev) => {
            // 当前上传文件大小/文件总大小 再将结果转换为百分数
            // 将结果赋值给进度条的宽度属性
            bar.style.width = (ev.loaded / ev.total) * 100 + '%'
        }
    }

FormData 文件上传图片即时预览

在我们将图片上传到服务器端以后,服务器端通常都会将图片地址作为响应数据传递到客户端,客户端可以从响应数据中获取图片地址,然后将图片再显示在页面中

二进制文件上传

<!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>Document</title>
    <style>
        .container {
            display: flex;
            flex-direction: column;
            justify-content: space-around;
            width: 800px;
            height: 200px;
            margin: 100px auto;
        }

        .progress {
            overflow: hidden;
            width: 100%;
            height: 20px;
            background-color: #f5f4f6;
        }

        .progress-bar {
            width: 0;
            height: 100%;
            line-height: 20px;
            text-align: center;
            font-size: 12px;
            color: #fff;
            background-color: #2f76ba;
        }
    </style>
</head>
<body>
    <div class="container">
        <label for="file">请选择文件</label>
        <input type="file" name="" id="file">
        <div class="progress">
            <div class="progress-bar"></div>
        </div>
    </div>
</body>
</html>
<script src="./ajax.js"></script>
<script>
    const file = document.querySelector('#file');
    const bar = document.querySelector('.progress-bar');
    file.addEventListener('change', function () {
        bar.innerHTML = '';
        bar.style.width = '0';
        let form = new FormData();
        form.append('file', this.files[0]);
        ajax({
            method: 'post',
            url: 'http://localhost:8080/api/upload',
            data: form,
            success: function (result) {
                console.log(result);
                let type = result.data.file[0].headers['content-type'];
                let path = result.path;
                if (type.includes('image')) {
                    let img = document.createElement('img');
                    img.src = path;
                    img.onload = function () {
                        document.body.appendChild(img);
                    }
                }
            },
            progress: function (e) {
                let todo = e.loaded / e.total * 100;
                bar.style.width = todo + '%';
                bar.innerHTML = todo.toFixed(2) + '%';
            }
        })
    })    
</script>
const path = require('path');
const multiparty = require('multiparty');
app.post('/upload', (req, res) => {
    const form = new multiparty.Form();
    // form.uploadDir 设置图片存储路径
	// form.keepExtensions 是否保留后缀
	// form.maxFiledsSize 设置内存大小
	// form.maxFilesSize 设置文件字节大小限制,超出时会报错
    form.uploadDir = './clock/upload/';
    form.parse(req, (err, fields, files) => {
        res.send({
            status: 0,
            msg: '请求成功!',
            path: path.join(__dirname, files.file[0].path),
        })
    })
})

同源政策

Ajax请求限制

Ajax只能向自己的服务器发送请求。比如现在有一个A网站、有一个B网站,A网站中个HTML文件只能向A网站服务器中发送Ajax请求,B网站中的HTML文件只能向b网站中发送Ajax请求,但是A网站是不能向B网站发送Ajax请求的,同理,B网站也不能

什么是同源

如果两个页面拥有相同的协议、域名和端口,那么这两个页面就属性同一个远,其中只要有一个不相同,就是不同源

http://www.example.com/dir/page.html

http://www.example.com/dir2/other.html :同源

http://example.com/dir/other.html : 不同源(域名不同)

http://v2.www.example.com/dir/other.html : 不同源(域名不同)

http://www.example.com:81/dir/other.html : 不同源(端口不同)

https://www.example.com/dir/page.html : 不同源(协议不同)

同源政策的目的

同源政策是为了保证用户信息的安全,防止恶意的网站窃取数据。最初的同源政策是指A网站在客户端设置的 Cookie,B网站是不能访问的

随着互联网的发展,同源政策也越来越严格,在不同源的情况下,其中有一项规定就是无法向非同源地址发送Ajax请求,如果请求,浏览器就会报错

使用 JSONP 解决同源限制问题

jsonp 是 json with padding 的缩写,它不属于 Ajax 请求,但它可以模拟 Ajax 请求

  1. 将不同源的服务器端请求地址写在 script 标签的 src 属性中
	<scripr src="www.example.com"></scripr>
	<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
  1. 服务器端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数
	const data = 'fn({name: "张三", age: "20"})'
    res.send(data)
  1. 在客户端全局作用域下定义函数 fn
	function fn (data) { }
  1. 在 fn 函数内部对服务器端返回的数据进行处理
	function fn (data) {
        console.log(data)
    }

JSONP 代码优化

  1. 客户端需要将函数名称传递到服务器端
  2. 将 script 请求的发送变成动态请求
  3. 封装 jsonp 函数,方便请求发送
	function jsonp (options) {
        // 动态创建script标签
        let script = document.createElement('script')
        // 拼接字符串的变量
        let params = ''
        for (var attr in options.data) {
            params += `&${attr}=${options.data[attr]}`
        }
        // myJsonp012324
        let fnName = 'myJsonp' + Math.random().toString().replace('.', '')
        // 它已经不是一个全局函数了
        // 我们要想办法将它变成全局函数
        window[fnName] = options.success;
        // 为script标签添加src属性
        script.src = options.url + '?callback=' fnName + params
        // 将script标签追加到页面中
        document.body.appendChild(script)
        // 为script标签添加 onload事件
        script.onload = () => {
            document.body.removeChild(script)
        }
        
    }

CORS 跨域资源共享

CORS:全称为 Cross-orgin resource sharing,即跨域资源共享,它允许浏览器向跨域服务器发送Ajax请求,客服了 Ajax 只能同源使用的限制

请求头(origin)
响应头(Access-Control-Allow-Origin)
浏览器端
客户端
	origin: http://localhost:3000
	Access-Control-Allow-Origin: 'htpp://localhost:3000'
	Access-Control-Allow-Origin: '*'

访问非同源数据 服务器端解决方案

同源政策是浏览器给予Ajax技术的限制,服务器端是不存在同源政策限制

请求
请求
响应
响应
A浏览器端
A服务器端
B服务器端

cookie 复习

请求cookie
响应cookie
客户端
服务器端

cookie 自定义设置/获取

			function getCookie (name) {
                const cookieArr = document.cookie.split(/;\s+/)
                for (let i = 0; i < cookieArr.length; i++) {
                    const cookie = cookieArr[i].split(/=/);
                    if (cookie[0] === name) {
                        return cookie[1]
                    }
                }
                return ''
            }
            
            function setCookie (name, value, expireDays) {
                const now = new Date();
                now.setTime(now.getTime() + 60 * 60 * 24 * 1000 * expireDays)
                document.cookie = `${ name }=${ value };expires=${ now.toUTCString() };path=/`
            }

withCredentials 属性

在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息

withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false

Access-Control-Allow-Credentials: true 允许客户端发送请求时携带cookie

$.ajax()

$.ajax()方法概述

作用:发送Ajax请求

	$.ajax({
        type: 'get',
        url: 'http://www.example.com',
        data: {
            name: 'zhangsan',
            age: '20',
        },
        contentType: 'application/x-www-form-urlencoded',
        beforeSend: function () {
            return false
        },
        success: function (response) {},
        error: function (xhr) {}
    })

serialize 方法

作用:将表单中的数据自动拼接成字符串类型的数据

	var params = $('#form').serialize()
    // name=zhangsan&age=20

发送jsonp请求

	$.ajax({
        url: 'http://www.example.com',
        // 指定当前发送jsonp请求
        dataType: 'jsonp',
        // 修改callback参数名称
        jsonp: 'cb',
        // 指定函数名称
        jsonCallback: 'fnName',
        success: function (response) {}
    })

$.get()、$.post()方法概述

作用:$.get方法用于发送get请求,$.post方法用于发送post请求。

	$.get({'http://www.example.com', { name: 'zhangsan', age: 30 }, function (response) {} })
	$.post({'http://www.example.com', { name: 'lisi', age: 20 }, function (response) {} })

jQuery中Ajax全局事件

全局事件

只要页面中有Ajax请求被发送,对应的全局事件就会被触发

	document.onajaxStart = function () {}	// 当请求开始发送时触发
	document.onajaxComplete = function {}   // 当请求完成时触发

NProgress

官宣:纳米级进度条,使用逼真的涓流动画来告诉用户正在发生的事情!

	<link rel="stylesheet" href="nprogress.css"/>
    <script scr="nprogress.js"></script>
	NProgress.start();		// 进度条开始运动
	NProgress.done();		// 进度条结束运动

RESTful 风格的 API

传统请求地址回顾

	GET http://www.example.com/qetUsers			// 获取用户列表
	GET http://www.example.com/qetUsers?id=1	// 获取某一个用户的信息
	GET http://www.example.com/modifyUser		// 修改用户信息
	GET http://www.example.com/deleteUser?id=1	// 删除用户信息

RESTful API 概述

一套关于设计请求的规范。

GET:获取数据

POST:添加数据

PUT:更新数据

DELETE:删除数据

users => /users

articles => /articles

RESTful API 的实现

GET:http://www.example.com/users 获取用户列表数据

POST:http://www.example.com/users 创建(添加)用户数据

GET:http://www.example.com/users/1 获取用户id为1的用户信息

PUT:http://www.example.com/users/1 修改用户id为1的用户信息

DELETE:http://www.example.com/users/1 删除用户id为1的用户信息

XML 基础

XML是什么

XML 的全称是 extensible markup language,代表可扩展标记语言,它的作用是传输和存储数据

	<students>
		<student>
        	<sid>001</sid>
            <name>张三</name>
        </student>
        <student>
        	<sid>002</sid>
            <name>李四</name>
        </student>
	</students>

XML DOM

XML DOM 即 XML 文档对象模型,是 w3c 组织定义的一套操作 XML 文档对象的API。浏览器会将 XML 文档解析成文档对象模型

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值