Ajax 编程基础(一)

Ajax技术允许在不刷新整个页面的情况下更新部分内容,提高用户体验。本文详细介绍了Ajax的运行原理,包括异步请求、响应处理、参数传递、错误处理,并展示了Ajax的封装函数。此外,还讨论了在低版本IE浏览器中如何处理缓存问题。Ajax应用场景包括动态加载、表单验证等,主要应用于前端开发。
摘要由CSDN通过智能技术生成

一. Ajax 概述

1、为什么要使用 Ajax 技术? 在不使用 Ajax 的传统网站中如果需要更新内容则必须重载整个网页,这会存在哪些问题?

  • 网速慢的情况下,一次性请求大量数据时。页面加载时间长,用户只能等待,而无法进行其他操作。
  • 表单提交后,如果某项数据经服务器端验证不通过(例如注册时账号已存在),需要重新加载页面并填写所有表单内容。
  • 页面跳转,需要重新加载页面,造成资源浪费,增加用户等待时间。

2、什么是 Ajax ?Ajax 主要有哪些应用场景?

AjaxAsynchronous JavaScript and XML(异步的 JavaScript 和 XML) 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。它是浏览器提供的一套方法,使用它们可以在不重新加载整个页面的情况下实现页面数据更新,提高用户浏览网站应用的体验。主要应用场景:

  • 页面动态(例如上拉)加载更多的数据
  • 列表数据无刷新分页
  • 表单项失去焦点进行数据验证(涉及后端数据校验)
  • 搜索框提示文字下拉例表

Ajax 虽然是在客户端运行的 js 程序,但是涉及前后端交互,所以在学习开发过程中必须通过访问服务器才能测试运行效果(需要运行在网站环境中才能生效)。

二. Ajax 运行原理及实现

1、Ajax 的运行原理是什么?

传统网站应用中发送请求并接收响应都是由浏览器本身实现。浏览器在发送请求接受响应时不能再响应用户的其他操作(例如继续滚动并浏览页面),影响用户体验。现 Ajax 对象可以代替浏览器去实现这些操作,这样在进行数据请求和接收响应时浏览器依旧可以响应用户操作。Ajax 接收到响应数据后使用 DOM 操作就可以实现页面数据更新。这就是利用 Ajax 实现发送请求和接收响应数据的同时可以响应用户操作的原理。

在这里插入图片描述

使用 Ajax 的另一个优点:开发者对于服务器请求的发送和响应的接收是不可控的,发送请求时浏览器处于无法响应状态,接收数据后进行页面刷新。因此整过程是不可控的。而使用 Ajax 向服务端发送请求和接收响应开发人员是可控的。可以在期间对用户进行加载提示,进一步提高用户体验。

2、怎么实现 Ajax ?

实现 Ajax 首先要创建一个 Ajax 对象,然后利用对象的 open() 方法配置请求方式和地址,之后利用 send() 方法发送请求。请求发送和服务器的响应过程需要时间(异步任务),因此需要使用 Ajax 的 onload 事件(或者 onreadystatechange 事件)接收并处理数据。代码示例如下:

// 客户端(静态页面js)代码
    // 创建 Ajax 对象
    const xhr = new XMLHttpRequest();
    // 配置请求方式、地址和是否进行异步操作(可选默认 true)
    xhr.open('get', 'http://localhost:3000/first', true);
    // 发起请求
    xhr.send();
    // 获取服务器响应的数据
    xhr.onload = function() {
        console.log(xhr.responseText);
    };
 
// 服务器获取静态页面、并实现响应请求的路由(服务端代码)
// 引入 express 框架
const express = require('express');
// 引入路径处理模块
const path = require('path');
// 创建服务器
const app = express();
// 实现静态页面访问功能
app.use(express.static(path.join(__dirname, 'public')));
app.get('/first', (req, res) => {
    res.send('这就是响应');
});
// 监听端口
app.listen(3000);
// 控制台提示输出
console.log('服务器已开启可访问:http://localhost:3000/');

