从ajax异步请求封装和JSONP跨域封装到第三方库的使用

开门见山

本文章采用倒序和插叙等进行叙述的手法,边吃西瓜边浏览效果更佳…

使用XMLHttpRequest (XHR)对象可以与服务器交互。您可以从URL获取数据,而无需让整个的页面刷新。这使得Web页面可以只更新页面的局部,而不影响用户的操作。XMLHttpRequest在 Ajax 编程中被大量使用。

GET请求的理解

Asynchronous Javascript And XML(异步JavaScript和XML),他并不是凭空出现的新技术,而是对于现有技术的结合:核心是js对象XMLHttpRequest

  1. 使用 AJAX 的过程可以类比平常我们访问网页过程
	// 1. 创建异步对象 --相当于打开一个浏览器
	var xhr = new XMLHttpRequest()
	// 2. 打开与网址特定的链接 --相当于在地址栏输入访问地址
	xhr.open('get','./01get.php')
	// 3. 通过链接发送一次请求 --相当于在浏览器输入回车发送请求
	xhr.send(null)
	//  4. 指定 xhr 状态变化事件处理函数 —— 相当于处理网页呈现后的操作
	xhr.onreadystatechange = function(){
		// 通过 xhr 的 readyState 判断此次请求的响应是否接收完成
		if (this.readyState === 4) {
			// 通过 xhr 的 responseText 获取到响应的响应体
			console.log(this)
		}
	}

如果GET需要传参数,需要接在open的url地址里面

POST请求
	var xhr = new XMLHttpRequest()
	// open 方法的第一个参数的作用就是设置请求的 method,第二个方法是路径,建立与浏览器特定端口的链接
	xhr.open('POST','./02post.php')
	// 设置请求头中的 Content‐Type 为 application/x‐www‐form‐urlencoded
	// 标识此次请求的请求体格式为 urlencoded 以便于服务端接收数据
	xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
	// 需要提交到服务端的数据可以通过 send 方法的参数传递
	// 格式:key1=value1&key2=value2
	xhr.send('key1=value1&key2=value2')//post请求参数在这里传递,并且不需要encodeURI()转码
	xhr.onreadystatechange = function(){
		// 状态为4才能获取完整的响应内容
		if (this.readyState === 4) {
			console.log(this.responseText) //获取服务端返回的内容
		}
	}

open的第三个参数是布尔类型,代表是否异步请求

简单粗暴–直接封装

我们废话不多说了,直接讲讲关于get和post的封装,其实就是找它们的不同…

  1. 我们首先要是xhr兼容;要使其兼容ie6和标准的浏览器
    var xhr = null;
    if(window.XMLHttpRequest){
        xhr = new XMLHttpRequest(); //标准
    }else{
        xhr = new ActiveXObject('Microsoft.XMLHTTP'); //ie6
    }
  1. 需要传递的参数:请求后台的路径,请求参数,请求方式,回调函数
  2. 请求方式methods,这里我们同一把传入进来的请求方式转为大写var method = method.toUpperCase()
  3. 请求参数格式是'key1=value1&key2=value2'但是我们需要传递的是一个对象,而且get方式请求是在open用?连接;post需要在send进行发送
  4. 由于是异步请求,所以我们需要使用
  5. 有些会把异步状态和响应状态一起写xhr.readyState == 4 && xhr.status == 200;但是个人xhr.status不是200也是需要处理的
  6. ajax.js封装
	function ajax(url,methods,params={},fn){
		var xhr = null;
		var methods = methods.toUpperCase() //方便后续判断
   		if(window.XMLHttpRequest){
        	xhr = new XMLHttpRequest(); //标准
    	}else{
        	xhr = new ActiveXObject('Microsoft.XMLHTTP'); //ie6
    	}
    	// 将参数转化为字符串用&链接的格式
    	var arr = [] //定义一个数组接收键值对
    	for(var key in params){
    		arr.push(`${key}=${params[key]}`)
    	}
    	var str = arr.join('&')//请求格式
    	if(methods=='GET'){
    		url += '?' + str
    	}
    	xhr.open(methods,url,true)
    	var data = null
    	if(methods=='POST'){
    		data = str
    		xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
    	}
    	xhr.send(data)
    	xhr.onreadystatechange = function(){
    		if(this.readyState === 4){
    			if(this.status===200){
    				fn(this.responseText)
    			}
    		}
    	}
	}
  1. 写完后测试一下
    后台测试文件test.php

