AJAX进阶

79 阅读5分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

5、jsonp的实现原理

jsonp是跨域的非官方解决方案,只支持get请求

5.1jsonp是如何工作的?

在网页有一些标签天生具有跨域能力,比如:img link iframe script。

JSONP就是利用script标签的跨域能力来发送请求的。

举个例子:比如引入axios的script标签,引入的是一个远程资源

5.2jsonp的使用

1、动态的创建一个script标签

var script=document. createElement("script");

2、设置script的src,设置回调函数

script.src="http://localhost:3000/testAJAX?calback=abc"; 

ps:使用jsonp返回代码给html文件需要返回完整的html代码,而不是简单的字符串

 response.send('hello jsonp-server');//会报错
 response.send('console.log("hello")');//不会报错

原因是浏览器引擎只能解析HTML的内容,所以服务端发送过来的内容必须是HTML语句

script.onload = function(){
//将body中的script标签删除
  document.body.removeChild(script)
}

5.3jsonp封装

    //封装jsonp函数
        function jsonp(option){
             //动态创建javascript标签
            var script = document.createElement('script');
            var fnName = 'myjsonp'+Math.random().toString().replace('.','');
            var params = '';
            for(var attr in option.data){
                params += '&'+attr + '=' + option.data[attr];
            }
            //success函数已经不是全局函数了,要想办法把它变成全局函数
            //window对象新建一个fn属性,这个属性是一个函数
            window[fnName] = option.success; //变量不能用点来引用
            //添加src属性
            script.src = option.url + '?callback='+fnName + params;
            //将script标签追加到页面中
            document.body.appendChild(script);
            script.onload = function(){
             document.body.removeChild(script);
           }
        }
var btn1 = document.getElementById('btn1')
btn1.onclick = function(){
 jsonp({
         url:"http://localhost:8000/better",
         success:function(data){
          console.log(123);
            },
          data:{
               name:'hgc',
               age:20
             }
        })
}

6、解决跨域问题的官方方案CORS

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

app.get('/server',(request,response)=>{
    //设置响应头允许跨域
    response.setHeader('access-control-allow-origin','*');
    response.send('hello ajax');
});

CORS的几种响应头:

1、access-control-allow-origin--设置允许跨域

Access-Control-Expose-Headers--让服务器把允许浏览器访问的头放入白名单,例如:

Access-Control-Expose-Headers:X-My-Custom-Header,X-Another-Custom-Header

否则浏览器只能访问一些最基本的响应头

2、Access-Control-Max-Age--指定了preflight请求的结果能够被缓存多久

Access-Control-Max-Age:<delta-seconds>

delta-seconds表示preflight请求的结果在多少秒内有效

3、Accese-control-A1low-Methods--首部字段用于预检请求的响应。其指明了实际请求所允许使用的HTTP方法。

Access-Control-Allow-Methods:<method>[,<method>]*//*表示任何请求方法都被允许

\

4、Access-Control-Allow-Headers--允许发送自定义响应头

Access-Control-Allow-Methods:<filename>[,<filename>]*//*表示允许用户发送任意请求头,可以是预定义的也可以是自定义的

7、ajax封装

发送一次请求代码过多,发送多次请求代码重复

解决方案:将代码封装到函数中,发请求时调用函数即可

        ajax({
            type:'get',
            //设置url为jsonp文件夹中的server.js中创建的服务器中的/server路由
            url:'http://127.0.0.1:7000/server',
            param:{
                name:'张三',
                age:'20'
            },
            header:{
                'Content-Type':'application/json'//用户想向服务器端传递json格式类型的请求参数
            },
            success:function(data){
                console.log('这里是success函数');
                console.log(data);
            },
            error:function(data,xhr){
                console.log('这里是error函数');
                console.log(xhr);
            }
        })

示例:

<body>
    <script type="text/javascript">
    function ajax(options){
            var defaults = {

                type:'get',
                url:'',
                param:'',
                header: 
                {
                    'Content-Type':'application/x-www-form-urlencoded'
                },
                success:function(){},
                error:function(){},

            }
            Object.assign(defaults,options);//使用options对象中的属性覆盖default对象中的属性
            //创建ajax对象
            var xhr = new XMLHttpRequest();
            //用来拼接请求参数的变量
            var params ='';
            for(var attr in defaults.param){
                params += attr + '=' + defaults.param[attr] + '&';
            }
            //将最后的&截取掉再赋值给params
            params = params.substr(0,params.length-1);
            xhr.type = defaults.type;
            //如果是get请求则参数直接放url后面
            if(defaults.type == 'get'){
                defaults.url =defaults.url + '?' + params;
            }
            //配置ajax对象
            xhr.open(defaults.type,defaults.url);
            //发送请求
            if(defaults.type == 'post'){
                //用户希望向服务器端传递的参数类型
                var ContantType = defaults.header['Content-Type'];
                //设置请求参数格式的类型
                xhr.setRequestHeader('Content-Type',ContantType);
                if(ContantType == 'application/json'){
                    xhr.send(JSON.stringify(defaults.param));
                }else{
                    xhr.send(params);
                }
            }else{xhr.send()}
            //监听xhr对象下面的onload事件
            //当xhr对象接收完响应数据后触发
            xhr.onload = function(){
                //获取响应头中的数据
                var ContentType =xhr.getResponseHeader('Content-Type') 
                // console.log(xhr.getResponseHeader('Content-Type') );
                //服务器返回的数据
                var responseText = xhr.responseText
                if(ContentType.includes('application/json')){
                    responseText = JSON.parse(responseText)
                }
                //当http状态码为200时
                if(xhr.status == 200)
                {
                    //请求成功调用处理成功情况的函数
                    defaults.success(responseText,xhr);  
                }
                else{
                    //请求失败调用处理失败情况的函数
                    defaults.error(responseText,xhr);
                }
                
            }
        }
    </script>
</body>

需要注意:

get请求的请求参数可以拼接在url后面,但是post请求的请求参数是放在请求体中

请求参数位置问题

将请求参数传递到ajax函数内部,在函数内部根据请求方式的不同将请求参数放在不同的位置

get:放在请求地址后面

post:放在send方法中

请求参数格式问题

格式一:

application/x-www-form-urlencoded

参数名称=参数值&参数名称=参数值

如:name=zhangsan&age=20

格式二:

application/json