3、请求

  1. XMLHttpRequest 对象如果要使用 AJAX 的话,其 open() 方法的 async 参数必须设置为 true(可选参数,默认为 true ,进行异步操作)。因为很多在服务器执行的任务都相当耗时,不执行异步操作可能会引起应用程序挂起或者停止。
  2. 当然对于一些小型请求也可以(不推荐)使用同步请求方式。因为是同步请求,所以此时可以直接将数据处理代码写在请求发送的后面。
    // 此时是同步请求
    xhr.open('get', 'http://localhost:3000/first', false);
    // 发起请求
    xhr.send();
    // 直接获取响应数据
    console.log(xhr.responseText);

上示同步请代码执行后,浏览器控制台会提示(警告⚠):因为影响用户体验,在主线程执行同步 XMLHttpReguest 的方式已被弃用。所以虽然不报错,但是不建议使用。

4、响应

可以使用 Ajax 对象的 responseText 或者 responseXML 属性来获取服务器的响应数据。若服务器响应的是 XML 形式的数据则使用 responseXML 获取,如果响应的是字符串形式的数据则使用 responseText 属性获取。

5、服务器端响应的数据格式

服务器端大多数情况下默认以 JSON 对象作为响应数据的格式。当在客户端拿到响应数据时,需要将 JSON 数据和 HTML 字符串进行拼接(元素操作、节点操作)并展示在页面中。

但是在 http 请求和响应过程中,无论是请求参数还是响应内容,如果是 JSON 对象类型,最终都会被转化为对象字符串进行传输。必须使用 JSON.parse() 方法将 json 字符串转换为 json 对象再进行操作

// 服务器端响应 ajax 请求
app.get('/responseData', (req, res) => {
    res.send({ name: 'TKOP', age: 18 });
});

// 浏览器处理
    xhr.onload = function() {
        const responseData = JSON.parse(xhr.responseText);
        // 数据简单处理
        let addContent = `<p>名字:${responseData.name}</br>年龄:${responseData.age}</p>`;
        document.body.innerHTML = addContent;
    }

6、Ajax 以 get 请求方式传递参数:

Ajax 以 get 请求方式传递参数和传统网站浏览器以 get 请求方式传递参数一样,都是将参数放入请求地址后面进行传递。例如表单数据以 get 请求方式传递。

html 结构代码和 css 样式代码(其实没有)省略,以下是客户端 js 代码和服务器响应代码。