if($_SERVER['REQUEST_METHOD']==='GET'){
	$username = $_GET['username'];
	$password = $_GET['password'];
	$arr  = array('username' => $username, 'password'=>$password,'methods'=>'get');
	echo json_encode($arr);
}else{
	$username = $_POST['username'];
	$password = $_POST['password'];
	$arr  = array('username' => $username, 'password'=>$password,'methods'=>'post');
	echo json_encode($arr);
}

引入我们写好的ajax.js文件,并写好前端页面

	<form>
		<input type="text" name="username" id="username">
		<input type="text" name="password" id="password">
		<button>testbutton>
	form>

点击按钮先发送get请求,再发送post请求

<script>
	document.querySelector('button').onclick = function(e){
		e = e || window.event
		e.preventDefault && e.preventDefault()
		var username = document.querySelector('#username').value
		var password = document.querySelector('#password').value
		ajax('./test.php','get',{username:username,password:password},function(data1){
			console.log(data1)
			ajax('./test.php','post',{username:username,password:password},function(data1){
			console.log(data1)
			
			})
		})
	}
</script>
  1. 缺点,如果我们想先后获取数据,即ajax是一个异步请求;我们必须在回调函数继续请求,不能并能着写,而且如果发生错误要怎么处理
神器Promise封装

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。

  1. 使用Promise改装上面的ajax.js,放在axios.js文件夹下
function axios(url,type,params){
	return new Promise(function(resolve,reject){
		var xhr = null;
		// var type = type.toUpperCase() //方便后续判断
   		if(window.XMLHttpRequest){
        	xhr = new XMLHttpRequest(); //标准
    	}else{
        	xhr = new ActiveXObject('Microsoft.XMLHTTP'); //ie6
    	}
    	// 将参数转化为字符串用&链接的格式
    	var arr = [] //定义一个数组接收键值对
    	for(var key in params){
    		arr.push(`${key}=${params[key]}`)
    	}
    	var str = arr.join('&')//请求格式
    	type = type.toUpperCase()
    	if(type=='GET'){
    		url += '?' + str
    	}
    	xhr.open(type,url,true)
    	var data = null
    	if(type=='POST'){
    		data = str
    		xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
    	}
    	xhr.send(data)
    	xhr.onreadystatechange = function(){
		    if (this.readyState !== 4) {
    			return;
  			}
  			if (this.status === 200) {
    			resolve(this.response);
  			} else {
    			reject(new Error(this.statusText));
  			}
    	}
	})
}
  1. 使用它
<script src="axios.js"></script>
<script>
	document.querySelector('button').onclick = function(e){
		......
		axios('./test.php','get',{username:username,password:password})
		.then(function(data){
			console.log(data)
			return axios('./test.php','post',{username:username,password:password})
		})
		.catch(function(err){
			console.log(err)
		})
		.then(function(data){
			console.log(data)
		})
		
	}
</script>

从ajax异步请求封装和JSONP跨域封装到第三方库的使用_第1张图片
3. 当然我们最好在封装的时候判断格式是否是json;是的话使用JSON.parse()将其转换,这点大家自己加上

Promise
  1. 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

JSONP

JSON with Padding;是一种借助script标签发送跨域请求的技巧

其原理就是在客户端借助 script 标签请求服务端的一个动态网页,服务端的这个动态网页返回一段带有函数调用的 JavaScript 全局函数调用的脚本,将原本需要返回给客户端的数据传递进去

比如我们的访问地址是http://localhost/test/test.html,但是请求地址域名是不同的

axios('http://127.0.0.1/test/test.php','get',{username:username,password:password})
		.then(function(data){
			console.log(data)
		})

结果

服务端代码http://localhost/jsonp/jsonp.html

请求的后台代码jsonp.php文件

 
    $sData = '{"msg":"","status":"0","error_code":"0","data":"服务端测试"}';
    $cbName = $_GET['callback'];
    echo $cbName.'('.$sData.')';
 ?>

