AJAX 入门

AJAX 简介

  • AJAX全称为Asynchronous Javascript And XML,就是异步的 JS 和 XML。
  • 通过AJAX可以在浏览器中向服务器发送异步请求,最大的优势:页面无刷新获取数据
  • AJAX不是新的编程语言,而是一种将现有的标准组合在一起使用的新方式。
  • ajax也是属于原生js的http请求,可以说是一种特殊的http请求。

在以往提交表单一般是这样子

<body>
    <form action="https://www.baidu.com">
        用户名:<input type="text" name="username">
        密码:<input type="password" name="pad">
        <!-- button按钮会触发表单的提交 -->
        <button type="submit">登录</button>
    </form>
</body>

XML简介

  • XML:可扩展标记语言

  • XML:被设计用来 传输和存储数据

    比方说客户端(client)服务器(serve)前后台进行数据交互的时候,你想获取十条新闻,服务器就要给你十条新闻,那么关键是用的什么方式给你?

    于是服务器可以用XML这种字符串形式给你,XML本质就是字符串,不过有很多的规矩。

  • XML和HTML类似都是可扩展的标记语言,不同点:HTML中都是预定义标签XML中没有预定义标签全是自定义标签,用来表示一些数据

    例:用xml表示学生数据
    <student>
            <name>孙悟空</name>
            <age>18</age>
            <gender></gender>
    </student>
    可以发现体积较大了
  • 现在已被JSON取代

    例:用JSON表示学生数据
    {"name":"孙悟空","age":18,gender:"男"}

AJAX 的特点

AJAX 的优点

  1. 可以 **无需刷新页面与服务端进行通信 **获取数据
  2. 允许你根据用户事件来更新部分页面内容(例如淘宝鼠标移动到某个区域页面不刷新,但是部分区域内容更新)

AJAX 的缺点

  1. 没有浏览历史,不能回退
  2. 存在跨域问题(同源)–面试高频问题
  3. SEO不友好(爬虫获取不到信息)

AJAX 的使用

核心对象:XMLHttpRequest

AJAX的所有操作都是通过该对象进行的

搭建测试ajax的服务器

新建任意名的文件夹(最好不用数字开头,不能有大写字母)

接着使用 yarn init (或者 npm init)将当前项目文件夹变成符合npm规范的包

使用 yarn express 安装express

项目文件夹下新建一个serve.js用来当作服务器

// 1. 引入express
const express = require('express')

// 2.创建一个app实例对象
const app = express()

// 4.配置一个路由 (比如想响应get请求,就起一个 'test_get' 的名字 ,里面有两个对象,请求和响应)
app.get('/test_get',(request,response)=>{
    // 函数体
    response.send('hello_test_get!!!')
})

// 3.绑定监听    端口号(一般4位数字) + 回调(err是错误对象,如果没有错误对象就证明服务器开启成功)
app.listen(8080,(err)=> {
    if(!err) console.log('测试ajax请求的服务器开启成功了');
})

启动服务器(三种方式):node serve / nodemon .\serve.js / 使用第三方插件 Code Runner(右键 点击Run Code开启,快捷键 ctrl+alt+m 关闭服务)

浏览器地址栏输入查看:localhost:8080/test_get (浏览器默认是get请求)

ajax小试牛刀

ajax也是属于原生js的http请求,可以说是一种特殊的http请求。

项目根目录新建文件夹src,下新建 ‘1_ajax小试牛刀.html’文件,右键文件使用vscode的Live Server打开

//...
    <style>
        #content {
            width: 300px;
            height: 100px;
            border: 1px solid black;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <h3>该页面是测试:ajax小试牛刀</h3>
    <button id="btn">点我发送请求(原生js里的ajax的GET请求)</button>
    <div id="content"></div>
    <script type="text/javascript">
        // 获取按钮
        const btn = document.getElementById('btn')
        // 给按钮绑定监听
        btn.onclick = () => {
            // 发送ajax请求 有如下几步:
                // 1.创建xhr实例对象
                const xhr = new XMLHttpRequest()
                // 2.指定发送请求的:method、url
                xhr.open('GET','http://localhost:8080/test_get')
                // 3.发送请求
                xhr.send()
        }
    </script>
</body>
</html>

当点击按钮后控制台会报错,出现经典的跨域问题。(如果提示有…the serve responded with a status of 404 :5050/favicon.ico:1 (Not Found) 那么需要找一个ico图片放到项目文件根目录下即可)

解决跨域:

服务器中暴露静态资源

// 1.引入express
const express = require('express')

// 2.创建app实例对象
const app = express()

// 5.解决跨域:暴露静态资源
app.use(express.static(__dirname+'/src'))

// 4.配置一个路由 (比如想响应get请求,就起一个 'test_get' 的名字 ,里面有两个对象,请求和响应)
app.get('/test_get',(request,response)=>{
    // 函数体
    // 这里设置一句话,检查是否有人请求了
    console.log('有人请求test_get了');
    response.send('hello_test_get!!!')
})

// 3.监听
app.listen(8080,(err)=> {
    if(!err) {
        console.log('测试ajax请求的服务器开启成功了!测试地址如下');
        console.log('http://127.0.0.1:8080/1_ajax小试牛刀.html');
    }
})

在html文件中将发送请求的url改成 ‘ http://127.0.0.1:8080/test_get ‘,然后不使用vscode的Live Server打开,而是复制到浏览器使用serve.js的 ‘http://127.0.0.1:8080/1_ajax小试牛刀.html’ 地址,同时要看到请求是否成功需要借助xhr的状态state来将xhr.response的结果返回给div中