// 发送ajax请求将参数以get方式传递
    // 获取页面元素
    var uname = document.getElementById('name');
    var psw = document.getElementById('psw');
    var btn = document.getElementById('btn');
    // 创建 Ajax 对象
    const xhr = new XMLHttpRequest();

    btn.onclick = function() {
        // 获取参数并拼接(必须在点击后进行否则没有值,js执行即获取为空值或者表单默认值)
        var params = `?name=${uname.value}&psw=${psw.value}`;
        // 将get请求参数放入请求地址中并发送请求
        xhr.open('get', 'http://localhost:3000/getParams' + params);
        xhr.send();
        // 获取服务器响应的数据
        xhr.onload = function() {
            const responseData = JSON.parse(xhr.responseText);
            console.log(responseData);

        }
// 接收Ajax请求将get参数直接返回
app.get('/getParams', (req, res) => {
    res.send(req.query)
});

7、Ajax 以 post 请求方式传递参数:

Ajax 以 post 请求方式传递参数时不将参数放入请求地址中,而是将其放入请求体中进行传输。将数据放入请求体内进行传递前,必须使用 setRequestHeader() 方法声明请求的数据在请求头部的保存格式

    // 获取页面元素
    var uname = document.getElementById('name');
    var psw = document.getElementById('psw');
    var btn = document.getElementById('btn');
    // 创建 Ajax 对象
    const xhr = new XMLHttpRequest();

    btn.onclick = function() {
        // 获取参数并拼接(必须在点击后进行否则没有值,js执行即获取为空值或者表单默认值)
        var params = `name=${uname.value}&psw=${psw.value}`;

        // 发送的是post请求,但是这里同时添加了get参数
        xhr.open('post', 'http://localhost:3000/postParams?age=18');

        // 声明请求参数的格式('name=xxx&psw=xxx')
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        // 发送请求并传递参数
        xhr.send(params);
        // 获取服务器响应的数据
        xhr.onload = function() {
            const responseData = JSON.parse(xhr.responseText);
            console.log(responseData);
        }
    }

// 服务器端代码
app.post('/postParams', (req, res) => {
    const params = { uname, psw, } = req.body;
    // 这里同时处理了两种方式传递的参数
    params.age = req.query.age;
    res.send(params);
});

8、post 请求方式将保存在请求体中的数据指定为 json 形式

请求体中保存的参数数据除了可以使用上述格式之外,json 数据格式也是常用的参数数据格式。发送请求前需要将参数保存格式声明改为 'application/json',同时发送请求时,send() 方法内的参数必须是 json 格式

当然服务器端使用 body-parser 模块处理数据时也要更改处理数据格式配置。 即更改中间件内的处理方法, app.use(bodyParser.urlencoded()); 改为 app.use(bodyParser.json());如果没有配置正确的解析格式得到的是空的数据(得不到数据)

	// 客户端代码
    btn.onclick = function() {
        xhr.open('post', 'http://localhost:3000/json');

        // 声明参数保存格式为json
        xhr.setRequestHeader('Content-Type', 'application/json');
        // 参数对象
        var params = {
            name: uname.value,
            psw: psw.value
        };
        // 将对象类型改为json对象字符串形式并发送请求
        xhr.send(JSON.stringify(params));
        // 获取服务器响应的数据
        xhr.onload = function() {
            const responseData = JSON.parse(xhr.responseText);
            console.log(responseData);
        }
    }
    // 服务器数据处理
    app.use(bodyParser.json());
	app.post('/json', (req, res) => {
    	res.send(req.body);
	});

可以在浏览器的开发者工具中看到两种格式的区别:第一、第二张是 json 格式的数据(不同的显示形式),第三张、第四张则是默认情况下使用的 application/x-www-form-urlencoded 格式的数据。

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
当然如果我们只查看 ajax 请求的话也可以看到 json 数据格式的请求文件,如下图所示:

在这里插入图片描述

9、请求状态监听事件 onreadystatechange

相对于 onload 事件只能在服务器端响应数据后才能触发,onreadystatechange 事件在请求的状态发生改变时就会触发。其中请求有五种状态,每种状态分别用不同的状态码表示。可以使用 ajax 对象的 readystate 属性来获取请求状态码。不同状态码对应的请求状态如下:

  • 0:请求未初始化(还没有调用 open() 方法)
  • 1:请求已建立,但是还没有发送(还没调用 send() 方法)
  • 2:请求已发送
  • 3:请求正在处理中,通常响应中已经有部分数据可用了
  • 4:响应已完成,可以获取并使用服务器响应的数据
        var xhr = new XMLHttpRequest();
        // 0 已经创建了ajax对象 但是还没有对ajax对象进行配置
        console.log(xhr.readyState);
        xhr.open('get', 'http://localhost:3000/readystate');
        // 1 已经对ajax对象进行配置 但是还没有发送请求
        console.log(xhr.readyState);

        // 当ajax状态码发生变化的时候触发

        xhr.onreadystatechange = function() {
            // 2 请求已经发送了
            // 3 已经接收到服务器端的部分数据了
            // 4 服务器端的响应数据已经接收完成
            console.log(xhr.readyState);
            // 对ajax状态码进行判断 如果状态码的值为4就代表数据已经接收完成了
            if (xhr.readyState == 4) {
                console.log(xhr.responseText);
            }
        }
        // 发送请求必须在readystatechage事件后面,不然无法检测发送前与发送后的变化
        xhr.send();

在此必须弄请 get 请求方式和 post 请求方式的区别,参数传递的两种方式。

onload 和 onreadystatechange 两个事件获取服务器端响应结果比较:

区别描述onload 事件onreadystatechange 事件
是否兼容低版本 IE不兼容兼容
是否需要判断 Ajax 状态码不需要需要
触发次数1 次多次

10、Ajax 错误处理

错误 1:网络畅通,服务器能接收到请求,但是响应结果不是预期结果。

这种请求需要判断服务器端返回的状态码,分别进行处理,xhr.status 可以获取 http 状态码。请求状态码分为 5 类,其中都是由三位十进制数字组成(第一位数字表示状态码类别),具体各个状态码表示什么在此不做详细介绍。如下示例返回 400 状态码,使用 xhr.status 获取状态码并输出。

// 服务器端手动返回400状态码
app.get('/error', (req, res) => {
    res.status(400).send('not ok');
});
// 客户端发送ajax请求并打印状态码
<script>
    var btn = document.querySelector('#btn');
    btn.onclick = function() {
        var xhr = new XMLHttpRequest();
        xhr.open('get', 'http://localhost:3000/error', true);
        xhr.send();
        xhr.onload = function() {
            // xhr.status 获取http状态码
            console.log(xhr.status);
        }
    }
</script>

在这里插入图片描述

错误 2:网络畅通,但是服务器没有接收到请求(例如请求地址出错),返回 404 状态码。

检查请求地址是否正确。如下示例中将 ajax 请求地址改为错误地址(服务器端没有映射处理路由)时返回 404 状态码。

在这里插入图片描述

错误 3:网络通畅,服务器能接收到请求,服务器返回 500 状态码。

这是服务器端程序错误,需要同后端开发者沟通解决。如下示例服务器端处理请求时程序运行出错(意图输出一个不存在的变量 num)时给客户端返回 500 状态码。

// 服务器端请求处理
app.get('/error', (req, res) => {
    console.log(num);
    res.send('not ok');
});

在这里插入图片描述

错误 4:网络中断,请求无法发送到服务器。

这种情况不会促发 onload 事件,但是会触发 xhr 对象下的 onerror 事件,可以在 onerror 事件处理函数中对错误进行处理。如下示例中在发送 ajax 请求时将网络设置为离线状态会触发 onerror 事件。

<script>
    var btn = document.querySelector('#btn');
    btn.onclick = function() {
        var xhr = new XMLHttpRequest();
        xhr.open('get', 'http://localhost:3000/error', true);
        xhr.send();
        xhr.onload = function() {
            alert('并不会触发onload事件')
        };
        xhr.onerror = function() {
            alert('请检查网络连接是否正常');
        }
    }
</script>

在这里插入图片描述

注意 Ajax 状态码和 Http 状态码的区别

  1. ajax 状态码表示 Ajax 请求的过程状态,它是由 ajax 对象返回的。
  2. Http 状态码表示请求处理的结果,它是由服务器端返回的。

11、低版本 IE 浏览器的缓存(cache)问题

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

解决方案在请求地址后面添加参数,并保证每次请求中该参数的值不同(例如可以是随机数或者当前时间戳)

xhr.open('get', 'http://www.xxx.com?n=' + Math.random());

3. Ajax 函数的封装

问题 :每发送一次 ajax 请求都需要大量的代码,发送多次请求时代码冗余重复。
解决方案 :将发送请求的代码进行函数封装,需要发送 ajax 请求时调用函数即可。

封装思路:

1. 封装时将与请求有关的不确定的数据作为函数参数传入函数内部,例如请求地址、请求方式和响应结果处理函数等。
2. 将函数参数作为对象属性进行传递能提高代码可读性,这样传参也非常方便。
3. 考虑到请求参数在请求中的存储位置不同(保存在请求地址中或者请求体中),所以也需要将其作为函数参数传入后在函数内进行处理。
4. 还有就是请求参数的格式问题,调用函数时传递对象类型的数据对于调用者会更加友好,并且在函数内部对象数据类型转换为字符串数据类型也更加方便,所以选择调用时传递对象类型数据。
5. 请求参数的格式当然也需要是根据调用者传递的函数参数来决定,所以在定义函数时也需要处理有关问题。
6. 当 onload 事件触发时只能说明这次 ajax 请求完成了,并不能代表这次请求是成功的。如果返回的不是 200 状态码呢?所以我们还需要判断 http 状态码,并根据状态码调用不同的处理函数,将处理成功和失败的情况进行分离。
7. 在处理响应结果的回调函数中我们常将返回数据解析为对象后使用,因此解析的过程可以封装在 ajax 函数中。这样调用者就没有必要在响应结果处理函数中再做该操作了。
8. 但是在不知道服务器端返回的数据是否是 json 类型的数据(或者只是一串普通字符,不是 json 字符串)的情况下,需要根据响应头信息给定的数据类型判断是否能将响应数据转换为对象。如果是 ‘application/json’ 类型,则在客户端将其转换为对象类型使用,如果是 ‘text/html’ 类型,则不再进行转换。
9. 最后就是封装的函数需要的参数过多,而有些参数可以设置特定的默认值。这样可以减少调用者传递的参数个数。请求方式、请求参数类型可以设置默认值,只有在调用者确实需要其他值时在传入。

// 封装ajax函数
function ajax(options) {
    // 设置默认参数
    var defaults = {
        type: 'get',
        url: '',
        data: {},
        header: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        success: function() {},
        error: function() {}
    };
    // 使用options对象中的属性覆盖defaults对象中的属性
    Object.assign(defaults, options);
    // 创建ajax对象
    var xhr = new XMLHttpRequest();
    // 处理请求参数,先声明用于拼接请求参数的变量
    var params = '';
    // 将参数拼接成application/x-www-form-urlencoded格式
    for (let attr in defaults.data) {
        params += attr + '=' + defaults.data[attr] + '&';
    };
    params = params.substr(0, params.length - 1);
    // 配置ajax对象
    defaults.url = defaults.type == 'get' && params ? defaults.url + '?' + params : defaults.url;
    xhr.open(defaults.type, defaults.url);
    console.log(params);
    // 发送请求
    if (defaults.type == 'post') {
        // 只有post请求才有多种参数格式
        let contentType = defaults.header['Content-Type'];
        xhr.setRequestHeader('Content-Type', contentType);
        // 根据调用者希望的请求参数格式的类型传递不同格式的参数
        if (contentType == 'application/json') {
            xhr.send(JSON.stringify(defaults.data));
        } else {
            xhr.send(params);
        }
    } else {
        xhr.send();
    }

    // 处理服务器端响应结果
    xhr.onload = function() {
        // 获取响应头中的数据(响应数据格式)
        var contentType = xhr.getResponseHeader('Content-Type');
        var data = xhr.responseText;
        // 如果是json字符串则对其进行解析
        if (contentType.includes('application/json')) {
            data = JSON.parse(data);
        }
        if (xhr.status == 200) {
            // 请求成功,调用处理成功情况的函数处理响应结果
            defaults.success(data, xhr);
        } else {
            // 请求失败,调用处理错误情况的函数
            defaults.error(data, xhr);
        }

    }

}

// 调用ajax函数
ajax({
    // 请求地址
    url: 'http://localhost:3000/first',
    // 参数类型

    // 处理服务器端响应结果的函数
    success: function(data, xhr) {
        console.log(data);
        console.log(xhr);
    },
    // 处理错误的函数
    error: function(data, xhr) {
        console.log(data);
        console.log(xhr);
    },
    // 请求参数
    data: {
        name: 'tkop',
        age: 18
    }
})
ajax({
    // 请求方式
    type: 'post',
    // 请求地址
    url: 'http://localhost:3000/first',
    // 处理服务器端响应结果的函数
    success: function(data, xhr) {
        console.log(data);
        console.log(xhr);
    },
    // 处理错误的函数
    error: function(data, xhr) {
        console.log(data);
        console.log(xhr);
    },
    // 请求参数
    data: {
        name: '扬尘',
        age: 20
    }
})
ajax({
    // 请求方式
    type: 'post',
    // 请求参数格式(这个需要查看请求体才能知道其类型)
    header: {
        'Content-Type': 'application/json'
    },
    // 请求地址
    url: 'http://localhost:3000/first',
    // 处理服务器端响应结果的函数
    success: function(data, xhr) {
        console.log(data);
        console.log(xhr);
    },
    // 处理错误的函数
    error: function(data, xhr) {
        console.log(data);
        console.log(xhr);
    },
    // 请求参数
    data: {
        name: '扬尘',
        age: 22
    }
})
// 服务器端
app.get('/first', (req, res) => {
    res.send('响应get 请求');
});
app.post('/first', (req, res) => {
    res.send({ content: '响应post请求' });
});

三个请求的响应结果如下图:第一个由于响应的是一个字符串类型数据,所以没有将其转换为对象,后面两个响应的是 json 对象,所以在 ajax 函数内部已经将其解析为了对象。

在这里插入图片描述
在看看请求结构:如图 get 请求得请求参数保存在请求地址中,而 post 请求放在请求体中(在请求地址中看不到)。

在这里插入图片描述

然后再看看后面两个 post 请求,因为前者请求参数使用默认类型,而后者使用 ‘application/json’ 类型,所以它们在请求体中得保存格式也不一样。

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

因此可以判断封装的 ajax 函数符合自己的预想要求,没有出现问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值