前言

本文详细描述了Ajax的基本知识,包括Ajax的基本请求、Ajax请求的封装、请求的数据格式问题、状态码等,此外还涉及了跨域问题与Ajax拦截器的实现问题并引用具体例子加深理解学习

1.概述

  1. Ajax它是浏览器提供的一套方法,可以实现页面无刷新更新数据,提高用户浏览网站应用的体验。
  2. 应用场景:
  • 页面上拉加载更多数据
  • 无刷新分页
  • 表单项离开焦点数据验证
  • 搜索框提示文字下拉列表
  1. 缺点:Ajax本身不支持跨域请求

2. ajax请求的基本过程

var xhr = new XMLHttpRequest();
xhr.open('method','url',async(可省略));
xhr.send();
xhr.onload = function (){}

2.1 创建ajax对象

	var xhr = new XMLHttpRequest()

2.2 设置请求

xhr.open(method,url,async):ajax请求方式:get或post,以及请求地址url,是否异步async(默认为true)

使用post请求

注意:若使用post方法则必须设置请求的参数格式类型,.setRequestHeader(header, value)
header:属性的名称。
value:属性的值。

httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

发送表单类型的资料:

('Content-Type','application/x-www-form-urlencoded')
数据格式: name=小明&age=20
xhr.send('username=admin&age=18')

发送JSON 类型的资料:( 要将json 对象转换为 ,因为请求参数必须要以字符串格式传递)

('Content-Type','application/json')
数据格式:{name: '小明', age: '20'}
xhr.send(JSON.stringify({name: 'admin', age:18}));

2.3 发送请求

	xhr.send()

2.4 获取服务器端响应到客户端的数据

	xhr.onload = function (){
		console.log(xhr.responseText)
// xhr.responseText 接收文本格式的响应数据
// xhr.responseXML 接收 xml 格式的响应数据
	}

3. 数据格式

3.1 JSON.parse()

  1. 在 http 请求与响应的过程中,无论是请求参数还是响应内容,如果是对象类型,最终都会被转换为进行传输,故获取时要用JSON.parse() 重新转为json对象
	JSON.parse() // 将 json 字符串转换为json对象

3.2 JSON.stringify()

  1. 注意:get 请求是不能提交 json 对象数据格式的,要转为json字符串
    JSON.stringify() // 将json对象转换为json字符串

    数字Infinity和NaN以及值 null使用JSON.stringify()时都会转为null

    JSON.stringify([NaN, null, Infinity]); // '[null,null,null]'

  2. JSON.stringify() 与 localStorage存取

	const session = {
	  'screens': [
	      { 'name': 'screenA', 'width': 450, 'height': 250 },
	      { 'name': 'screenB', 'width': 650, 'height': 350 },
	      { 'name': 'screenC', 'width': 750, 'height': 120 }
	  ],
	  'state': true
	};
localStorage.setItem('session',JSON.stringify(session) );
const restoredSession = JSON.parse(localStorage.getItem('session'));
console.log(restoredSession);
Ajax总结-LMLPHP

若没用JSON.stringify()存数据 与JSON.parse()取数据:

localStorage.setItem('session', session);
const restoredSession = localStorage.getItem('session');
console.log(restoredSession);

Ajax总结-LMLPHP
若取数据时没用JSON.parse()取数据

localStorage.setItem('session', JSON.stringify( session));
const restoredSession = localStorage.getItem('session');
console.log(restoredSession);

Ajax总结-LMLPHP
若存数据时没用JSON.stringify()会报错
Ajax总结-LMLPHP


4. Ajax状态码

  • 0:请求未初始化(还没有调用open())
  • 1:正在加载:已建立服务器连接但还没有发送
    (已调用open,还没有调用send())
  • 2:请求已经发送,已加载
  • 3:请求正在处理中,通常响应中已经有部分数据可以用了
  • 4:响应已经完成,可以获取并使用服务器的响应了

xhr.readyState // 获取Ajax状态码
xhr.onprogress是状态码为3的时候,此时可以写加载动画等效果,如页面跳转时的小圆点加载,xhr.onprogress为非必写函数

    xhr.onprogress = function(){
       console.log('READYSTATE: ', xhr.readyState);
   }

