渣码AJAX:从xhr到fetch

78 阅读5分钟

async javascripts and XML

原生方式

  • 创建jax核心对象XMLHttpRequest
let xhr = new XMLHttpRequest()
  • 注册回调函数
xhr.onreadystatechange = function () {
    console.log(xhr.readyState)//会被调用4次
}
xhr.onreadystatechange = function () {
    console.log(xhr.readyState)
    // readyState 1-2-3-4
    if (this.readyState == 4) {
        //表示响应结束了
        console.log("响应结束了")
        //响应结束会有一个HTTP状态码200 - 300表示成功 是HTTP协议的一部分
        console.log(this.status)
        if (this.status == 404) {
            alert('访问的页面不存在')
        }
        else if (this.status >= 200 && this.status <= 300) {
            alert('响应成功')
            document.querySelector('mydiv').innerHTML = this.responseText//从服务器传出来的数据用来渲染div盒子
        }
    }
}
  • 开启通道
XMLHttpRequest.open(method,url,async,user,psw)
//method :GET/POST
//url请求的路径
//async:异步与否
//user:用户名
//pwd:密码
  • 通过 ajax 提交数据
xhr.open("GET","url?name=value&name=value...",true)
  • post请求提交数据:
xhr.open("POST","url",true)
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
//模拟form表单数据提交,这里一定要先open才能设置请求头
let password = document.querySelector('password').value
xhr.send("username="+username+"&password="+password)
  • 发送请求
  • send()用于get请求, send(string)用于POST请求
        xhr.send()

完整代码

<!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>
</head>
<body>
    <script type="text/javascript">
        window.onload = function () {
            document.getElementById("btn").onclick = function () {
                //创建XMLHttprequest核心对象
                let xhr = new XMLHttpRequest()
                //注册回调函数
                xhr.onreadystatechange = function () {
                    if (this.readyState == 4) {
                        if (this.status == 200) {
                            //通过XMLHttpRequest对象的response属性可以获取到服务器的内容
                            document.querySelector('.myspan').innerHTML = this.responseText
                        } else {
                            alert(this.status)
                        }
                    }
                }
                //开通道
                xhr.open('GET', "/ajax/??", true)
                //发送请求
                xhr.send()
            }
        }
    </script>
    <button id='btn'>发送ajax get请求</button>
    <span id="myspan"></span>
</body>
</html>

fetch

他是区别于XML的ajax请求接口,可以看作升级版的XMLHttprequest 值得注意的是:fetch 不会发送跨域的cookie

  • Promise<Response> fetch(input[, init]);

input

• 一个 USVString 字符串,包含要获取资源的 URL。一些浏览器会接受 blob:data: 作为 schemes.

init

  • 一个 Request 对象。init 可选一个配置项对象,包括所有对请求的设置。可选的参数有:
  • method: 请求使用的方法,如 GETPOST
  • headers: 请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。
  • body: 请求的 body 信息:可能是一个 BlobBufferSourceFormDataURLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
  • mode: 请求的模式,如 corsno-cors 或者 same-origin
  • credentials: 请求的 credentials,如 omitsame-origin 或者 include。为了在当前域名内自动发送 cookie,必须提供这个选项,从 Chrome 50 开始,这个属性也可以接受 [FederatedCredential (en-US)](developer.mozilla.org/en-US/docs/…) 实例或是一个 [PasswordCredential (en-US)](developer.mozilla.org/en-US/docs/…) 实例。
  • cache: 请求的 cache 模式:defaultno-storereloadno-cacheforce-cache 或者 only-if-cached
  • redirect: 可用的 redirect 模式:follow (自动重定向), error (如果产生重定向将自动终止并且抛出一个错误),或者 manual (手动处理重定向)。在 Chrome 中默认使用 follow(Chrome 47 之前的默认值是 manual)。
  • referrer: 一个 USVString 可以是 no-referrerclient 或一个 URL。默认是 client
  • referrerPolicy: 指定了 HTTP 头部 referer 字段的值。可能为以下值之一:no-referrerno-referrer-when-downgradeoriginorigin-when-cross-originunsafe-url
  • integrity: 包括请求的 subresource integrity 值(例如: sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)。

返回值

一个 Promise,resolve 时回传 Response 对象。

使用实例

