Ajax基础学习笔记-基于原生Ajax

199 阅读11分钟

传统网站中存在的问题

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

Ajax

定义

是浏览器提供的一套方法,可以实现页面无刷新下向服务器端发送请求并更新数据,提高用户浏览网站的体验。

应用场景

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

运行原理

Ajax相当于浏览器发送请求与接收响应的代理人,以实现在不影响用户浏览页面的情况下,局部更新页面数据,从而提高用户体验。而过往的请求由浏览器直接发送,用户在浏览器发送请求的时候无法进行其他操作,从而用户体验不佳。

例子

原生的Ajax请求方式

浏览器端

  // 1.创建ajax对象
    let xhr = new XMLHttpRequest();
    // 2.告诉Ajax对象要向哪发送请求,以什么方式发送请求,配置项
    // 1)请求方式 2)请求地址
    xhr.open('get', 'http://localhost:3000/first');
    // 3.发送请求
    xhr.send();
    // 4.获取服务器端响应到客户端的数据,请求的响应需要一定的时间,所以采用事件监听的方式监听响应
    xhr.onload = function () {
        console.log(xhr.responseText)
    }

服务器端

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, '/public')));
app.get('/first',(req,res) =>{
	res.send('你好');
});
// 监听端口
app.listen(3000);
// 控制台提示输出
console.log('服务器启动成功');

服务器端响应的数据格式

在真实的项目中,服务器端大多数情况下会以JSON对象作为响应数据的格式。当客户端拿到响应数据时,要将JSON数据和HTML字符串进行拼接,然后将拼接的结果展示在页面中。在http请求与响应的过程中,无论是请求参数还是响应内容,如果是对象类型,最终都会被转换为对象字符串进行传输。所以要在客户端用Json.parse()方法对响应的json字符串进行处理。

客户端


    // 1.创建ajax对象
    let xhr = new
    XMLHttpRequest();
    // 2.告诉Ajax对象要向哪发送请求,以什么方式发送请求
    // 1)请求方式 2)请求地址
    xhr.open('get', 'http://localhost:3000/responseData');
    // 3.发送请求
    xhr.send();
    // 4.获取服务器端响应到客户端的数据
    xhr.onload = function () {
		//返回的是string格式的对象
		console.log(typeof xhr.responseText);
		console.log(xhr.responseText);//'{"name":"lzy"}'
		// console.log(typeof xhr.responseText)
		// 将JSON字符串转换为JSON对象
		let responseText = JSON.parse(xhr.responseText);
		// 测试:在控制台输出处理结果
		console.log(responseText);
		// 将数据和html字符串进行拼接
	}

服务器端

//创建服务器的代码在第一个例子里面
app.get('/responseData',(req,res) =>{
    res.send({
        name:'lzy'
    });
});

get请求参数的拼接

客户端

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<p>
		<input type="text" id="username">
	</p>
	<p>
		<input type="text" id="age">
	</p>
	<p>
		<input type="button" value="提交" id="btn">
	</p>
	<script type="text/javascript">
		// 获取按钮元素
		let btn = document.getElementById('btn');
		// 获取姓名文本框
		let username = document.getElementById('username');
		// 获取年龄文本框
		let age = document.getElementById('age');
		// 为按钮添加点击事件
		btn.onclick = function () {
			// 创建ajax对象
			let xhr = new XMLHttpRequest();
			// 获取用户在文本框中输入的值
			let nameValue = username.value;
			let ageValue = age.value;
			// 拼接请求参数,手动拼接
			let params = 'username='+ nameValue +'&age=' + ageValue;
			// 配置ajax对象
			xhr.open('get', 'http://localhost:3000/get?'+params);
			// 发送请求
			xhr.send();
			// 获取服务器端响应的数据
			xhr.onload = function () {
				console.log(xhr.responseText)
			}
		}
	</script>
</body>
</html>

post请求设置

客户端

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<p>
		<input type="text" id="username">
	</p>
	<p>
		<input type="text" id="age">
	</p>
	<p>
		<input type="button" value="提交" id="btn">
	</p>
	<script type="text/javascript">
		// 获取按钮元素
		let btn =
				document.getElementById('btn');
		// 获取姓名文本框
		let username =
				document.getElementById('username');
		// 获取年龄文本框
		let age =
				document.getElementById('age');
		// 为按钮添加点击事件
		btn.onclick = function () {
			// 创建ajax对象
			let xhr = new
			XMLHttpRequest();
			// 获取用户在文本框中输入的值
			let nameValue =
					username.value;
			let ageValue =
					age.value;
			// 拼接请求参数
			let params =
					'username='+ nameValue +'&age=' + ageValue;
			// 配置ajax对象
			xhr.open('post', 'http://localhost:3000/post');
			// 设置请求参数格式的类型(post请求必须要设置),当是参数拼接的时候就设置为下面的形式
			xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
			// 发送请求
			xhr.send(params);
			// 获取服务器端响应的数据
			xhr.onload = function () {
				console.log(xhr.responseText)
			}
		}
	</script>