<h3>该页面是测试:ajax小试牛刀</h3>
    <button id="btn">点我发送请求(原生js里的ajax的GET请求)</button>
    <div id="content"></div>
    <script type="text/javascript">
        // 获取按钮
        const btn = document.getElementById('btn')
        const content = document.getElementById('content')
        // 给按钮绑定监听
        btn.onclick = () => {
            // 发送ajax请求 有如下几步:
                // 1.创建xhr实例对象
                const xhr = new XMLHttpRequest()
                xhr.onreadystatechange = ()=> {
                    if(xhr.readyState === 4) {
                        console.log(xhr.response);
                        content.innerHTML = `<h3>${xhr.response}</h3>`
                    }
                }
                // 2.指定发送请求的:method、url
                xhr.open('GET','http://127.0.0.1:8080/test_get')
                // 3.发送请求
                xhr.send()
        }
    </script>
</body>
</html>

xhr的5种状态(了解)

复制’1_ajax小试牛刀.html’文件,改名为‘2_xhr的5种状态.html’,其他步骤和1文件类似方法打开

注意服务器需要再写一行

//方便复制

// 4.监听
app.listen(8080,(err)=> {
    if(!err) {
        console.log('测试ajax请求的服务器开启成功了!测试地址如下');
        console.log('http://127.0.0.1:8080/1_ajax小试牛刀.html');
        console.log('http://127.0.0.1:8080/2_xhr的5种状态.html'); //方便复制
    }
})

页面html文件代码如下:

//...
<h3>该页面是测试:xhr的5种状态.html</h3>
    <button id="btn">点我发送请求(原生js里的ajax的GET请求)</button>
    <div id="content"></div>
    <script type="text/javascript">
        // 获取按钮
        const btn = document.getElementById('btn')
        const content = document.getElementById('content')
        // 给按钮绑定监听
        btn.onclick = () => {
            // 发送ajax请求 有如下几步:
                // 1.创建xhr实例对象
                const xhr = new XMLHttpRequest()

                // xhr实例对象在实例出来的那一刻就是0,随着请求变化不断变化
                /*
                    xhr内部有5种状态,5种状态值分别为:0、1、2、3、4
                    0:实例出来的那一刻就是0,初始状态
                    1:open已经调用,但是send没有调用,此时可以修改请求头内容
                    2:send已经调用了,已经无法修改请求头
                    3:已经回来部分数据,小的数据会在此阶段一次性接收完毕,较大的数据有待进一步接收,响应头回来了.
                    4:数据全部接收完毕
                   // getAllResponseHeaders获取所有响应头
                */
                xhr.onreadystatechange = ()=> {
                    if(xhr.readyState === 1) {
                        // xhr的请求头api setRequestHeader //配置请求头:请求头中增加 key:value
                        // xhr.setRequestHeader('demo',123)
                        // alert('@')
                    }
                    if(xhr.readyState === 2) {
                        // xhr.setRequestHeader('demo',123) //配置请求头:报错 因为2阶段无法修改请求头了
                    }
                    if(xhr.readyState === 3) {
                        console.log('3时接收到的数据',xhr.response); 
                        console.log('3时接收到的响应头',xhr.getAllResponseHeaders()); 
                    }
                    if(xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)) {
                        console.log(xhr.response);
                        content.innerHTML = `<h3>${xhr.response}</h3>`
                    }
                }
                // 2.指定发送请求的:method、url
                xhr.open('GET','http://127.0.0.1:8080/test_get')
                // 3.发送请求
                xhr.send()
        }
    </script>
</body>
</html>

注意:请求成功的判断最好详细一点

if(xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)){

//......

}

ajax的get请求(掌握)

复制’2_xhr的5种状态.html’文件,改名为‘3_ajax_get请求.html’,其他步骤和2文件类似方法打开

页面html文件代码:

//...
<h3>该页面是测试:ajax_get请求.html</h3>
    <button id="btn">点我发送请求(原生js里的ajax的GET请求)</button>
    <div id="content"></div>
    <script type="text/javascript">
        // 获取按钮
        const btn = document.getElementById('btn')
        const content = document.getElementById('content')

        // 给按钮绑定监听
        btn.onclick = () => {
            // 发送ajax请求 有如下几步:
                // 1.创建xhr实例对象
                const xhr = new XMLHttpRequest()
                // 绑定监听
                xhr.onreadystatechange = ()=> {
                    if(xhr.readyState === 4) {
                        if(xhr.status >= 200 && xhr.status < 300) {
                            console.log(xhr.response);
                            content.innerHTML = `<h3>${xhr.response}</h3>`
                        }
                    }
                }
                // 2.指定发送请求的:method、url、参数
                /*
                    1.形如:key=value&key=value 就是query参数的urlencoded编码形式
                    2.形如:/xx/xxx/老刘/18 		就是params参数
                */
                // xhr.open('GET','http://127.0.0.1:8080/test_get?name=老刘&age=18')   //携带query参数
                xhr.open('GET','http://127.0.0.1:8080/test_get2/老刘/18')            //携带params参数
                // 3.发送请求
                xhr.send()
        }
    </script>
</body>
</html>

服务器代码

// 1.引入express
const express = require('express')

// 2.创建app实例对象
const app = express()

// 5.解决跨域:暴露静态资源
app.use(express.static(__dirname+'/src'))