常见状态码

  • 200 表示从客户端发来的请求在服务器端被正常处理
  • 204 表示请求处理成功,但没有资源返回。
  • 301 表示永久性重定向。该状态码表示请求的资源已被分配了新的URI,以后应使用资源现在所指的URI。
  • 302 表示临时性重定向。
  • 401 表示未授权(Unauthorized),当前请求需要用户验证
  • 403 表示服务器拒绝请求
  • 404 表示服务器找不到请求的网页
  • 503 表示服务器暂时处于超负载或维护,无法处理请求。

5. onreadystatechange 事件与onload事件的区别

onreadystatechange :
当 Ajax 状态码发生变化时将自动触发onreadystatechange事件,当状态码为 4 时就可以通过xhr.responseText获取服务器端的响应数据了。

 // 当Ajax状态码发生变化时
 xhr.onreadystatechange = function () {
     // 判断当Ajax状态码为4时
     if (this.readyState == 4 && this.status == 200) {
         // 获取服务器端的响应数据
         console.log(xhr.responseText);
     }
 }

使用onreadystatechange可以打印状态码 2、3、4

onload:
只有处于状态码4,即响应已就绪的情况下才会进入onload,使用onload只打印状态码4

6. Ajax错误处理

	xhr.onerror = function () {
				alert('Ajax请求出错')
			}
	//当请求遇到错误时触发该事件
  1. 网络畅通,服务器端能接收到请求,服务器端返回的结果不是预期结果。用xhr.status 获取http状态码进行排查
  2. 网络畅通,服务器端没有接收到请求,返回404状态码:检查请求地址是否错误。
  3. 网络畅通,服务器端能接收到请求,服务器端返回500状态码:服务器端错误,找后端程序员进行沟通。

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


7. ajax实例运用

:若用本地文件调试ajax可以用open with live server打开,否则会报错,因为浏览器认为此种方式不安全,将此视为跨站点攻击,并会阻止请求
推荐一个可以免费请求数据的ajax接口网站用于测试:FillText.com

7.1 ajax获取纯文本

  <button id="button">获取纯文本</button>
  <br><br>
  <div id="text"></div>

  <script>
    document.getElementById('button').addEventListener('click', loadText);
    function loadText(){
      var xhr = new XMLHttpRequest();
      xhr.open('GET', 'sample.txt');
      xhr.onload = function(){
        console.log('READYSTATE: ', xhr.readyState);
        if(this.status == 200){
          document.getElementById('text').innerHTML = this.responseText;
        } else if(this.status = 404){
          document.getElementById('text').innerHTML = 'Not Found';
        }
      }

      xhr.onerror = function(){
        console.log('请求出错');
      }
        xhr.send();
    }
  </script>

Ajax总结-LMLPHPAjax总结-LMLPHP

若改为不存在的文本文件会报错
xhr.open('GET', 'sample2.txt');Ajax总结-LMLPHP

7.2 ajax获取本地JSON文件

<body>
   <button id="button">Get Users</button>
    <h1>Users</h1>
    <div id="users"></div>
    <script>
        document.getElementById('button').addEventListener('click', loadUsers);
        function loadUsers() {
            var xhr = new XMLHttpRequest();
            xhr.open('GET', 'users.json');
            xhr.onload = function () {
                    var users = JSON.parse(this.responseText);
                    var output = '';
                    for (var i in users) {
                         output += `
                        <ul>
                        <li>ID:${users[i].id}</li>
                        <li>Name: ${users[i].name}</li>
                         </ul>
          `;
                    }
                    document.getElementById('users').innerHTML = output;
            };

            xhr.send();
        }
    </script>
    </body>

JSON文件:

[
  {
    "id":1,
    "name":"Rick"
  },
  {
    "id":2,
    "name":"Wang"
  },
  {
    "id":3,
    "name":"Fang"
  }
]
Ajax总结-LMLPHP