</body>
</html>

服务器端

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 创建web服务器
const bodyParser = require('body-parser');
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, '/public')));
//拦截全局的post请求
app.use(bodyParser.urlencoded({
    //false时使用queryString进行参数转对象,true时使用qs处理,然后继续next传递下去
    extended: false
}));
app.post('/post', (req, res) => {
    console.log(req.body);
    res.send(req.body);//响应的时候以字符串的形式进行传送,所以在客户端需要用JSON.parse进行处理
});
// 监听端口
app.listen(3000);
// 控制台提示输出
console.log('服务器启动成功');

请求参数的格式

application/json格式的处理

服务器端需要设置body-parser的解析方式为json

//拦截全局的post请求,使用\body-parse的json方法处理application/json格式的请求参数
app.use(bodyParser.urlencoded({
    //false时使用queryString进行参数转对象,true时使用qs处理,然后继续next传递下去
    extended: false
}));

客户端需要设置请求头以及使用send方法发送请求的时候需要用JSON.stringify把对象为转换json字符串才能发送

// 通过请求头告诉服务器端客户端向服务器端传递的请求参数的格式是什么
	xhr.setRequestHeader('Content-Type', 'application/json');
	// JSON.stringify() 将json对象转换为json字符串
	// 3.发送请求,请求参数必须是json字符串的格式,所以用Json.stringify转换
	xhr.send(JSON.stringify({name: 'lisi', age:50}));

注意:get请求是不能提交json对象数据格式的,传统网站的表单提交也是不支持json对象数据格式的。即不支持application/json的形式的。