// 3.响应GET请求 -query参数
app.get('/test_get',(request,response)=>{
    // 函数体
    // 这里设置一句话,检查是否有人请求了
    console.log('有人请求test_get了--携带的query参数是:',request.query);
    response.send('hello_test_get!!!')
})

// 3.响应GET请求 -params参数
app.get('/test_get2/:name/:age',(request,response)=>{
    // 函数体
    // 这里设置一句话,检查是否有人请求了
    console.log('有人请求test_get2了--携带的params参数是:',request.params);
    response.send('hello_test_get2!!!')
})

// 4.监听
app.listen(8080,(err)=> {
    if(!err) {
        console.log('测试ajax请求的服务器开启成功了!测试地址如下');
        console.log('http://127.0.0.1:8080/3_ajax_get请求.html');
    }
})

ajax的post请求(掌握)

复制’3_ajax_get请求.html’文件,改名为‘4_ajax_post请求.html’,其他步骤和3文件类似方法打开

页面html文件代码如下:

//...
<h3>该页面是测试:ajax_post请求.html</h3>
    <button id="btn">点我发送请求(原生js里的ajax的POST请求)</button>
    <div id="content"></div>

    <script type="text/javascript">
        // 获取按钮
        const btn = document.getElementById('btn')
        const content = document.getElementById('content')

        // 给按钮绑定监听
        btn.onclick = () => {
            // 发送ajax请求 有如下几步:
                // 1.创建xhr实例对象
                const xhr = new XMLHttpRequest()

                // 绑定监听
                xhr.onreadystatechange = ()=> {
                    if(xhr.readyState === 4) {
                        if(xhr.status >= 200 && xhr.status < 300) {
                            console.log(xhr.response);
                            content.innerHTML = `<h3>${xhr.response}</h3>`
                        }
                    }
                }
                // 2.指定发送请求的:method、url、参数
                
                // post 可以携带query params参数 和请求体body(body有两种编码形式 urlencoded 、json)
                // xhr.open('POST','http://127.0.0.1:8080/test_post?name=tom&age=18')       // 携带query参数      
                xhr.open('POST','http://127.0.0.1:8080/test_post')                          // 不带参数   (参数放到下方请求体发送)      
                // 追加响应头用于标识携带请求体参数的编码形式 --urlencoded 形式 
                // xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded')  
                
                // 追加响应头用于标识携带请求体参数的编码形式 --json 形式
                xhr.setRequestHeader('Content-type','application/json')                   
                
                // 3.发送请求 (post请求可以携带query、params参数,但是一般不带,而是放到请求体里面) 请求体里用urlencoded / json编码形式
                const person = {
                    name:'老刘',
                    age:18
                }
                // xhr.send('name=老刘&age=18')     //携带 urlencoded 编码形式的请求体参数
                xhr.send(JSON.stringify(person))   //携带 json 编码形式的请求体参数 --使用JSON.stringify()方法将对象转为json格式
        }   
    </script>
</body>
</html>

服务器代码:

// 1.引入express
const express = require('express')

// 2.创建app实例对象
const app = express()
// 使用中间件express.urlencoded() 解析urlencoded编码形式的请求体参数
// 简单说:如果想用express框架接收post请求所携带的请求体参数,需要借助中间件 express.urlencoded
app.use(express.urlencoded({extended:true}))
// 使用中间件解析json编码形式的请求体参数
app.use(express.json())

// 5.解决跨域:暴露静态资源
app.use(express.static(__dirname+'/src'))

// 3.响应POST请求 -请求体参数(后端需要占位符) 
app.post('/test_post',(request,response)=>{
    console.log('有人请求test_post了,携带的请求体参数是:',request.body);
    response.send('hello_test_post!!!')
})

// 4.监听
app.listen(8080,(err)=> {
    if(!err) {
        console.log('测试ajax请求的服务器开启成功了!测试地址如下');
        console.log('http://127.0.0.1:8080/4_ajax_post请求.html');
    }
})

git相关操作

克隆仓库

例:在一个名为student_ajax文件夹右键打开Git Bush Here 使用 git clone ‘仓库地址’,拿到公司代码 (此时克隆下来的文件里面有.git文件夹,该文件夹里边配置的是克隆的远程仓库的地址)

本地环境yarn 下载依赖

git add * –将文件放到暂存区

git commit -m ‘描述干啥事情了’ –对刚才的操作进行注释

目前文件仅在本地git仓库里,还需要推送到线上仓库

git push origin master

当线上主线文件更新了之后,支线需要下载主线的最新版本,使用

git pull origin master 拉取代码

那么这样拉取后本地文件将被线上文件覆盖,所以建议单独新建个文件夹存放克隆的的仓库,每次线上有更新就拉取代码到本地该文件夹里

借助vscode去操作git

ajax解析json数据(了解)

推荐安装FeHelper(前端助手)插件到浏览器。

新建’5_ajax_解析json数据.html’

页面html文件代码如下:

// ...
    <h3>该页面是:解析json数据.html</h3>
    <button id="btn">点我发送请求(原生js里的ajax的get请求)</button>
    <div id="content"></div>

    <script type="text/javascript">
        const btn = document.getElementById('btn')
        const content = document.getElementById('content')
        btn.onclick = ()=> {
            // 1.实例化xhr对象
            const xhr = new XMLHttpRequest()
            // 2.绑定监听
            xhr.onreadystatechange = ()=> {
                if(xhr.readyState === 4) {
                    if(xhr.status >= 200 & xhr.status < 300) {
                        console.log(xhr.response);
                       // 将json格式解析成对象 然后使用解构赋值
                       // 因为不知道后端那边的数据格式,假如后端是对象,直接使用JSON.parse()就会报错 在3下方使用 xhr.responseType = 'json'解决
                        const {name,age,sex} = (xhr.response)
                        content.innerHTML = (`
                            <ul>
                                    <li>姓名:${name}</li>    
                                    <li>年龄:${age}</li>    
                                    <li>性别:${sex}</li>    
                                </ul>
                        `)
                    }
                }
            }
            // 3.指定发送请求的:method、url (这里暂时不加参数)
            xhr.open('GET','http://127.0.0.1:8080/get_person')
            // 因为不知道后端那边的数据格式, responseType用于指定返回数据的格式
            xhr.responseType = 'json'
            // 4.发送请求
            xhr.send()
        } 
    </script>
</body>
</html>

服务器代码:

// 1.引入express
const express = require('express')

// 2.创建app实例对象
const app = express()
// 使用中间件express.urlencoded() 解析urlencoded编码形式的请求体参数
// 简单说:如果想用express框架接收post请求所携带的请求体参数,需要借助中间件 express.urlencoded
app.use(express.urlencoded({extended:true}))
// 使用中间件解析json编码形式的请求体参数
app.use(express.json())

// 5.解决跨域:暴露静态资源
app.use(express.static(__dirname+'/src'))

// 3.响应get请求 
app.get('/get_person',(request,response)=>{
    console.log('有人请求get_person了');
     const person = {name:'老刘',age:'18',sex:'女'}
     response.send(JSON.stringify(person))
    //response.send('hello')
})

// 4.监听
app.listen(8080,(err)=> {
    if(!err) {
        console.log('测试ajax请求的服务器开启成功了!测试地址如下');
        console.log('http://127.0.0.1:8080/5_ajax_解析json数据.html');
    }
})

连续解构赋值

<!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>
        // 例子 想要得到c ??
        
        let obj = {
            a:1,
            b:{
                c:2,
            },
        }
		
		// 方法1 --标准的解构赋值得到 2
        // const {c} = (obj.b)
        // console.log(c);   /

        // 方法2 --连续解构赋值
        // const {b:{c}} = obj
        // console.log(c); // 得到 2

        // 新需求不喜欢用c名称,改为value 
        //  连续解构赋值 + 重命名
        const {b:{c:value}} = obj
        console.log(value); // 得到 2

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

处理IE浏览器-get请求缓存问题(了解)

协商缓存 –一般浏览器中,当页面第一次向服务器发起请求后,服务器返回数据给页面,在控制台终端Network中可以看到请求的状态码是200。此时,当服务器中的数据更新(改变)后,页面第二次向服务器发起请求,页面数据随之变化,但是第三次点击按钮,控制台终端的状态码显示304(因为这次和上一次请求的地址都没有变化,浏览器会认为返回的信息依然和之前一样,但还是会问一问服务器,数据是否有变化,此时若服务器返回没有变化,那么浏览器会从自己缓存中读取数据,304就出现了) 所以也叫协商缓存。

但是!IE浏览器,在遇到该问题时候非常武断,直接不问服务器直接拿之前的数据给页面用。–IE的强缓存机制

解决方式:加上了时间戳参数,忽悠IE浏览器每次的请求地址不同。即使服务器接口没有参数,默认不处理请求来的参数。

// 指定发送请求的:method、url (这里暂时不加参数)
xhr.open('GET','http://127.0.0.1:8080/get_person?t='+Date.now())

请求异常与超时的处理

新建’ 7_ajax请求的异常与超时处理.html ‘文件

页面html文件代码如下:


 <h3>该页面是:ajax请求的异常与超时处理.html</h3>
    <button id="btn">点我发送请求(原生js里的ajax的get请求)</button>
    <div id="content"></div>
    <script type="text/javascript">
        const btn = document.getElementById('btn')
        const content = document.getElementById('content')
        btn.onclick = function(){
            // 1.实例化xhr对象
            const xhr = new XMLHttpRequest()
            // 2.绑定监听
            xhr.onreadystatechange = function(){
                if(xhr.readyState === 4) {
                    if(xhr.status >= 200 & xhr.status < 300) {
                        console.log(xhr.response);
                        const {name,age,sex} = xhr.response
                        content.innerHTML = (`
                            <ul>
                                <li>姓名:${name}</li>    
                                <li>年龄:${age}</li>    
                                <li>性别:${sex}</li>    
                            </ul>
                        `)
                    }
                }
            }
            // 3.指定发送请求的:method、url (这里暂时不加参数)
            xhr.open('GET','http://127.0.0.1:8080/get_person_delay?t='+Date.now())
            // 因为不知道后端那边的数据格式, responseType用于指定返回数据的格式
            xhr.responseType = 'json'

            // 网络不好的处理 --配置出错的回调
            xhr.onerror = ()=>{
                alert('请求出错了,可能当前网络不稳定请稍后再试');
            }
            //超时时间 --这个api意思是仅请求2秒,2秒后没有数据返回就取消请求
            xhr.timeout = 2000
            // 超时后的回调
            xhr.ontimeout = () => {
                alert('网速不给力,请切换网络');
            } 
            // 4.发送请求
            xhr.send()
        } 
    </script>
</body>
</html>

服务器代码

// 3.响应get请求 --延时响应
app.get('/get_person_delay',(request,response)=>{
    console.log('有人请求get_person了');
    const person = {name:'海峰6',age:'18',sex:'女'}
    // 3秒后返回数据
    setTimeout(()=>{
        response.send(JSON.stringify(person))
    },3000)
})