前端使用jsonp进行跨域请求

<script>
	function foo(data){
		console.log(data)//{msg: "", status: "0", error_code: "0", data: "服务端测试"}
		console.log(typeof data)//object
	}
</script>
<script src="http://127.0.0.1/jsonp/jsonp.php?callback=foo" type="text/javascript"></script>
请求城市的天气案例
    <input type="text" id="inp">
    <button id="btn">点击button>
<script type="text/javascript">
   var inp = document.getElementById('inp')
   var btn = document.getElementById('btn')
   function hello(data){
       console.log(data);
   }
   btn.onclick = function(){
      var script = document.createElement('script');
      script.src = `
      http://api.jisuapi.com/weather/query?appkey=eee8cacbc1e612cd&city=${inp.value}&callback=hello`;
      var head = document.getElementsByTagName('head')[0];
      head.appendChild(script);    
   }  
</script>

获取广州的天气
从ajax异步请求封装和JSONP跨域封装到第三方库的使用_第2张图片

这么好用和常用的东西,当然得进行封装
  1. 我们使用Promise对象和面向对象来封装;为什么要使用这两种;因为让人看起来很高级;当然;主要是不会重复创建实例;
  2. 我们首先要对参数进行处理,我们设定参数是对象,查看是否有传入参数,有的话使用for…in循环;
  3. 我们随机生成一个函数名,声明它
  4. 创建后拿到我们的数据后就删除它
  5. 创建一个jsonp.js文件
// jsonp的封装
function Jsonp(){
	
}
// 创建链接
Jsonp.prototype.ranNum = function(min,max){ //生成随机数
	return parseInt(Math.random()*(max-min+1)+min)
}
Jsonp.prototype.get = function(url,data){
	return new Promise((resolve,reject)=>{
		var num = this.ranNum(0,100) //获取[0,100]随机数
		var callBackRadom = "jsonpSuccess_" + num; //指定回调函数
		window[callBackRadom] = resolve;//处理函数,这是函数名,resolve成功后的回调函数(这是声明式函数)
		var script = document.createElement('script');//创建script标签
		var arr = [] //用来存放我们的键值对数据
		script.type = "text/javascript"
		script.id = callBackRadom//给它一个id;方便我们后续用来删除它
		if(data!=null){
			for(var key in data){
				arr.push(`${key}=${encodeURIComponent(data[key])}`)//对字符串进行编码
			}
			var str = arr.join('&')//转化成我们想要的格式
			script.src = `${url}?${str}&callback=${callBackRadom}`;//发送请求
		}else{
			script.src = `${url}&callback=${callBackRadom}`;
		}
	    var head = document.getElementsByTagName('head')[0];
	    head.appendChild(script);//添加到头部
	    this.removeJsonp(callBackRadom) //创建后拿到数据就删除		
	})	
}
Jsonp.prototype.removeJsonp = function(id){
	var head = document.getElementsByTagName('head')[0];
    var el = document.getElementById(id);
    if (head != null && el != null) {
        head.removeChild(el);
    }
}
var jsonp = new Jsonp()
  1. 如何来调用它(使用)
<script src='jsonp.js'></script>
<script>
	jsonp.get(`http://api.jisuapi.com/weather/query`,{appkey:'eee8cacbc1e612cd',city:'深圳'})
	.then(data=>{
		console.log(data)
		return jsonp.get(`http://api.jisuapi.com/weather/query`,{appkey:'eee8cacbc1e612cd',city:'汕头'})
	})
	.then(data=>{
		console.log(data)
	})
</script>

从ajax异步请求封装和JSONP跨域封装到第三方库的使用_第3张图片

readyState

readyState 状态描述 说明
0 UNSENT 代理(XHR)被创建,但尚未调用 open() 方法。
1 OPENED open() 方法已经被调用,建立了连接。
2 HEADERS_RECEIVED send() 方法已经被调用,并且已经可以获取状态行和响应头。
3 LOADING 响应体下载中, responseText 属性可能已经包含部分数据。
4 DONE 响应体下载完成,可以直接使用 responseText 。
时间轴