fetch('
autolinkhttp://example.com/movies.jsonautolink
')
  .then(response => response.json())
  .then(data => console.log(data));

fetch返回一个Promise对象,但是不是JSON,而是HTTP响应,用response.json()获得JSON的Promise对象,然后再次用then方法来获取到JSON格式的data

是javascript的全局对象,可以直接调用

btn[2].onclick = () => {
        fetch('
autolinkhttp://127.0.0.1:8000/serverautolink
',
            {
                method: "POST",
                headers: {
                    name: "chenzikai"
                },
                body: 'username=admin&password=admin'
            }
        ).then(response => {
            return response.json()
        }).then(response => {
            console.log(response.name)
        })
    }

跨域问题

从一个域名的网页去请求另一个域名的资源

通过超链接,form表单,window.location.href/document.location.href执行的跨域提交或者访问操作是没有问题的

如果尝试用ajax去访问其他站点的资源,默认情况下会报错:ajax访问被同源策略(CORS)阻止,只有同源的情况下XMLHttpRequest.responseText才可以共享

同源策略是浏览器的策略: 所谓同源就是协议一致,域名一致,端口号一致,就是同源,任何一个不同就不算同源。同源策略是一种安全策略(因为ajax是可以共享数据的)

解决方案

设置响应头

b站点允许ajax跨域访问

response.setHeader("Access-Control-Allow-Origin","
" ) *response.setHeader("Access-Control-Allow-Headers","*
")
response.setHeader("Access-Control-Allow-Method","*")

这是对所有网站开放

response.setHeader("Access-Control-Allow-Origin","autolinkhttp://localhost:8080autolink")

这是对localhost:8080端口开放

jsonp 方式

json with padding (带填充的json) 不是一个真正的ajax请求,jsonp是一种类似ajax请求的机制, 可以完成局部刷新的效果和跨域

利用script的src属性可以是xxx.js文件,让sevelet作为src也可以

//在后端中
@WebServelet("jsonp1")
//在doGet方法中返回JavaScript指令,这个指令会传到前端,由前端浏览器来解析执行
out.print("sayHello()")//这个函数是前端定义的
out.print("sayHello(\"这里写json字符串\")")
//动态获取函数名
String fun = request.getParameter("fun")
out.print(fun + "(JSON对象字符串)")
//在前端中
<script type="text/javascript">
function sayHello (
        alert("hello," + data.name) {}
 </script>
<script type="text/javascript" src="后端servelet路径 localhost:8080/b/jsonp1 ?fun=sayHello">
=</script>//把函数名传到后端让后端动态读取函数名

只能是get请求

上面的版本是打开的时候就会执行,下面实现点击的局部刷新

<body>
    <script type="text/javascript">
        function sayHello(data) {
            document.getElementById("mydiv").innerHTML = data.username
        }
        window.onload = () => {//等待页面加载
            document.getElementBy('btn').onclick = () => {//绑定点击事件
                const htmlScirptElement = document.createElement("script")//创建script对象
                htmlScirptElement.type = "text/javascript"
                htmlScirptElement.src = "http:/localhost:8081/b/jsonp2?fun=sayHello"//设置script属性
                document.getElementsByName("body")[0].appendChild(htmlScirptElement)//将script标签添加到body中,就是加载javascript
            }
        }
    </script>
    <button id="btn">jsonp 解决跨域问题,达到ajax局部刷新效果</button>
    <div id="mydiv"></div>
</body>

jquery

已经有现成的jquery库,对jsonp进行了高度封装,底层原理完全相同

<!-- //引入jQuery库 -->
    <script type="text/javascript" src="/a/js/jquery-3.6.0.min.js"></script>
    <script type="text/javascript">
        
$function ()  {
            $
("#btn").click(function(){
                
$.ajax({
                    type :"GET" , 
                    url :"<http://localhost:8081/b/json3>",
                                                                                //这里jQuery会自动生成一个callback函数并加到url后面,在后端中只需要直接用callback()就行
                    datatype : "jsonp",
                    success : function(data){//data是json对象
                        $
("#mydiv").html("欢迎你:" + data.useranme)
                    }
                })
            })
        }
    </script>

不采用默认函数名和回调函数可以这样写

$.ajax({
        type :"GET" ,
        url :"
autolinkhttp://localhost:8081/b/json3autolink
",
        datatype : "jsonp",
                                jsonp:"fun",//指定函数名字
              jsonpCallback : "sayHello"//不设置的的话会自动生成一个调用success的callbakc函数,名字就叫callback

使用 反向代理

用java代码去跨域访问,ajax就不用跨域

react vite proxy 配置 反向代理

  • npm install vite axios
  • 主要是配置vite.config.js 文件
proxy: {
        "/api": {//接口可匹配多个域名,为/api时走这个域名
          // 当遇到 /api 路径时,将其转换成 target 的值
          target: "
autolinkhttps://study:8888autolink
",
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, "/api"),// 将 /api 重写
        },
        "/api2": {
          target: "
autolinkhttps://lianghj.top:8888autolink
",
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ""),
        },
  • 配置好代理之后,向对应的路径请求的时候要注意不能写完整的url否则会导致404
  • 他会拼上target然后再请求,所以我们直接写/api开始的路径就行了

creat-react-app 配置 反向代理

好像是用webpack,不清楚这方面还得学

  • npm install createProxyMiddleware
  • 在src文件目录下创建一个 setupProxy.js 必须是这个名字
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = function (app) {
    app.use(
        '/ajax', //以ajax开头的代理
        createProxyMiddleware({
            target: '
autolinkhttps://i.maoyan.comautolink
',//目标域名
            changeOrigin: true,
        })
    )
}

这样看好像是node.js的语法,配置了一个express中间件,不知其中的原理如何

AJAX异常和取消

异常取消

  btn.onclick = function () {
        //创建对象1
        const xhr = new XMLHttpRequest()
        //打开通道并发送请求23
        xhr.open("GET", "
autolinkhttp://127.0.0.1:8000/serverautolink
")
        xhr.send()
        //注册回调函数4 只需要在创建之后再注册就行
        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    //响应成功,处理结果
                    // console.log(xhr.status)
                    // console.log(xhr.statusText)
                    // console.log(xhr.getAllResponseHeaders())
                    // console.log(xhr.response)
                    document.getElementById('result').innerHTML = xhr.response
                }
            }
        }
    }

在以上的代码基础上,添加一个超时和网络异常处理

xhr.timeout=2000;//2s
xhr.ontimeout = () => {
alert("请求超时,请稍后重试")
}
xhr.onerror = () => {
        alert("你的网络好像出现了一些问题")
}

主动取消请求

let x = null; //注意要放在函数外部,因为发送请求和取消请求的按钮应该是不同的函数体
btn.onclick = () => {
                x.abort()
}

防止重复请求

创建标识符

let isSending = false
//在发送请求的时候就表示在发送
isSending = true
//在x.readyState === 4 就是响应结束的时候判断为false
if ( x.readyState === 4 ) isSending = flase

在点击发送的时候先判断有没有正在发送的同一种请求,如果有就取消重新发

let isSending = false

btn.onclick = function () {
        //创建对象1
                                if ( isSending) xhr.abort
        const xhr = new XMLHttpRequest()
                                isSending = true
        //打开通道并发送请求23
        xhr.open("GET", "
autolinkhttp://127.0.0.1:8000/serverautolink
")
        xhr.send()
        //注册回调函数4 只需要在创建之后再注册就行
        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                                                                isSending = false
                if (xhr.status >= 200 && xhr.status < 300) {
                    //响应成功,处理结果
                    // console.log(xhr.status)
                    // console.log(xhr.statusText)
                    // console.log(xhr.getAllResponseHeaders())
                    // console.log(xhr.response)
                    document.getElementById('result').innerHTML = xhr.response
                }
            }
        }
    }

手撕Jquery

重复代码封装工具类,可以把他当作一个JS的库,所以这里和ajax其实没什么关系,换句话说就是手写jQuery

初步封装:

<script type="text/javascript">
        // 封装一个函数,通过这个函数可以找到html中的页面的结点
        function jQuery(selector) {
            if (typeof selector == "string") {
                if (selector.charAt(0) == "#") {//这是个id选择器
                    let element  = document.getElementById(selector.substring(1))//截掉字符串
                                return element
}
            }
            if (typeof selector == "function") {
                window.onload = selector
            }
        }
        $ = jQuery
        
$(function () {//这里jQuery先得到一个回调函数,所以会执行window.onload = selector 
            $
("#btn").onclick = function () {
                $("#div1").innerHTML = "<font color='red'> 用户名不可用 ~~~~</font>"//下面这几个就是把jquery作为对选择器的封装来用了
            }
        })
    </script>

进一步封装:

function jQuery(selector) {
            if (typeof selector == "string") {
                if (selector.charAt(0) == "#") {//这是个id选择器
                    domObj = document.getElementById(selector.substring(1))//让他是一个全局变量
                    return new jQuery()//返回的是一个对象,这样就可以再对这个对象封装函数
                }
            }
            if (typeof selector == "function") {
                window.onload = selector
            }
            this.html = function (htmlStr) {
                domObj.innnerHTML = htmlStr
            }
            this.click = function (fun) {
                domObj.onclick = fun
            }
            //还可以继续封装方法
            this.focus = function(fun) {
                domObj.onfocus = fun
            }
        }
        $ = jQuery
        
$(function () {//这里jQuery先得到一个回调函数,所以会执行window.onload = selector 
            $
("#btn").click(function () {//这里将jquery当作一个对象,使用它的实例方法
                $("#div1").innerHTML = "<font color='red'> 用户名不可用 ~~~~</font>"//下面这个就是把jquery作为对选择器的封装来用了

            })
        })

封装彻底之后:

代替了jQuery代替了jQuery: ()函数如果传入的是一个字符串,就会当作一个选择器用,用完之后返回一个jQuery对象,可以直接用jQuery封装的函数

如果()传入的是一个函数,那么就把这个函数赋值给window.onload,这样再最外层的时候只需要用()传入的是一个函数,那么就把这个函数赋值给window.onload,这样再最外层的时候只需要用(这里面写下一步代码)就能实现window.onload = function() {同上一个花括号内容}的效果,封装完之后,整个代码变得非常简洁

其实这里手撕jQuery是为了理解它的源码,实际开发的时候我我们直接用别人包装好的方法就行了

最重要的是这个想法:

在把jquery当作选择器来用之后返回一个对象,而且将选择到的元素声明为全局变量,然后给jquery对象写静态方法,最后用$ = jQuery 实现彻底的封装

如果对 ajax 的四步进行封装

下面这一个封装从元素的获取到发送ajax请求到渲染页面的函数可以当作手写的jQuery v1.0.0

function jQuery(selector) {
    if (typeof selector == "string") {
        if (selector.charAt(0) == "#") {//这是个id选择器
            domObj = document.getElementById(selector.substring(1))//让他是一个全局变量
            return new jQuery()//返回的是一个对象,这样就可以再对这个对象封装函数
        }
    }
    if (typeof selector == "function") {
        window.onload = selector
    }
    this.html = function (htmlStr) {
        domObj.innnerHTML = htmlStr
    }
    this.click = function (fun) {
        domObj.onclick = fun
    }
    //还可以继续封装方法
    this.focus = function (fun) {
        domObj.onfocus = fun
    }
    //静态方法
    //要保证灵活性:所以要传参(用一个json对象) :
    // 请求方式type
    // url
    // 提交的数据data
    // 异步还是同步
    jQuery.ajax = function (jsonArgs) {
        //1
        const xhr = new XMLHttpRequest()
        //2
        xhr.onreadystatechange = function () {
            if (this.readyState == 4) {
                if (this.status == 200) {
                    const jsonObj = JSON.parse(this.responseText)
                    jsonArgs.success(jsonObj)
                }
            }
            if (jsonArgs.type.toUpperCase() == "POST") {
                // 3
                xhr.open()("POST", jsonArgs.url, jsonArgs.async)
                // 4
                xhr.setRequestHeader("")
                xhr.send(jsonArgs.data)
            }
            if (jsonArgs.type.toUpperCase() == "GET") {
                // 3
                xhr.open()("GET", jsonArgs.url+"?"+jsonArgs.data, jsonArgs.async)
                // 4
                xhr.send()
            }
        }
    }
}
$ = jQuery
new jQuery //这里必须new一下后面才能用jQuery里面的静态方法

⚠️要注意的是,这里一定要先new一下,才能用里面的ajax等静态方法

对于封装好的工具,我们只需要引入,然后像下面这样引用就可

$$(function(){
        $$("btn1").click(function(){
                        
$.ajax({
    type: "POST",
    url: "",
    data: "username" + $
("#username").val(),
    async: true,
    success: function (Obj) {
        // 里面写回调函数要执行的内容
                                $("#div1").html(json.uname)
    }
  }
)
    }
  }
)

$.get方法

$$(() => {
        $$('button').eq(0).click(() => {
            $.get("
autolinkhttp://127.0.0.1:8000/server-jQueryautolink
", { a: 100, b: 200 }, function (data) {
                console.log(data)
            })
        })
    }
    )