• 一幅长文细学JavaScript(七)——一幅长文系列


    7 Ajax

    7.1 概述

    7.1.1 基本概念

    服务器(Server):负责存放和对外提供资源的电脑。

    客户端(Client):负责获取和消费资源的电脑。

    统一资源定位符(UniformResourceLacator,URL):用于标识互联网上每个资源的唯一存放位置。浏览器只有通过URL位置,才能正确定位资源的存放位置,从而成功访问到对应的资源。


    7.1.2 网络通信开发者工具

    如果是edge浏览器,可以右键检查,然后按图中步骤查看。

    image-20220723115259107

    如果是谷歌浏览器,同样点击右键检查,然后按图中步骤查看。

    image-20220723115436988


    7.1.3 网页请求数据的方式

    数据,也是服务器对外提供的一种资源。只要是资源,必然要通过请求-处理-响应的方式来获取。

    image-20220723115754153

    如果要在网页中请求服务器上的数据资源,就需要使用XMLHttpRequest(简称xhr)对象。类似于document对象都是由浏览器即window提供的。不同的是,xhr需要通过构造函数实例化。即var xhrObj = new XMLHttpRequest()。


    7.1.4 资源的请求方式

    请求的方式有很多,常见的为get和post。

    • get请求通常用于获取服务端资源(向服务器要任何资源)

    • post请求通常用于向服务器提供数据(往服务器发送资源)


    7.2 JQuery中的Ajax

    7.2.1 基本知识

    Ajax的全程是Asynchronous Javascript And XML(异步js和XML)。通俗的理解就是在网页中利用xhr来和服务器进行数据交互的方式

    之前所学的技术只能把网页做的美观,如果要实现网页和服务器的数据交互则需要使用Ajax。

    image-20220813121753415

    Ajax典型的应用场景有:用户名检测是否存在、数据可视化大屏的时间更新、搜索引擎动态加载提示列表、根据页码值动态刷新表格的数据、数据的增删改查。


    7.2.2 了解jQuery的Ajax

    浏览器中提供的XMLHttpRequest用法较为复杂,故JQuery对其进行了封装,提供了一系列Ajax相关的函数,极大地降低了Ajax的使用难度。

    JQuery中发起Ajax请求的最常用三个方法如下:

    • $.get()
    • $.post()
    • $.ajax()

    7.2.3 $.get()

    说明:jQuery中的$.get()函数的功能单一,专门用于发起get请求,从而将服务器上的资源请求到客户端来进行使用。

    语法:$.get(url,[data],[callback])


    7.2.4 $.post()

    说明:jQuery中的$.post()函数的功能单一,专门用于发起post请求,

    语法:$.post(url,[data],[callback])


    7.2.5 $.ajax()

    说明:$.ajax可看做是get和post的结合体。

    语法

    $ajax({
    	type:'',//post或get
    	url:'',
    	data:{},
    	success:function(res){}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    7.3 接口

    7.3.1 接口概念

    说明:使用ajax请求数据时,被请求的URL地址,被叫做数据接口。同时,每个接口必须有请求方式。


    7.3.2 接口测试工具

    • postman
    • arifox

    7.4 form表单

    7.4.1 form表单的基本使用

    说明:表单在网页中主要负责数据采集的功能,HTML中的

    标签就是用于采集用户输入的信息,并通过其提交来将采集后的信息提交到服务器端进行处理。


    7.4.2 form标签的属性

    说明:标签用来采集数据,标签的属性则是用来规定如何把采集到的数据发送到服务器。

    属性描述
    actionURL地址规定当提交表单时,向何处发送表单数据
    methodget或post规定以何种方式将表单数据提交到action URL
    enctypeapplication/x-www-form-urlencoded ,multipart/form-data ,text/plain规定在发送表单数据之前如何对其进行编码
    target_blank/, _self, _parent, _top, framname规定在何处打开action URL

    action

    • action属性用来规定当提交表单时,向何处发送表单数据
    • action属性的值应该是后端提供的一个URL地址,这个URL地址专门负责接收表单提交过来的数据
    • 当表单在未指定action属性的情况下,action的默认值为当前页面的URL地址
    • 当提交表单后,页面会立即跳转到action属性指定的URL地址中

    method

    • method默认为get,表示通过URL地址的形式,将表单数据提交到action URL中。
    • get方式适合用来提交少量的、简单的数据。
    • post方式适合用来提交大量的、复杂的、或包含文件上传的数据
    • 在实际开发中,表单的post提交方式最多,很少用get。例如登录、注册、添加数据等表单操作,都需要使用post方式来提交表单。

    enctype

    描述
    application/x-www-form-urlencoded在发送前编码所有字符(默认)
    multipart/form-data不对字符编码。在使用包含文件上传控件的表单时,必须使用该值
    text/plain空格转换为+,但不对特殊字符编码

    7.4.3 表单的同步提交

    说明:按submit后表单提交到指定的url即为同步提交

    缺点

    • 整个页面会发生跳转,用户体验差
    • 表单同步提交后,页面之前的状态和数据会丢失

    解决方案:表单只负责采集数据,Ajax来复杂将数据提交到服务器。

    解决步骤:获取表单元素并监听,一旦提交数据,则触发事件通过ajax提交数据给服务器。

    <script src="./jquery.min.js">script>
    head>
    
    <body>
        <div>
            <form action="login" id="f1">
                <input type="text" placeholder="请输入账号">
                <br>
                <input type="text" placeholder="请输入密码">
                <br>
                <input type="submit" value="登录">
            form>
        div>
    
        <script>
            $(()=>{
                $('#f1').submit((e)=>{
                    alert('表单被提交')
                })
            })
        script>
    body>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    7.4.4 阻止表单默认提交行为

    <script src="./jquery.min.js">script>
    head>
    
    <body>
        <div>
            <form action="login" id="f1">
                <input type="text" placeholder="请输入账号">
                <br>
                <input type="text" placeholder="请输入密码">
                <br>
                <input type="submit" value="登录">
            form>
        div>
    
        <script>
            $(()=>{
                $('#f1').submit((e)=>{
                    alert('表单被提交')
                    e.preventDefault()// 可以阻止表单提交后跳转界面
                })
            })
        script>
    body>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    7.4.5 快速获取提交数据

    通过给form标签指定id,通过JQuery的serialize函数可以快速获取表单信息,但form中的每个表单元素必须都有设置name属性

    <script src="./jquery.min.js">script>
    head>
    
    <body>
        <div>
            <form action="login" id="f1">
                <input type="text" placeholder="请输入账号" name = "username">
                <br>
                <input type="text" placeholder="请输入密码" name = "password">
                <br>
                <input type="submit" value="登录">
            form>
        div>
    
        <script>
            $(function() {
                $('#f1').submit((e)=>{
                    e.preventDefault()// 可以阻止表单提交后跳转界面
                    let data = $('#f1').serialize()
                    console.log(data);
                })
            })
        script>
    body>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    7.5 模板引擎

    7.5.1 模板引擎

    说明:根据程序员指定的模板结构和数据,自动生成一个完整的HTML页面。

    好处:减少了字符串的拼接操作,代码更加清晰更容易维护。


    7.5.2 art-template

    说明:art-template是一个简约超快的模板引擎。

    官网art-template (aui.github.io)

    注意:我们实际上不需要学习这个模板引擎,Vue使用的就是模板的概念,而且es6也有模板字符串,无需模板引擎。


    7.6 原生Ajax

    7.6.1 XMLHttpRequest

    说明:XMLHttpRequest(简称xhr)是浏览器提供的js对象,通过它可以请求服务器上的数据资源。Jquery的Ajax函数由此封装。

    示意图

    image-20220813142248068


    7.6.2 xhr对象属性

    属性描述
    onreadystatechange定义当 readyState 属性发生变化时被调用的函数
    readyState保存 XMLHttpRequest 的状态。 0:请求未初始化 1:open方法已被调用 2:send方法被调用,请求已收到 3:正在处理请求,数据接收中 4:ajax请求完成,这意味着数据传输已经彻底完成或者失败
    responseText以字符串返回响应数据
    responseXML以 XML 数据返回响应数据
    status返回请求的状态号 200: “OK” 403: “Forbidden” 404: “Not Found”
    statusText返回状态文本(比如 “OK” 或 “Not Found”)

    7.6.3 xhr的get请求

    // 1.创建xhr对象
    let xhr = new XMLHttpRequest()
    // 2.调用open函数指定请求方式和URL地址
    xhr.open('GET','{name:zs,age:12}')
    // 3.调用send函数发起ajax请求
    xhr.send()
    // 4.监听onreadystatechange事件
    xhr.onreadystatechange = ()=>{
        // 如果服务器能够连接则返回数据
        if(xhr.readyState === 4 && xhr.status === 200) console.log(xhr.responseText);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    7.6.4 xhr发起带参数的get请求

    xhr.open('Get','http://www.liulongbin.top:3306/api/getbooks?id=1')
    
    • 1

    7.6.5 查询字符串

    说明:url后?后面为查询字符串,=左边为键,=右边为值。多个键值对参数使用&连接。

    范例:http://www.liulongbin.top:3306/api/getbooks?id=1

    get请求携带参数的本质:无论是使用Jquery还是原生ajax,当需要携带参数的时候,本质上都是将参数以查询字符串的形式,追加到url地址的后面,发送到服务器。

    $.get('url',{name:'zs',age:20},function(){})
    // 等价于
    $.get('url?name=zs&age=20',function(){})
    
    • 1
    • 2
    • 3

    7.6.6 URL编码

    说明:URL地址中,值允许出现英文相关的字母、标点符号、数字,因此,在URL中不允许出现中文字符。如果携带的参数出现了中文或者URL本身有中文,则需要对其编码转义。

    原则:使用安全的字符串去表示那些不安全的字符,即用英文字符来表示非英文字符。

    范例

    image-20220813144921305

    注意:一个中文会被编码转义为三个百分号加字符,且浏览器默认会自动编码和解码。

    编码:使用浏览器提供的encodeURl() 可以编码

    解码:使用浏览器提供的decodeURl()可以进行解码

    <script>
            // 编码
            let encodestr = encodeURI('你我皆是黑马')
            console.log(encodestr);
            
            // 解码
            let decodestr = decodeURI('%E4%BD%A0%E6%88%91')
            console.log(decodestr);
        script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    7.6.7 xhr的post请求

    <script>
            // 1.创建xhr对象
            let xhr = new XMLHttpRequest()
            // 2.调用open
            xhr.open('POST','http://www.liulongbin.top:3006/api/addbook')
            // 3.设置Content-Type属性,这是一个固定写法
            xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
            // 4.调用send() 将数据以查询字符串的形式发送给服务器
            xhr.send('bookname=水浒传&author=施耐庵')
            // 5.监听onreadystatechange事件
            xhr.onreadystatechange = ()=>{
                if(xhr.readyState === 4 && xhr.status === 200) console.log(xhr.responseText);
            }
        script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    7.6.8 数据交换格式

    说明:数据交换格式,就是服务器端与客户端之间进行数据传输和交换的格式。前端领域用的数据交换格式为XML和json,最常用json。

    XML

    1. 和HTML一样是一种可扩展标记语言。
    2. 被设计用于传输和存储数据,是数据的载体。
    3. XML格式臃肿,体积大,传输效率低。
    4. 在JS中解析XML较为麻烦。

    Json

    1. json是javaScript Object Notation,即js对象表示法。
    2. json是js中对象和数组的字符串表示方法
    3. json是一种轻量级文本数据交换格式

    7.6.10 JSON的两种结构

    对象结构:如下所示,需要注意的是key必须为英文双引号,value必须是数字、字符串、布尔值、null、数组、对象六种数据类型之一。

    {
    	"name":"zs",
    	"age":20,
    	"address":["吃饭","睡觉"]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    数组结构:如下所示,需要注意数组中数据的类型可以是数字、字符串、布尔值、null、数组、对象六种数据类型之一。

    ["java","python","php"]
    [100,200,300]
    
    • 1
    • 2

    Json本质:Json是一个字符串


    7.6.11 JSON和JS对象

    两者的区别

    //这是一个对象
    var obj = {a:'hello',b:'world'}
    //这是一个json字符串
    var json = '{"a":"hello","b":"world"}'
    
    • 1
    • 2
    • 3
    • 4

    两者的互转

    <script>
            //json转object
            var jsonstr = '{"name":"ArimaMisaki","age":12}'
            var obj = JSON.parse(jsonstr)
            console.log(obj);
            //object转json
            var jsonstr2 = JSON.stringify(obj)
            console.log(jsonstr2);
        script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    序列化和反序列化:对象转字符串为序列化,字符串转对象为反序列化。


    7.7 XMLHttpRequest Level2

    7.7.1 新特性

    • 可以设置HTTP请求的时限
    • 可以使用FormData对象管理表单数据
    • 可以上传文件
    • 可以获得数据传输的进度信息

    7.7.2 设置HTTP请求时限

    //设置响应时间为3秒
    xhr.timeout = 3000
    //设置超时之后的处理函数
    xhr.ontimeout = ()=>{
    	console.log('请求超时!')
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    7.7.3 FormData对象管理表单数据

    <script>
            // 1.创建FormData实例
            var fd = new FormData()
            // 2.调用append函数,像fd中追加数据
            fd.append('uname','zs')
            fd.append('upwd','123456')
    
            var xhr = new XMLHttpRequest()
            xhr.open('POST','http://www.liulongbin.top:3306/api/formdata')
            xhr.send(fd)
    
            xhr.onreadystatechange = ()=>{
                if (xhr.readyState === 4 && xhr.status === 200) {
                    console.log(JSON.parse(xhr.responseText));
                }
            }
        script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    7.7.4 FormData对象管理表单数据

    <div>
            <form action="" id = "form1">
                <input type="text">
                <input type="text">
                <button type="submit">提交button>
            form>
        div>
        <script>
            var form = document.querySelector('#form1')
            // 1.创建FormData实例
            var fd = new FormData()
            // 2.监听表单元素的submit事件
            form.addEventListener('submit', (e) => {
                e.preventDefault()
                var fd = new FormData(form)
                var xhr = new XMLHttpRequest()
                xhr.open('POST', 'http://www.liulongbin.top:3306/api/formdata')
                xhr.send(fd)
    
                xhr.onreadystatechange = () => {
                    if (xhr.readyState === 4 && xhr.status === 200) {
                        console.log(JSON.parse(xhr.responseText));
                    }
                }
            })
        script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    7.7.5 上传文件

    新版XMLHTTPRequest对象,不仅可以发送文本信息,还可以上传文件。

    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>Documenttitle>
        <style>
            #box {
                background-color: pink;
                width: 60%;
                height: 300px;
                margin: 0 auto;
            }
        style>
    head>
    
    <body>
        
        <div id="box">
            
            <input type="file" id="file1">
            <br>
            
            <button id="btnUpload">上传文件button>
            <br>
            
            <img src="" alt="" id="img" width="800">
            <br>
        div>
    
        
        <script>
            // 1.获取文件上传按钮
            var btnUpload = document.getElementById('btnUpload')
            // 2.为按钮绑定单机事件处理函数
            btnUpload.addEventListener('click', () => {
                // 3.获取到用户选择的文件列表
                var files = document.querySelector("#file1").files
                if (files.length <= 0) return alert(`请选择待上传文件!`)
                console.log('用户选择了待上传的文件');
    
                // ======向FormData中追加文件======
                // 1.创建FormData对象
                var fd = new FormData()
                // 2.追加提交的文件
                fd.append('avatar',files[0])
                
                // ======使用xhr发起上传文件的请求======
                // 1.创建xhr对象
                var xhr = new XMLHttpRequest()
                // ======监听文件上传进度======
                xhr.upload.onprogress = (e)=>{
                    if(e.lengthComputable) {
                        var procentComplete = Math.ceil((e.loaded / e.total)*100)
                        console.log(procentComplete);
                    }
                }
                // 2.调用open函数指定请求方式和URL地址
                xhr.open('POST','http://www.liulongbin.top:3306/api/upload/avatar')
                // 3.发起请求
                xhr.send(fd)
    
                // ======监听onreadystatechange事件======
                xhr.onreadystatechange = ()=>{
                    if(xhr.readyState === 4 && xhr.status === 200){
                        var data = JSON.parse(xhr.responseText)
                        if (data.status === 200) {
                            document.querySelector('#img').src = 'http://www.liulongbin.top:3006'+data.url
                        }else{
                            console.log(data.message);
                        }
                    }
                }
    
                // ======显示文件的上传进度======
    
            })
    
        script>
    body>
    
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    7.8 axios

    7.8.1 Axios概述

    Axios是专注于网络数据请求的库。相比于原生的XMLHttpRequest对象,axios简单易用。


    7.8.2 Axios发起请求

    get格式:axios.get(‘url’,{params:{/参数/}}.then(callback))

    post格式:axios.post(‘url’,{params:{/参数/}}.then(callback))

    axios格式:axios({method:,url:,data:,params:}).then(callback)


    7.9 跨域和JSONP

    7.9.1 同源策略

    同源:如果两个页面的协议、域名和端口都相同,则两个页面具有相同的源。

    同源策略:浏览器提供了一个安全功能。其限制了同一个源加载的文档或脚本如何来与另一个源的资源进行交互,这是一个用于隔离潜在恶意文件的重要安全机制。

    跨域:非同源即为跨域

    同源策略的拦截原理

    image-20220813222938421

    跨域请求:可以使用JSONP和CORS来解决。但JSONP已经是上古神器了,而且不支持POST请求。CORS出现的较晚,目前为主流,支持GET和POST请求。


    7.9.2 JSONP原理

    由于浏览器同源策略的限制,网页中无法通过Ajax请求非同源的接口数据。但是script标签不受浏览器同源策略影响,故我们可以通过src属性,请求非同源的js脚本。

    主要原理是通过动态构建 script 标签来实现跨域请求,因为浏览器对 script 标签的引入没有跨域的访问限制 。通过在请求的 url 后指定一个回调函数,然后服务器在返回数据的时候,构建一个 json 数据的包装,这个包装就是回调函数,然后返回给前端,前端接收到数据后,因为请求的是脚本文件,所以会直接执行,这样我们先前定义好的回调函数就可以被调用,从而实现了跨域请求的处理。这种方式只能用于 get 请求。

    <script>
            var getdata = (data)=>{
                console.log("拿到了data数据:");
                console.log(data);
            }
        script>
    
        
        <script src = "./7_9.2.js?callback=getdata">script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    getdata({ name: 'ls', age: 30 })
    
    • 1

    7.10 防抖

    说明:防抖策略是当事件触发后,延迟n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时。

    应用:用户在输入框中输入一串字符时,可以通过防抖策略,只有在输入完成后,才执行查询的请求,这样可以有效减少请求次数,节约请求资源。

    原理:通过延时器来延时发送请求。当事件重新被触发或外界干扰,则清空延时器。


    7.11 节流

    说明:节流策略可以减少一段时间内时间的触发频率。可以理解为游戏的后摇。

    应用:鼠标连续不断地触发某事件,只在单位事件内触发一次

    节流阀:节流阀为空,则可以执行下次操作,不为空,表示不能执行下次操作。

    原理:通过节流阀判断事件是否可用,节流阀为空则执行事件,否则不执行。

    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>Documenttitle>
        <script src="./jquery.min.js">script>
        <style>
            body{
                margin:0;
                padding:0;
            }
            div{
                background-color: pink;
                width: 100px;
                height: 100px;
                position: absolute;
            }
        style>
    head>
    <body>
        <div id = "box">div>
    
        <script>
            $(()=>{
                // 获取盒子
                var movebox = $('#box')
                // 定义节流阀
                var timer = null
                $(document).on('mousemove',(e)=>{
                    // 如果节流阀为空则可以使用
                    if(timer) return 
                    timer = setTimeout(()=>{
                        movebox.css('left',e.pageX+'px').css('top',e.pageY+'px')
                        // 事件执行结束,节流阀置空
                        timer = null
                    },16)
                })
            })
        script>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
  • 相关阅读:
    Kotlin高仿微信-第21篇-个人信息-修改头像
    Linux学习之平均负载的概念和查看方法
    gRPC 提供接口文档
    解忧云SMS短信服务平台系统 短信发送系统 全解密完美版
    【动手学深度学习----注意力机制笔记】
    01-js书写方式、变量
    带你走进并发编程的世界
    Linux进程概念
    CDR2024版本免费Windows10包含免费激活码序列号
    安全狗又拿下一场重保胜战 第22届投洽会顺利谢幕
  • 原文地址:https://blog.csdn.net/chengyuhaomei520/article/details/126336649