Ajax状态码

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

  • 0:请求未初始化(还没有调用open()
  • 1:请求已经建立,但是还没有发送(还没有调用send0)
  • 2:请求已经发送
  • 3:请求正在处理中,通常响应中已经有部分数据可以用了
  • 4:响应已经完成,可以获取并使用服务器的响应了
    let xhr = new
    XMLHttpRequest();
    // 0 已经创建了ajax对象 但是还没有对ajax对象进行配置
    console.log(xhr.readyState);
    xhr.open('get', 'http://localhost:3000/readystate');
    // 1 已经对ajax对象进行配置 但是还没有发送请求
    console.log(xhr.readyState);
    // 当ajax状态码发生变化的时候出发,添加事件监听,以便在send方法发送请求的时候可以监听
	xhr.onreadystatechange = function () {
        // 2 请求已经发送了
        // 3 已经接收到服务器端的部分数据了
        // 4 服务器端的响应数据已经接收完成
        console.log(xhr.readyState);
        // 对ajax状态码进行判断 如果状态码的值为4就代表数据已经接收完成了
        if
        (xhr.readyState === 4) {
            console.log(xhr.responseText);
        }
    };
    xhr.send();

Ajax错误处理

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

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

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

解决方案:在请求地址的后面加请求参数,保证每一次请求中的请求参数的值不相同。加上随机数的参数,以解决该问题。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<button id="btn">发送Ajax请求
</button>
<script type="text/javascript">
    var btn =
			document.getElementById('btn');

    btn.onclick = function () {
        // 1.创建ajax对象
        var xhr = new
        XMLHttpRequest();
        // 2.告诉Ajax对象要向哪发送请求,以什么方式发送请求
        // 1)请求方式 2)请求地址
        xhr.open('get',
            'http://localhost:3000/IEcache?t=' + Math.random());
        // 3.发送请求
        xhr.send();
        // 4.获取服务器端响应到客户端的数据,IE低版本不支持onload事件
        xhr.onreadystatechange = function () {
            //响应完成并且数据接收正确
            if
            (xhr.readyState === 4 && xhr.status === 200) {
                alert(xhr.responseText);
            }
        };
    }
</script>
</body>
</html>

Ajax封装

每次发送Ajax请求时候都要写一堆代码,繁琐。所以将Ajax请求封装到函数里面,发送请求时调用函数即可。

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<script type="text/javascript">
		function ajax (options) {
			// 存储的是默认值
			let defaults = {
				type: 'get',
				url: '',
				data: {},
				header: {
					'Content-Type': 'application/x-www-form-urlencoded'
				},
				success: function () {},
				error: function () {}
			};

			// 使用options对象中的属性覆盖defaults对象中的属性
			Object.assign(defaults, options);

			// 创建ajax对象
			let xhr = new
			XMLHttpRequest();
			// 拼接请求参数的变量
			let params = '';
			// 循环用户传递进来的对象格式参数
			for (let attr in defaults.data) {
				// 将参数转换为字符串格式
				params += attr + '=' + defaults.data[attr] + '&';
			}
			// 将参数最后面的&截取掉
			// 将截取的结果重新赋值给params变量
			params = params.substr(0, params.length - 1);

			// 判断请求方式
			if (defaults.type === 'get') {
				defaults.url = defaults.url + '?' + params;
			}

			/*
				{
					name: 'zhangsan',
					age: 20
				}

				name=zhangsan&age=20

			 */

			// 配置ajax对象
			xhr.open(defaults.type, defaults.url);
			// 如果请求方式为post
			if (defaults.type === 'post') {
				// 用户希望的向服务器端传递的请求参数的类型
				let contentType = defaults.header['Content-Type']
				// 设置请求参数格式的类型
				xhr.setRequestHeader('Content-Type', contentType);
				// 判断用户希望的请求参数格式的类型
				// 如果类型为json
				if(contentType === 'application/json') {
					// 向服务器端传递json数据格式的参数
					xhr.send(JSON.stringify(defaults.data))
				}else {
					// 向服务器端传递普通类型的请求参数
					xhr.send(params);
				}

			}else {
				// 发送请求
				xhr.send();
			}
			// 监听xhr对象下面的onload事件
			// 当xhr对象接收完响应数据后触发
			xhr.onload = function () {

				// xhr.getResponseHeader()
				// 获取响应头中的数据
				let contentType = xhr.getResponseHeader('Content-Type');
				// 服务器端返回的数据
				let
				responseText = xhr.responseText;

				// 如果响应类型中包含applicaition/json
				if (contentType.includes('application/json')) {
					// 将json字符串转换为json对象
					responseText = JSON.parse(responseText)
				}

				// 当http状态码等于200的时候
				if
				(xhr.status === 200) {
					// 请求成功 调用处理成功情况的函数
					defaults.success(responseText, xhr);
				}else {
					// 请求失败 调用处理失败情况的函数
					defaults.error(responseText, xhr);
				}
			}
		}

		ajax({
			type: 'post',
			// 请求地址
			url: 'http://localhost:3000/responseData',
			data:{
				'name':'hhh'
			},
			header: {'Content-Type': 'application/json'},
			success: function (data) {
				console.log('这里是success函数');
				console.log(data)
			}
		})

		/*
			请求参数要考虑的问题

				1.请求参数位置的问题

					将请求参数传递到ajax函数内部, 在函数内部根据请求方式的不同将请求参数放置在不同的位置

					get 放在请求地址的后面

					post 放在send方法中

				2.请求参数格式的问题

					application/x-www-form-urlencoded

						参数名称=参数值&参数名称=参数值

						name=zhangsan&age=20

					application/json

						{name: 'zhangsan', age: 20}

					1.传递对象数据类型对于函数的调用者更加友好
					2.在函数内部对象数据类型转换为字符串数据类型更加方便

		*/
	</script>
</body>
</html>

Ajax案例

邮箱地址唯一性验证

邮箱地址验证的正则表达式

/^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/
  • 1.获取文本框并为其添加离开焦点事件
  • 2.离开焦点时,检测用户输入的邮箱地址是否符合规则
  • 3.如果不符合规则,阻止程序向下执行并给出提示信息
  • 4.向服务器端发送请求,检测邮箱地址是否被别人注册
  • 5.根据服务器端返回值决定客户端显示何种提示信息

FormData对象

作用

  • 模拟HTML表单,相当于将HTML表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式。
  • 异步上传二进制文件。

自动将表单转换为表单对象

客户端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form id="form">
    <input type="text"
           name="name">
    <input type="password"
           name="password">
    <input type="button"
           id="btn"
           value="提交">
</form>
<script type="text/javascript">
    let btn =
        document.getElementById('btn');
    btn.onclick = function () {
        let form = document.getElementById('form');
        let formData = new FormData(form);
        let xhr = new XMLHttpRequest();
        xhr.open('post', 'http://localhost:3000/formData')
        xhr.send(formData);
        xhr.onload = function () {
            if (xhr.status === 200) {
                console.log(xhr.responseText);
            }
        }
    }
</script>

</body>
</html>

服务器端

//app是使用express创建的服务器对象,formidable是引用的模块
app.post('/formData', (req, res) => {
	// 创建formidable表单解析对象
	const form = new formidable.IncomingForm();
	// 解析客户端传递过来的FormData对象
	form.parse(req, (err, fields, files) => {
		res.send(fields);
	});
});

实例方法

//根据键名获取值
formData.get(name);
//set() 方法会对 FormData 对象里的某个 key 设置(覆盖)一个新的值,如果该 key 不存在,则添加。
formData.set(name, value);
//FormData 接口的 delete() 方法会从 FormData 对象中删除指定键,即 key,和它对应的值,即 value。可用于在要求用户输入密码多次确认的时候删除掉其中一个值。
formData.delete(name);
//FormData 接口的append() 方法 会添加一个新值到 FormData 对象内的一个已存在的键中,如果键不存在则会添加该键。
//FormData.set 和 append() 的区别在于,如果指定的键已经存在, FormData.set 会使用新值覆盖已有的值,而append()会把新值添加到已有值集合的后面。而服务器如果用formidable的话并且不经过特殊设置,默认是获取到最后一个同名的属性值。
formData.append(name, value);

Ajax请求限制

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

什么是同源

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

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是利用script标签绕过了浏览器的跨域限制,发送的也不是Ajax请求 客户端:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<button id="btn1">点我发送请求</button>
	<button id="btn2">点我发送请求</button>
	<script type="text/javascript">
		// 获取按钮
		var btn1 = document.getElementById('btn1');
		var btn2 = document.getElementById('btn2');
		// 为按钮添加点击事件
		btn1.onclick = function () {
			jsonp({
				// 请求地址
				url: 'http://localhost:3001/better',
				data: {
					name: 'lisi',
					age: 30
				},
				success: function (data) {
					console.log(123)
					console.log(data)
				}
			})
		}

		btn2.onclick = function () {
			jsonp({
				// 请求地址
				url: 'http://localhost:3001/better',
				success: function (data) {
					console.log(456789)
					console.log(data)
				}
			})
		}

		function jsonp (options) {
			// 动态创建script标签
			var script = document.createElement('script');
			// 拼接字符串的变量
			var params = '';

			for (var attr in options.data) {
				params += '&' + attr + '=' + options.data[attr];
			}
			
			// myJsonp0124741
			var 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 = function () {
				document.body.removeChild(script);
			}
		}

		
	</script>
</body>
</html>

服务器端

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, 'public')));
app.get('/better', (req, res) => {
	// 接收客户端传递过来的函数的名称
	//const fnName = req.query.callback;
	// 将函数名称对应的函数调用代码返回给客户端
	//const data = JSON.stringify({name: "张三"});
	//const result = fnName + '('+ data +')';
	// setTimeout(() => {
	// 	res.send(result);
	// }, 1000)
	res.jsonp({name: 'lisi', age: 20});//实际上做了上面的代码的工作
});
// 监听端口
app.listen(3001);
// 控制台提示输出
console.log('服务器启动成功');