如:{name:‘zhangsan',age:20}

小结:

1.传递对象数据类型对于函数的调用者更加友好

2.在函数内部对象数据类型转换为字符串数据类型更方便

8、模板引擎

使用步骤:

1、下载art-template引擎并在html页面中用script标签引入库文件

2、准备art-template模板

3、告诉模板引擎哪个数据和哪个模板进行拼接

4、将拼接好的html字符串添加到页面中

5、通过模板语法告诉模板引擎,数据和html字符串要如何拼接

    <!-- 引入库文件 -->
    <script src="./template-web.js"></script>
</head>
<body>
    <div id="container"></div>
    <!-- 准备art-template模板 -->
    <script id="tpl" type="text/html">
       <h1>{{username}} {{age}}</h1>
    </script>
    <!-- 告诉模板引擎将哪个数据和哪个模板进行拼接 -->
    <!-- 格式:(1)模板id (2)数据(对象类型) -->
    <script type="text/javascript">
        //template方法的返回值就是拼接好的字符串
        var html = template('tpl',{username:'zhangsan',age:30})
        document.getElementById('container').innerHTML = html;
    </script>
    
</body>

8.2模板引擎中的语法

art-template支持两种语法:标准语法和原始语法。

标准语法更好写,原始语法具有强大的逻辑处理能力

8.2.1标准语法:

<h1>{{value}}</h1>
//语法内部可以进行简单的运算
<h1>{{a?b:c}}</h1>
<h2>{{a+b}}</h2>
//原文输出
//如果数据中带html标签,模板引擎默认不会解析标签,会将其转义后输出
{{@数据}}
//在模板中可以根据条件来决定显示哪块html代码
{{if 条件}}
...
{{/if}}
 
 {{if v1}}...{{else if v2}}...{{/if}}
//用来循环数据库返回的数据是数组的情况    
{{each target}}
{{$index}} {{$value}}//index代表当前循环的索引,value代表当前循环的数据
{{/each}}

 <!-- 准备art-template模板 -->
<script>
  <ul>
    {{each users}}
      <li>{{$value.name}}</li>
      <li>{{$value.age}}</li>
      <li>{{$value.sex}}</li>
     {{/each}}</ul>         
  </script>
<script>
const html=template(views,{
users:[{
name:‘张三‘,age:20,sex:‘男
},{
name:‘李四’,age:30,sex:‘男
},{
name:‘玛丽‘,age:15,[sex:'女
}]
});
</script>

8.2.2原始语法

<h1><%=value %></h1>
  <h1><%=a?b:c %></h1>
<h2><%=a+b %></h2>
<%-数据 %>

8.3子模板

使用子模板可以将网站公共区块(头部/底部)抽离到单独的文件中

标准语法

{{include '模板'}}
//例:
{{include '/header.art}}

原始语法

<%include('模板')%>
  //例:
  <%include('/header.art')%>

8.4模板继承

使用模板继承可以将网站html骨架抽离到单独的文件中,其他页面模板可以继承骨架文件

步骤:

1、填充css内容(main.css)

2、填充js内容(index.js)

3、填充页面主体内容(

hello

)

标准语法

{{extend './xxx.art'}}
<!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>
{{block 'head'}}{{/block}}
</head>
<body>
    {{block 'content'}}{{/block}}
</body>
</html>
//先继承挖坑的文件
{{extend './layout.art'}}

{{block 'content'}}
...要填充的内容,如:
<p>{{msg}}</p>
{{/block}}

总结:

在骨架模板使用<block 'name'>标签挖坑,

在具体的.art文件继承骨架然后根据业务需求填坑

9、FormData对象

9.1FormData对象的作用

1.模拟HTML表单,相当于将HTML表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式。

2、异步上传二进制文件

9.2FormData对象的使用

1、准备html表单

2、将html表单转换为FormData对象

FormData对象可以接受一个form元素作为参数,构造函数会将表单中的数据拼接成请求参数所需的格式

3、提交表单对象

xhr.send(formdata);

ps:

formdata对象不能用于get请求

9.3FormData对象的实例方法

formData.get('key');
formData.set('key','value');
//如果设置的表单属性存在就会替换原来的值,如果设置的表单属性不存在就会创建这个属性
formData.delate('key');
formData.append('key','value');

ps:

set和append的区别是,在属性名已存在的情况下,set 会覆盖已有键名的值,append会保留两个属性及其值,但是如果同个属性设置两次值,后端默认只会接收最后一次设置的那个属性值

9.4、FormData二进制文件上传

<input type="file" id = 'file'/>

<script>
  var file = document.getElementById('file')
  file.onchange - function(){
    //创建空表单对象
    var formData = new FormData();
    //将用户选择的二进制文件追加到表单对象中
    formData.append('attrName',this.files[0]);
        //配置ajax对象,请求方式必须为post
          var xhr = new XMLHttpRequest();
             xhr.open('post','http://localhost:3000/upload')
             //发送ajax请求
             xhr.send(formData);
             //监听服务器端的响应
             xhr.onload = function(){
                 if(xhr.status === 200){
                     //讲服务器端返回的数据显示在控制台中
                     console.log(xhr.responseText);
                 }
             }
  }
    </script>
// 实现文件上传的路由
app.post('/upload', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', "*")
  res.setHeader("Access-Control-Allow-Headers", "content-type");
  // 创建formidable表单解析对象
  const form = new formidable.IncomingForm();
  // 设置客户端上传文件的存储路径
  form.uploadDir = path.join(__dirname,'uploads');
  // 保留上传文件的后缀名字
  form.keepExtensions = true;
  // 解析客户端传递过来的FormData对象
  form.parse(req, (err, fields, files) => {
    res.send('ok');
  });
});

注意路径问题!

向模板中开放外部变量

格式:

 template.defaults.imports.dataFormat = dataFormat;
//template.defaults.imports写法是固定的,.dataFormat是要追加的属性是自定义的,等号右边是处理函数的名称

处理函数:

       function dataFormat(data){
           var year = data.substr(0,4);
           var month = data.substr(4,2);
           var day = data.substr(6,2);
           var hour = data.substr(8,2);
           var min = data.substr(10,2);
           var sec = data.substr(12,2);
           return year+'年'+month+'月'+day+'日'+hour+'时'+min+'分'+'秒';
//必须要return,return什么页面就显示什么
       }
  <td>{{dataFormat($value.update_time)}}</td>
//调用处理函数是要把需要处理的数据作为参数传进去

withCredentials属性

在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息。

withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false Access-Control-Allow-Credentials:true 允许客户端发送请求时携带cookie