ajax取消请求(掌握)

新建 ’ 8_ajax取消请求.html ‘

页面html文件代码如下:

// ...
    <h3>该页面是:ajax取消请求.html</h3>
    <button id="btn">点我发送请求(原生js里的ajax的get请求)</button>
    <button id="btn2">取消请求</button>
    <div id="content"></div>
    <script type="text/javascript">
        const btn = document.getElementById('btn')
        const btn2 = document.getElementById('btn2')
        const content = document.getElementById('content')
        let xhr
        btn.onclick = function(){
            // 1.实例化xhr对象
            xhr = new XMLHttpRequest()
            // 2.绑定监听
            xhr.onreadystatechange = function(){
                if(xhr.readyState === 4) {
                    if(xhr.status >= 200 & xhr.status < 300) {
                        console.log(xhr.response);
                        const {name,age,sex} = xhr.response
                        content.innerHTML = (`
                            <ul>
                                <li>姓名:${name}</li>    
                                <li>年龄:${age}</li>    
                                <li>性别:${sex}</li>    
                            </ul>
                        `)
                    }
                }
            }
            // 3.指定发送请求的:method、url (这里暂时不加参数)
            xhr.open('GET','http://127.0.0.1:8080/get_person_delay?t='+Date.now())
            // 因为不知道后端那边的数据格式, responseType用于指定返回数据的格式
            xhr.responseType = 'json'

            // 网络不好的处理 --配置出错的回调
            xhr.onerror = ()=>{
                alert('请求出错了,可能当前网络不稳定请稍后再试');
            }

            //超时时间 --这个api意思是仅请求2秒,2秒后没有数据返回就取消请求
            xhr.timeout = 2000
            // 超时后的回调
            xhr.ontimeout = () => {
                alert('网速不给力,请切换网络');
            } 

            // 4.发送请求
            xhr.send()
            // xhr.abort() //这里只要发送请求的次数多还是有概率会取消不了

        } 
			//点击第二个按钮后触发取消请求
        btn2.onclick = function() {
             xhr.abort()
        }
    </script>
</body>
</html>

服务端代码:

// 3.响应get请求
app.get('/get_person_delay',(request,response)=>{
    console.log('有人请求get_person了');
    const person = {name:'海峰6',age:'18',sex:'女'}
    response.send(JSON.stringify(person))

如何避免用户发送重复请求(掌握)

新建 ’ 9_避免多次重复请求.html ‘

页面html文件代码如下:

思路:点击按钮前,定义一个isLoading变量

代码执行顺序:当第一次点击按钮后,先判断isLoading变量,为假就取消。然后实例化xhr,指定发送请求的格式参数,发送请求,接着关键一点就是将isLoading变量变为真,(这样当下次点击按钮时候,isLoading变量为真就会取消下次的请求)然后判断服务器那边的状态码将isLoading变量变为假。

// ...
    <h3>该页面是:避免多次重复请求.html</h3>
    <button id="btn">点我发送请求(原生js里的ajax的get请求)</button>
    <div id="content"></div>

    <script type="text/javascript">
        const btn = document.getElementById('btn')
        const content = document.getElementById('content')
        let xhr
        // 声明一个变量,假如它为真代表正在发送请求中,就可以在之后取消请求
        let isLoading
        // 每次点击按钮 xhr将被重新实例化,即每次都被替换成新的xhr对象
        btn.onclick = function(){
                // 这里代表第一次点击按钮 由于isLoading为undefin 所以不会取消请求
                if(isLoading) xhr.abort()

                // 1.实例化xhr对象
                xhr = new XMLHttpRequest()
                // 4. 这是判断服务器那边的状态码 (绑定监听)
                xhr.onreadystatechange = function(){
                    if(xhr.readyState === 4) {
                        if(xhr.status >= 200 & xhr.status < 300) {
                            isloading = false;
                            console.log(xhr.response);
                            const {name,age,sex} = xhr.response
                            content.innerHTML = (`
                                <ul>
                                    <li>姓名:${name}</li>    
                                    <li>年龄:${age}</li>    
                                    <li>性别:${sex}</li>    
                                </ul>
                            `)
                        }
                    }
                }
                // 2.指定发送请求的:method、url (这里暂时不加参数)
                xhr.open('GET','http://127.0.0.1:8080/get_person_delay')
                // 因为不知道后端那边的数据格式, responseType用于指定返回数据的格式
                xhr.responseType = 'json'
                // 3.发送请求
                xhr.send()
                isLoading = true
              
        } 

    </script>
</body>

</html>

服务端代码:

// 3.响应get请求 --延时响应
app.get('/get_person_delay',(request,response)=>{
    console.log('有人请求get_person了');
    const person = {name:'海峰6',age:'18',sex:'女'}
    // 3秒后返回数据
    setTimeout(()=>{
        response.send(JSON.stringify(person))
    },3000)

})

jQuery封装的ajax(了解)

新建 ‘10_jquery封装的ajax.html’

打开浏览器 –下载好jquerymini.js放到文件中引入

页面html文件代码如下:

// ...
<script src="./js/jquery.min.js"></script>
</head>
<body>
    <h3>该页面是:jQuery封装的ajax.html</h3>
    <button id="btn1">点我发送请求(jQuery-ajax-get</button>
    <button id="btn2">点我发送请求(jQuery-ajax-post)</button>
    <div id="content"></div>
    <script type="text/javascript">
        const btn1 = $('#btn1')
        const btn2 = $('#btn2')
        const content = $('#content')

        btn1.click(() => {
            // 使用jQyuery发送get请求 --完整版写法
            $.ajax({
                url: 'http://127.0.0.1:8080/test_jquery_get', //请求地址
                method: 'GET',                                //请求方式 --不写的话默认也是get
                data: { school: '麻省理工学院' },              //携带的数据
                dataType: 'json',                             //配置响应数据格式 --将服务器拿到的json数据类型转换成对象
                timeout:'2000',                               //指定超时的时间
                // 成功的回调函数
                success: (result, responseText, xhr) => {      //第二个参数 responseText --代表本次响应(成功与否)的文字 第三个参数是xhr
                    console.log(result, responseText, xhr);
                    content.append(`<div>汽车名:${result.name},价格:${result.price}</div>`)
                },
                // 失败的回调
                error: (xhr) => {                           //里边可以有 xhr参数
                    console.log('请求出错了');
                }
            })
            // 使用jQyuery发送get请求 携带query参数 --简版写法
            // 如果是params请求  /test_jquery_get/bilibili
            // $.get('http://127.0.0.1:8080/test_jquery_get',{school: '麻省理工学院' },(data)=>{
            //     console.log(data);
            // },'json')
        
        })
        btn2.click(() => {
            // 使用jQyuery发送 post请求 --完整版写法
            $.ajax({
                url: 'http://127.0.0.1:8080/test_jquery_post', //请求地址
                method: 'POST',                                //请求方式 --不写的话默认也是get
                data: { school: '麻省理工学院' },              //携带的数据
                dataType: 'json',                             //配置响应数据格式 --将服务器拿到的json数据类型转换成对象
                timeout:'2000',                               //指定超时的时间
                // 成功的回调函数
                success: (result, responseText, xhr) => {      //第二个参数 responseText --代表本次响应(成功与否)的文字 第三个参数是xhr
                    console.log(result, responseText, xhr);
                    content.append(`<div>汽车名:${result.name},价格:${result.price}</div>`)
                },
                // 失败的回调
                error: (xhr) => {                           //里边可以有 xhr参数
                    console.log('请求出错了');
                }
            })
            // 使用jQyuery发送post请求 携带query参数 --简版写法
            // 如果是params请求  /test_jquery_post/bilibili
            //$.post('http://127.0.0.1:8080/test_jquery_post',{school: '麻省理工学院' },(data)=>{
                //console.log(data);
                //content.append(`<div>汽车名:${data.name},价格:${data.price}</div>`)
            //},'json')
        })
    </script>
</body>

</html>

服务端代码

// 响应GET请求 --为jQuery准备: 接收query参数  如果是params参数 --'/test_jquery_get/:school' console.log('有人请求test_jquery_get了',request.params);
app.get('/test_jquery_get',(request,response)=>{
    console.log('有人请求test_jquery_get了',request.query);
    const car= {name:'马自达.阿特兹',price:'25w'}
    response.send(JSON.stringify(car))

})

// 响应POST请求 --为jQuery准备: 响应体:request.body
app.post('/test_jquery_post',(request,response)=>{
    console.log('有人请求test_jquery_post了',request.body);
    const car= {name:'马自达.阿特兹',price:'25w'}
    response.send(JSON.stringify(car))

})

演示回调地狱(了解)

新建 ’ 11_演示回调地狱.html ‘

<!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>11_演示回调地狱.html</title>
    <script src="./js/jquery.min.js"></script>
</head>

<body>
    <h3>该页面是:演示回调地狱.html(看代码)</h3>
    <h3>需求:点击按钮1之后发送一条请求,成功后发第二条,第二条成功后再发第三条,</h3>
    <button id="btn1">点我发送请求(jQuery-ajax-get</button>

    <script type="text/javascript">
        const btn1 = $('#btn1')
        btn1.click(() => {
            // 使用jQyuery发送get请求 携带query参数 --简版写法
            $.get('http://127.0.0.1:8080/test_jquery_get',{school: '麻省理工学院' },(data)=>{
                console.log(data);
                        $.get('http://127.0.0.1:8080/test_jquery_get',{school: '麻省理工学院' },(data)=>{
                        console.log(data);
                                    $.get('http://127.0.0.1:8080/test_jquery_get',{school: '麻省理工学院' },(data)=>{
                                        console.log(data);
                
                                    },'json')
                        },'json')
            },'json')
        
        })

    </script>
</body>

</html>

跨域问题与同源策略总结:

为什么有跨域问题?

  • 原因是浏览器为了安全,而采用的同源策略。

    浏览器中有一个ajax引擎,只要xhr请求必须走ajax引擎,ajax引擎听一个叫同源策略的话,当前后端的端口不同时,浏览器阻止页面收到请求。

什么是同源策略?

  • 同源策略是由Netscape(网景公司)提出的一个著名安全策略,现在所有支持JavaScript的浏览器都会使用这个策略。

  • Web是构建同源策略协议基础之上的,浏览器只是针对同源策略的一种实现。

  • 所谓同源是指:协议、域名(IP),端口必须要完全相同。

​ 即:协议、域名(IP)、端口都相同,才能算是在同一个域里

备注:规则举例如下(假设已有网站地址为:http://study.cn 不写端口号时,http会使用默认的)

请求地址:												形式					 结果	
http://study.cn/test/a.html						协议、域名、端口均相同		   	   成功
http://study.cn/user/a.html						协议、域名、端口均相同			   成功
http://a.study.cn/user/a.html					域名不同			   		   失败
http://study.cn:8080/tset/a.html				域名不同			   		   失败
http://study.cn/user/a.html						协议、域名、端口均相同			   成功
https://study.cn/tset/a.html					协议不同			   		   失败

没有同源策略的危险场景:

危险场景:

​ 有一天,你刚睡醒,收到一封邮件,说你的银行账号有风险,赶紧点进www.yinghang.com改密码。你着急的赶紧点击进去,还是熟悉的银行登陆界面,你果断输入账号密码后,还没准备看里边的余额,睡眼朦胧的你想起了平时访问的银行网站是 www.yinhang.com,不是现在访问这个。随后你收到短信,钱没了,这个钓鱼网站做了什么呢? 大概是如下思路:

新建 ’ 12_演示没有同源策略的危险场景.html ‘

<iframe id="baidu" src="https://www.baidu.com"></iframe>
<script type="text/javascript">
      const iframe = window.frames['baidu']
      const inputNode = iframe.document.getElementById('输入铭感信息的input的id')
      console.log(inputNode.value);
</script>

非同源受到哪些限制?

  • Cookie不能读取;

  • DOM无法获得;

  • Ajax请求不能获取数据;

JSONP解决跨域(掌握)

JSONP是什么

JSONP (JSON with Padding),是一个非官方的跨域解决方案,纯粹凭借程序员的聪明才智开发出来,只支持get请求

同源策略限制了xhr、Cookie、DOM,但是在网页有一些标签具有跨域能力,比如:img, link, iframe, scriptJ。SONP就是利用script标签的跨域能力来发送请求的

新建文件‘13_jsonp解决跨域.html’

代码如下:

一切的前提是定义了demo(),当点击按钮后创建一个script节点并且src属性为请求的地址,将节点放入页面,最后在给window添加demo方法。

解决的原理是绕开了xhr,借助script标签发请求不收同源策略的限制。有一种前端定义函数,后端调用函数的感觉。

<body>
    <!-- 这里的jquery可以发送跨域请求 --因为是在script的src下支持跨域 -->
    <button id="btn">点我获取数据</button>
    <script>
        const btn = document.getElementById('btn')
        btn.onclick = () => {
            // 1.创建script节点
            const scriptNode = document.createElement('script')
            // 2.给节点指定src属性(请求地址)
            scriptNode.src = 'http://localhost:8080/test_jsonp'
            // 3.将节点放入页面
            document.body.appendChild(scriptNode)
            // 4.准备好一个函数
            window.demo = (a) => {
                console.log(a);
            }
        }
    </script>
</body>
</html>

服务端代码:

// 响应get请求 --为jsonp跨域准备
app.get('/test_jsonp',(request,response)=>{
    const person = {name:'tpm',age:18}
    // 想将一个对象变成字符有两种方式:
    // 1、toString() 方法         但是人类一般接受不了 --demo([object object])
    // 2、JSON.stringify() 方法
    response.send(`demo(${JSON.stringify(person)})`)
})

完善JSONP

优化:

1、 假如前端的函数名更改了,后端也要更改一次就很麻烦 –需要优化写法

2、前边每次点击按钮都会动态创建了一个script标签 –需要移除已经使用过的script节点

页面代码:

<body>
    <!-- 这里的jquery可以发送跨域请求 --因为是在script的src下支持跨域 -->
    <h3>当前页面一定不要用服务器去打开,因为要制造跨域问题,用jsonp解决问题</h3>
    <button id="btn">点我获取数据</button>
    <script>
        const btn = document.getElementById('btn')
        btn.onclick = () => {
            // 1.创建script节点
            const scriptNode = document.createElement('script')
            // 2.给节点指定src属性(请求地址) 这里的key使用callback命名是因为前端请求,后端返回的数据是一段可以执行的js代码,这段代码触发了peiqi函数的调用
            scriptNode.src = 'http://localhost:8080/test_jsonp?callback=peiqi' 
            // 3.将节点放入页面
            document.body.appendChild(scriptNode)
            // 4.准备好一个函数
            window.peiqi = (a) => {
                console.log(a);
            }
            // 5.(优化)移除已经使用过的script节点
            document.body.removeChild(scriptNode)
        }
    </script>
</body>
</html>

服务端代码:

// 响应get请求 --为jsonp跨域准备
app.get('/test_jsonp',(request,response)=>{
    const {callback} = request.query
    const person = [{name:'tpm',age:18},{name:'jery',age:'16'}]
    // 这样写的优势是前端的函数名随便更改,后端这里不需要改
    response.send(`${callback}(${JSON.stringify(person)})`) 
})

JSONP面试问题:

JSON 和 JSONP有关系吗?

有一定关系,但是他俩是两回事儿。JSON是一种存储和交互的一种格式,JSONP是后端一种解决跨域的方式。他俩的关系是因为后端返回数据的时候,必须将数据变为JSON字符串。

用JSONP去解决跨域也是用到了xhr对吗?

不对,JSONP解决跨域根本没用到xhr

JSONP解决跨域有什么缺点?

后端需要配合前端去拿到callback函数名,后端需要配合前端将数据变成JSON格式,后端需要配合前端写成函数调用的形式,简单来说前后端都挺麻烦。

jQuery封装的JSONP(了解)

用jQuery去发送一个JSONP请求

<!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>14_jQuery封装的JSONP.html</title>
    <script src="./js/jquery.min.js"></script>
</head>
<body>
    <h3>当前页面一定不要用服务器去打开,因为要制造跨域问题,用jQuery封装的JSONP解决</h3>
    <button id="btn">点我获取数据</button>
    <script>
        const btn = $('#btn')
        btn.click(()=>{
            $.getJSON('http://localhost:8080/test_jsonp?callback=?',{},(data)=>{
                console.log(data);
            })
        })
    </script>
</body>
</html>

CORS (后端技术)解决跨域(掌握)

CORS 是什么?

​ CORS (Cross-Origin Resource Sharing), 跨域资源共享。CORS 是官方的跨域解决方案,它的特点是不需要在客户端做任何特殊的操作,完全在服务器中进行处理,支持 get 和 post 等请求。跨域资源共享标准新增了一组 HTTP 首部字段(响应头),允许服务器声明哪些源站通过浏览器有权限访问哪些资源

CORS 怎么工作的?

​ CORS 是通过设置一个响应头来告诉浏览器,该请求允许跨域,浏览器收到该响应以后就会对响应放行。

CORS 的使用

新建 ‘ 15_测试cors解决跨域.html ’文件

代码如下:

// ...
    <h3>当前页面一定不要用服务器去打开,因为要制造跨域问题,测试cors解决跨域</h3>
    <button id="btn">点我获取数据</button>
    <script type="text/javascript">
        // 获取按钮
        const btn = document.getElementById('btn')
        const content = document.getElementById('content')

        // 给按钮绑定监听
        btn.onclick = () => {
            // 发送ajax请求 有如下几步:
                // 1.创建xhr实例对象
                const xhr = new XMLHttpRequest()
                xhr.onreadystatechange = ()=> {
                    if(xhr.readyState === 4) {
                        if(xhr.status >= 200 & xhr.status < 300) {
                            console.log(xhr.response);
                            console.log(xhr.getAllResponseHeaders());
                        }
                    }
                }
                // 2.指定发送请求的:method、url
                xhr.open('GET','http://127.0.0.1:8080/test_get')
                // 3.发送请求
                xhr.send()
        }

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

​ 主要是服务端的设置:

// 3.响应GET请求 -可以接收query 参数   request,response --请求、响应对象
app.get('/test_get',(request,response)=>{
    console.log('有人请求test_get了--携带的query参数是:',request.query);
    response.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:5500') // 只允许这个 http://127.0.0.1:5500 网站进行跨域请求
    response.setHeader('Access-Control-Expose-Headers','*') // 把所有的响应头都交给跨域的网站
    // response.setHeader('Access-Control-Allow-Origin','*') // '*' 任何网站都可以来这个接口拿数据
    // 这里设置一句话,检查是否有人请求了
    response.send('hello_test_get!!!')
})

发送 put 请求

新建文件 ‘16_ajax_put请求.html’

文件代码为:

// ...
    <h3>该页面是测试:ajax_put请求.html</h3>
    <button id="btn">点我发送请求(原生js里的ajax的PUT请求)</button>
    <div id="content"></div>
    <script type="text/javascript">
        // 获取按钮
        const btn = document.getElementById('btn')
        const content = document.getElementById('content')
        // 给按钮绑定监听
        btn.onclick = () => {
            // 发送ajax请求 有如下几步:
                // 1.创建xhr实例对象
                const xhr = new XMLHttpRequest()
                // 绑定监听
                xhr.onreadystatechange = ()=> {
                    if(xhr.readyState === 4) {
                        if(xhr.status >= 200 && xhr.status < 300) {
                            console.log(xhr.response);
                            content.innerHTML = `<h3>${xhr.response}</h3>`
                        }
                    }
                }
                xhr.open('PUT','http://127.0.0.1:8080/test_put')           
                // 3.发送请求
                xhr.send()
        }
    </script>
</body>
</html>

服务端代码:

// 响应 put请求
app.put('/test_put',(request,response) => {
    response.setHeader('Access-Control-Expose-Headers','*') // 把所有的响应头都交给跨域的网站
    response.setHeader('Access-Control-Allow-Origin','*')   // '*' 任何网站都可以来这个接口拿数据
    response.send('hello_tset_put')
})

假如此时使用 open with live serve打开文件,将会显示跨域问题,即仅仅如上设置服务端是没有解决 put请求的跨域问题 –(预请求失败)

因为get、put属于http里的简单请求,不存在嗅探请求(也叫预请求 –在请求真正发出之前要进行一个预请求的动作 预解析 –在代码真正执行之前有人扫了一遍代码),put和delete有预请求这种嗅探请求。同时复杂请求可以检查服务器的性能,使用HTTP的OPTIONS方法,因此目前服务端没有写这个方法就会失败。所以可以在服务端的put请求之前再写一个options请求

// 预请求的方式
app.options('/test_put',(request,response)=>{
    response.setHeader('Access-Control-Allow-Origin','*')   // '*' 任何网站都可以来这个接口拿数据
    response.setHeader('Access-Control-Expose-Headers','*') // 把所有的响应头都交给跨域的网站
    response.setHeader('Access-Control-Allow-Methods','*')   // 预请求 这里代表哪些请求可以跨域 '*'代表任何请求都可以
    response.send('我性能很好')
})

// 响应 put请求
app.put('/test_put',(request,response) => {
    response.setHeader('Access-Control-Expose-Headers','*') // 把所有的响应头都交给跨域的网站
    response.setHeader('Access-Control-Allow-Origin','*')   // '*' 任何网站都可以来这个接口拿数据
    response.send('hello_tset_put')
})

总结:掌握的需要掌握,了解的需要了解