7.3 ajax获取外部链接api

  <script>
    document.getElementById('button').addEventListener('click', loadUsers);
    function loadUsers(){
      var xhr = new XMLHttpRequest();
      xhr.open('GET', 'http://www.filltext.com/?rows=6&fname={firstName}&lname={lastName}');
      xhr.onload = function(){
          var users = JSON.parse(this.responseText);
          console.log(users);
          var output = '';
          for(var i in users){
            output +=
              '<div class="user">' +
              '<ul>' +
              '<li>fname: '+users[i].fname+'</li>' +
              '<li>lname: '+users[i].lname+'</li>' +
              '</ul>' +
              '</div>';
          }

          document.getElementById('users').innerHTML = output;
      }

      xhr.send();
    }
  </script>

7.4 jquery中使用ajax

	$('#btn').on('click', function () {
		 $.ajax({
      type: 'post',
      url: '',
      	// 向服务器端发送的请求参数
		data: JSON.stringify(params),
      	success: function (xhr) {
        console.log(xhr);
      }
	})
});


8. ajax封装

参考黑马程序员教程,我们抽离一个 ajax.js 文件,这样使用ajax只需引入并传递相应的参数,大大节省代码量

Object.assign(target, ...sources):
target:目标对象,接收源对象属性的对象,也是修改后的返回值。
sources:源对象,包含将被合并的属性。
如果目标对象与源对象具有相同的 key,则目标对象中的属性将被源对象中的属性覆盖,后面的源对象的属性将类似地覆盖前面的源对象的属性。

封装:

<script>
		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);
			var xhr = new XMLHttpRequest();
			// 拼接请求参数的变量
			var params = '';
			// 循环用户传递的参数,即对对象的循环遍历与取值
			for (var value in defaults.data) {
				// 将参数转换为字符串格式
				params += value + '=' + defaults.data[value ] + '&';
				//value为对象属性名,defaults.data[value ]为对应的属性值
			}
			// 将参数最后面的&截取掉
			params = params.substr(0, params.length - 1);
			// 判断请求方式
			if (defaults.type == 'get') {
				defaults.url = defaults.url + '?' + params;
			}
			// 配置ajax对象
			xhr.open(defaults.type, defaults.url);
			// 如果请求方式为post
			if (defaults.type == 'post') {
				// 设置请求参数格式的类型
				var contentType = defaults.header['Content-Type']
				//xhr.setRequestHeader在xhr.open之后,xhr.send之前
				xhr.setRequestHeader('Content-Type', contentType);
				// 如果类型为json
				if (contentType == 'application/json') {
					xhr.send(JSON.stringify(defaults.data))
				}else {
					// 向服务器端传递普通类型的请求参数
					xhr.send(params);
				}
			}
			else {
				// 发送get请求
				xhr.send();
			}

			// 当xhr对象接收完响应数据后触发
			xhr.onload = function () {
				// 获取响应头中的数据类型
				var contentType = xhr.getResponseHeader('Content-Type');
				// 服务器端返回的数据
				var responseText = xhr.responseText;
				// 如果响应类型为json
				if (contentType.includes('application/json')) {
					// 将json字符串转换为json对象
					responseText = JSON.parse(responseText)
				}
				if (xhr.status == 200) {
					// 请求成功 调用处理成功情况的函数
					defaults.success(responseText, xhr);
				}else {
					// 请求失败 调用处理失败情况的函数
					defaults.error(responseText, xhr);
				}
			}
				// 当网络中断时
			xhr.onerror = function () {
			// 调用失败回调函数并且将xhr对象传递给回调函数
			defaults.error(xhr);
			}
		}


	</script>

使用:

<body>
    <script  src="/js/ajax.js"></script>
    <script type="text/javascript">
        ajax({
            url: '',
            type: '',
            success: function(data) {
              console.log(data)
            }
        })
    </script>
	</body>

9. 跨域问题解决

1、 通过jsonp跨域
2、 跨域资源共享(CORS)
3、 location.hash + iframe
4、 window.name + iframe跨域
5、 postMessage跨域
6、 document.domain + iframe跨域
7、 nginx代理跨域
8、 nodejs中间件代理跨域
9、 WebSocket协议跨域

详见博客:前端跨域解决方案


10. 使用ajax实现拦截器

拦截器的设置可以让我们在发出 request 或接到 response 之前做一些事情,如改变 response的数据格式或是根据不同 request 来添加不同的config 等。

具体实现方法:Ajax拦截器的实现

11. 参考资料

MDN JSON.stringify()

MDN AJAX

07-23 18:26