从ajax异步请求封装和JSONP跨域封装到第三方库的使用_第4张图片
由于 readystatechange 事件是在 xhr 对象状态变化时触发(不单是在得到响应时),也就意味着这个事件会被触发多次,所以我们为什么要设定xhr.readyState=4的原因

ajax遵循http

本质上 XMLHttpRequest 就是 JavaScript 在 Web 平台中发送 HTTP 请求的手段,所以我们发送出去的请求任然是HTTP 请求,同样符合 HTTP 约定的格式:

// 设置请求报文的请求行
xhr.open('GET', './time.php')
// 设置请求头
xhr.setRequestHeader('Accept', 'text/plain')
// 设置请求体
xhr.send(null)
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
// 获取响应状态码
console.log(this.status)
// 获取响应状态描述
console.log(this.statusText)
// 获取响应头信息
console.log(this.getResponseHeader('Content‐Type')) // 指定响应头
console.log(this.getAllResponseHeader()) // 全部响应头
// 获取响应体
console.log(this.responseText) // 文本形式
console.log(this.responseXML) // XML 形式,了解即可不用了
}
}

同源策略

同源策略是浏览器的一种安全策略,所谓同源是指域名,协议,端口完全相同,只有同源的地址才可以相互通过AJAX 的方式请求。同源或者不同源说的是两个地址之间的关系,不同源地址之间请求我们称之为跨域请求。
由于同源从略的限制,XMLHttpRequest只允许请求前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后再服务端输出JSON数据并执行回调函数,从而解决跨域数据请求; 而JSONP 需要服务端配合,服务端按照客户端的要求返回一段 JavaScript 调用客户端的函数。

jsonp的产生
  1. jax直接请求普通文件存在跨域无权限访问的问题,甭管你是静态页面、动态网页、web服务、WCF,只要是跨域请求,一律不准;
  2. 当前阶段如果想通过纯web端(ActiveX控件、服务端代理、属于未来的HTML5之Websocket等方式不算)跨域访问数据就只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理;
  3. 恰巧,我们已经知道有一种叫做JSON的纯字符数据格式可以简洁的描述复杂数据,更妙的是JSON还被js原生支持,所以在客户端几乎可以随心所欲的处理这种格式的数据;
  4. 这样子解决方案就呼之欲出了,web客户端通过与调用脚本一模一样的方式,来调用跨域服务器上动态生成的js格式文件(一般以JSON为后缀),显而易见,服务器之所以要动态生成JSON文件,目的就在于把客户端需要的数据装入进去。
  5. 客户端在对JSON文件调用成功之后,也就获得了自己所需的数据,剩下的就是按照自己需求进行处理和展现了,这种获取远程数据的方式看起来非常像AJAX,但其实并不一样。
  6. 为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
Cross Origin Resource Share,跨域资源共享
header('Access‐Control‐Allow‐Origin: *');

这种方案无需客户端作出任何变化(客户端不用改代码),只是在被请求的服务端响应的时候添加一个 AccessControl-Allow-Origin 的响应头,表示这个资源是否允许指定域请求

同步与异步

  • 同步:必须等待前面的任务完成,才能继续后面的任务(我们在银行排队时,只有等到你了,才能够去处理业务)
  • 异步:我们在排队的时候,拿台电脑打代码是没有任何影响的(不建议做低头族哦)
同步和异步

想象一下:在一片蓝色的天空和碧绿的湖水包围下;你在刷着微博;当你看完了内容;点击加载更多按钮;页面重新刷新了…但实际情况是:我们在访问新浪微博时,当你看到一大半了,会自动帮我们加载更多的微博,同时页面并没有刷新

ajax的好处
  1. 当然,实现前后端分离;前端只需要关注自己的数据
  2. 加载页面的时候能在不更新整个页面的前提下维护数据
  3. Ajax应用程序必须在众多不同的浏览器和平台上经过严格的测试。
    但是,同样的它可能破坏浏览器的后退与加入收藏书签功能;对搜索引擎支持较弱;还有它的安全等问题

第三方库

Axios

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中

  1. 一般都是首推这个。因为它•支持 Promise API
  2. 拦截请求和响应;这样在我们判断进入页面是否登录注册了可以进行判断:比如判断是否有身份认证存储的信息