CORS跨域资源共享

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

origin:http://localhost:3000
Access-Control-Allow-Origin:'http://localhost:3000'
Access-Control-Allow-Origin:'*'
//node服务器端代码
// 在请求发出的服务器端进行拦截所有请求
app.use((req, res, next) => {
	// 1.允许哪些客户端访问我
	// * 代表允许所有的客户端访问我
	// 注意:如果跨域请求中涉及到cookie信息传递,值不可以为*号 比如是具体的域名信息
	res.header('Access-Control-Allow-Origin', 'http://localhost:3000')
	// 2.允许客户端使用哪些请求方法访问我
	res.header('Access-Control-Allow-Methods', 'get,post')
	// 允许客户端发送跨域请求时携带cookie信息
	res.header('Access-Control-Allow-Credentials', true);
	next();
});

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

同源政策是浏览器端的限制,而在服务器端是没有这个限制的。所以可以把服务器端作为中间中介 浏览器端

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<button id="btn">点我发送请求</button>
	<script src="/js/ajax.js"></script>
	<script>
		// 获取按钮
		var btn = document.getElementById('btn');
		// 为按钮添加点击事件
		btn.onclick = function () {
			ajax({
				type: 'get',
				url: 'http://localhost:3000/server',
				success: function (data) {
					console.log(data);
				}
			})
		};
	</script>
</body>
</html>

监听3000端口的服务器端

// 引入express框架
const express = require('express');
// 路径处理模块
const path = require('path');
// 向其他服务器端请求数据的模块
const axios = require('axios');
// 创建web服务器
const app = express();
// 静态资源访问服务功能
app.use(express.static(path.join(__dirname, 'public')));

app.get('/server', (req, res) => {
    axios.get('http://localhost:3001/cross').then((response) => {
        console.log(response);
    }).catch((err) => {

    }).finally(() => {

    })
});
// 监听端口
app.listen(3000);
// 控制台提示输出
console.log('服务器启动成功');

监听3001端口的另一个服务器端

app.get('/cross', (req, res) => {
	res.send('ok')
});

Jquery的$.ajax()方法

$.ajax()方法

serialize()序列化表格元素为拼接好的请求参数

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