import Axios from 'axios'
Vue.prototype.HOST = 'http://localhost:3000'//虚拟接口
Vue.prototype.$axios  = Axios//在vue中使用

// 添加请求拦截器
Axios.interceptors.request.use(function (config) {
  const token = localStorage.getItem('auth_token')
  config.headers['X-Access-Token'] = token
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 添加响应拦截器
Axios.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response;
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error);
});
  1. 还有其它:转换请求数据和响应数据;取消请求;自动转换 JSON 数据;客户端支持防御 XSRF
httpClient

这是angular的

import { HttpClient } from '@angular/common/http'
...
  constructor(
    private http: HttpClient,
    private router: Router
  ) { }
  ......
      this.http.post(url, params)
      .toPromise()
      .then(data => {
      })
      .catch(err => {
      })
vue-resourse

这是vue提供的;但是已经不再维护了…

import VueResource from 'vue-resource'
Vue.use(VueResource)
Vue.http.options.root = 'http://www.liulongbin.top:3005/'
Vue.http.options.emulateJSON = true//支持post
this.$http.get('api/getnew/'+this.id).then(result=>{
  })
jquery

http://jquery.cuishifeng.cn/jQuery.Ajax.html

$.get()	 $.post()  $.getJSON()  不需要传入对象,单个参数即可,而ajax可以传入对象
 $.get('json.php', { id: 1 }, function (res) {
       console.log(res)
    })
    // $.post('json.php', { id: 1 }, function (res) {
    //   console.log(res)
    // })

    // $.getJSON('json.php', { id: 1 }, function (res) {
    //   console.log(res)
    // })

当然可以使用一个ajax搞定了

$.ajax({
		url: '',
		type: 'get',
		data: { id: 1, name: '张三' },
		dataType: 'json',
    	// 一旦设置的 dataType 选项,就不再关心 服务端 响应的 Content-Type 了
        // 客户端会主观认为服务端返回的就是 JSON 格式的字符串
		success:function(data){
			console.log(data)
		},
		error:function(){

		},
		complete:function(){

		}
	})

如果想要在请求前和请求后处理一些业务逻辑

$(document)
      .ajaxStart(function () {
        // 只要有 ajax 请求发生 就会执行
        $('.loading').fadeIn()
        // 显示加载提示
        console.log('注意即将要开始请求了')
      })
      .ajaxStop(function () {
        // 只要有 ajax 请求结束 就会执行
        $('.loading').fadeOut()
        // 结束提示
        console.log('请求结束了')
      })
fetch

Fetch API 提供了一个获取资源的接口(包括跨域请求fetch-jsonp)。任何使用过 XMLHttpRequest 的人都能轻松上手,但新的API提供了更强大和灵活的功能集。
https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API

wx.request

这是微信小程序发送请求的API

//app.js
App({
  config: {
    apiBase: 'https://locally.uieee.com'
  }
})

进行封装utils/fetch.js

const app = getApp()
module.exports = (url,data,methods="get")=>{
  wx.showLoading({ title: 'Loading...' })
  return new Promise((res, rej)=>{
    wx.request({
      url:app.config.apiBase+url,
      data,
      methods,
      success:function(data){
        res(data)
      },
      fail:function(err){
        rej(err)
      },
      complete:wx.hideLoading()
    })
  })
}

引入并使用fetch代码

const fetch = require('../../utils/fetch.js')
    fetch(`/shops/1`)
    .then(res=>{
      console.log(res.data)
    })
    .catch(err=>{
      console.log(err)
    })
配合ajax使用的其它第三方库

art-template 是一个简约、超快的模板引擎。
前端轻量级web进度条 – Nprogress
better-scroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。

如何将jsonp和ajax一起封装

我这里就不深入了…可以参考下jquery源码。
我的思路:

  1. 我们要考虑是否同源,可以像jquery传入一个dataType判断;也可以自己根据locallhost来判断
  2. ajax主要是异步请求对象XMLHttpRequest;而jJSONP主要通过创建script发送请求
  3. 都是要使用回调函数将数据存储出来
  4. 想封装的更完美一些最好配合进度事件
实现跨域

readyState状态
ajax进度

你可能感兴趣的:(js)