Blogger Information
Blog 54
fans 6
comment 31
visits 107521
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
ajax异步请求和jsonp的跨域请求探讨
吾逍遥
Original
1332 people have browsed it

一、学习的新认识

今天学习了前端中与后端交互的重要环节ajax异步请求,同时也学习了最流行的跨域方式JSONP。自己新的认识有以下几点:

  • json和js对象联系和区别 json是js提出的轻量级跨平台的数据交换/传输/存储格式,它解决了xml格式中标签过多导致网络传输量过大的问题,是目前最欢迎,支持最广的。不仅Javascript,其它如C、C++、C#、Java、PHP和Python都提供了编程接口对它的支持。
  • ajax的post请求三种方式 跟着老师理解了前端和后端交互的三种方式:是默认键值对方式、JSON方式和最简单的FormData对象方式,尤其是FormData方式是推荐方式,相比于普通ajax方式,它序列化数据直接被ajax识别传输,不用再要转为字符串,在后端也直接自动识别。
  • JSONP跨域请求 基于”同源策略”浏览器禁止使用JS脚本(Ajax)发起跨域请求,通过JSONP可以实现跨域请求数据,即使后端不支持跨域情况下。老师讲了JSONP的原理,但没讲怎么使用,我一直疑惑JSONP的回调函数的关键名从哪里来,直到在网上看了百度JSONP接口才明白。文中对后端跨域和JSONP跨域进行了区别,并实例演示了真实案例。

二、JSON

1、JSON是什么

  • JSON: JavaScript Object Notation(JS 对象表示法)
  • 跨平台跨语言的 轻量级数据交换/传输/存储格式 ,相比于XML,没有多余的标签,减少了数据体积,提高了效率。
  • JSON 独立于任何编程语言, 几乎所有编程语言都提供了访问JSON数据的API接口,如JS的JSON.parse和JSON.stringify,PHP的json_decode和json_encode。
  • 尽管 JSON 与 JS 并无直接关系,但 JSON 在 JS 代码中应用最广泛

其实json就是字符串,不过它是符合json格式的字符串 ,有时我们称json为json字符串 。

JSON格式:

  • 大括号或中括号 包裹。大括号 {}存储对象字面量 ,中括号 []存储数组字面量
  • 键-值对 json中都是键值对表示数据,键和值以:冒号分隔键值对以,逗号分隔 ,并且最后一个键值对后不能有逗号
  • 键名必须用英文双引号 包裹。 不管键名是否合法的js标识符都要用双引号包裹键名,单引号和没有引号都不是json格式,这点要与js对象区别开来。
  • 值可以是数值、字符串、布尔、null、对象和数组 注意没有undefined。
  • 值是字符串时要用英文双引号 包裹。 js对象中可用单引号或双引号,在json中若值是字符串则必须要用双引号。
  • 不能有注释 不能在json之间注释,因为它本身就是字符串,注释后就不符合json格式了。

2、js对象和json字符串

如下是典型的js对象定义:

  1. const user = {
  2. name:'woxiaoyao',
  3. age:28,
  4. "first job":'worker',
  5. isJob:true,
  6. comment:null,
  7. }

转化为json字符串时,要除掉const user=,给所有键名加上双引号,值中有单引号改为双引号,最后一个逗号除掉。于是就是下面形式

  1. {
  2. "name":"woxiaoyao",
  3. "age":28,
  4. "first job":"worker",
  5. "isJob":true,
  6. "commen":null
  7. }

在javascript中专门提供了两个方法实现了json字符串和js格式对象互转。

  • JSON.parse(jsonStr) 将json字符串转换为js对象
  • JSON.stringify(jsObj) 将js对象转换为json字符串

两者怎么记忆呢?我之前有时分不清,如果你像我一样,可这样记忆,因为string是字符串的意思,所有stringify最终结果是字符串,所有可以区分parse和stringify二个作用了。

三、ajax异步请求

Ajax 即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),是指一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

它依赖XMLHttpRequest对象 ,XMLHttpRequest是浏览器提供的,处理异步请求的宿主对象,而非 JS 内置对象

1、ajax的GET请求

ajax的GET请求流程:

  1. 创建请求对象: new XMLHttpRequest()
  2. 监听请求回调: onreadystatechange
  3. 初始化请求参数: open(请求类型,请求地址,是否异步)
  4. 发送请求: send()
  • 前后端数据传输: json字符串 ,不同语言都支持json字符串
  • 前后端数据处理: 接受到数据后先通过自己的JSON编程接口转为自己语言的对象,再进行处理。如javascript的JSON.parse()和PHP的json_decode()。
  1. <script>
  2. const form = document.querySelector('form');
  3. const btn = document.querySelector('button');
  4. btn.addEventListener('click', ajaxGet, false);
  5. // ajax的Get请求流程分四步:
  6. // 1. 创建请求对象: `new XMLHttpRequest()`
  7. const xhr = new XMLHttpRequest();
  8. // 2. 监听请求回调: `onreadystatechange`
  9. xhr.addEventListener('readystatechange', show, false);
  10. function ajaxGet(ev) {
  11. if (form.children[1].value) {
  12. let url = 'data/index.php';
  13. url = url.concat('?', 'name=',form.children[1].value);
  14. // 3. 初始化请求参数: `open(请求类型,请求地址,是否异步)` true表示异步
  15. xhr.open('GET', url, true);
  16. // 4. 发送请求: `send()`
  17. xhr.send();
  18. }
  19. }
  20. function show(ev) {
  21. if (xhr.readyState == 4) {
  22. // 返回的数据在xhr.responseText
  23. console.log(xhr.responseText);
  24. }
  25. }
  26. </script>

ajax-get

2、ajax的POST请求

ajax的POST请求流程:

  1. 创建请求对象: new XMLHttpRequest()
  2. 监听请求回调: onreadystatechange
  3. 初始化请求参数: open(请求类型,请求地址,是否异步)
  4. 设置请求头: setRequestHeader()
  5. 发送请求: send()
  • post 与 get 相比, 多了一步: 设置请求头
  • 前后端数据传输: json字符串 ,不同语言都支持json字符串,调用接口转为JSON字符串再传输。如JSON.stringify和PHP的json_encode()。
  • 前后端数据处理: 接受到数据后先通过自己的JSON编程接口转为自己语言的对象,再进行处理。如javascript的JSON.parse()和PHP的json_decode()。
  • 请求方式:
    • json 数据以表单数据类型发送, 前端请求头application/x-www-form-urlencoded, 后端用$_POST 接收
    • json 数组就是以JSON发送, 前端请求头application/json;charset=utf-8(json只支持utf-8编码), 后端用php://input 流文件方式接收
  1. <script>
  2. const form = document.querySelector('form');
  3. const btn = document.querySelector('button');
  4. btn.addEventListener('click', ajaxPost, false);
  5. // ajax的Post请求流程分五步:
  6. // 1. 创建请求对象: `new XMLHttpRequest()`
  7. const xhr = new XMLHttpRequest();
  8. // 2. 监听请求回调: `onreadystatechange`
  9. xhr.addEventListener('readystatechange', show, false);
  10. function ajaxPost(ev) {
  11. let url = 'data/index2.php';
  12. // 3. 初始化请求参数: `open(请求类型,请求地址,是否异步)` true表示异步
  13. xhr.open('POST', url, true);
  14. // 4. 设置请求头: `setRequestHeader()`
  15. // 第一种:以表单键值对的方式发送数据
  16. // xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
  17. // 第二种:以json方式发送
  18. xhr.setRequestHeader('content-type', 'application/json;charset=utf-8');
  19. let $user = {
  20. name: 'admin',
  21. password: '123456',
  22. };
  23. // 转换为json字符串
  24. let data = JSON.stringify($user);
  25. // 5. 发送请求: `send()`
  26. xhr.send(data);
  27. }
  28. function show(ev) {
  29. if (xhr.readyState == 4) {
  30. // 返回的数据在xhr.responseText
  31. console.log(xhr.responseText);
  32. }
  33. }
  34. </script>

ajax-post

3、FormData对象

前端ajax的post请求时能不能不写请求头, 服务器端php还能用$_POST来接收呢?答案是可以的,就是使用FormData对象直接序列化表单数据,将表单数据进行封装后统一提交,可直接被 Ajax 识别,所以可以不设置请求头。除了表单数据外,也可用于普通数据。

  1. // 第三种:formData对象方式,不用设置请求头,由浏览器自行判定,上面代码中请求可不用设置,数据直接通过FormData提交。
  2. <script>
  3. const form = document.querySelector('form');
  4. const btn = document.querySelector('button');
  5. btn.addEventListener('click', ajaxPost, false);
  6. // ajax的Post请求流程分五步:
  7. // 1. 创建请求对象: `new XMLHttpRequest()`
  8. const xhr = new XMLHttpRequest();
  9. // 2. 监听请求回调: `onreadystatechange`
  10. xhr.addEventListener('readystatechange', show, false);
  11. function ajaxPost(ev) {
  12. let url = 'data/index2.php';
  13. // 3. 初始化请求参数: `open(请求类型,请求地址,是否异步)` true表示异步
  14. xhr.open('POST', url, true);
  15. // 4. 发送请求: `send()`
  16. // 第三种:formData对象方式,不用设置请求头,由浏览器自行判定,
  17. xhr.send(new FormData(form));
  18. }
  19. function show(ev) {
  20. if (xhr.readyState == 4) {
  21. // 返回的数据在xhr.responseText
  22. console.log(xhr.responseText);
  23. }
  24. }
  25. </script>

FormData是XMLHttpRequest 2级定义的,它是为序列化表以及创建与表单格式相同的数据提供便利。

  1. 利用一些键值对来模拟一系列表单控件:即将form中的所有表单元素的name和value组装成一个queryString;
  2. 异步上传二进制文件。
  • 与普通Ajax相比,使用FormData的最大优点:可以异步上传二进制文件。

下面对FormData对象进行拓展介绍

创建FormData对象方式:

  1. let formdata = new FormData(); 创建一个空的FormData对象,可以使用formdata.append(key,value)来添加数据。
  2. let formdata = new FormData(form); 使用已有的表单来初始化一个FormData对象。

操作方法:

  1. 获取数据formdata.get(key)
    formdata.get(“username”); //获取key为username的第一个值
    formdata.getAll(“username”); //获取key为username的所有值,返回一个数组

  2. 添加数据formdata.append(key,value)
    formdata.append(“k1”,”v1”);
    formdata.append(“k1”,”v2”);
    formdata.append(“k2”,”v2”);
    formdata.getAll(“k1”); //[“v1”,”v2”]
    formdata.get(“k2”); //“v2”

  3. 设置/修改数据formdata.set(key,value) 如果key不存在则新增一条数据,如果存在,则会修改对应的value值。
    formdata.append(“k1”,”v1”);
    formdata.set(“k1”,”v2”);
    formdata.get(“k1”); //“v2”

  4. 判断是否存在某条数据formdata.has(key) ,存在返回true,不存在返回false。
    formdata.append(“k1”,”v1”);
    formdata.has(“k1”); //true
    formdata.has(“k2”); //false

  5. 删除数据formdata.delete(key)
    formdata.append(“k1”,”v1”);
    formdata.delete(“k1”);
    formdata.get(“k1”); //null

  6. 遍历 通过entries()或values()来获取一个遍历器
    formdata.append(“k1”,”v1”);
    formdata.append(“k2”,”v2”);
    for(let [key,value] of formdata.entries()){
    console.log(key +”:”+ value)
    }

  1. <!-- 发送数据(上传文件不刷新页面) -->
  2. <input type="file" id="upload"/>
  3. <input type="button" id="upload-btn" value="上传"/>
  4. const btn = document.querySelector("#upload-btn");
  5. btn.onclick = function(){
  6. let file = document.querySelector("#upload");
  7. let formdata = new FormData();
  8. formdata.append("file",file.files[0]);
  9. fetch("https://www.baidu.com",{
  10. method:'POST',
  11. body:formdata
  12. }).then(d=>{
  13. console.log("result is" + d);
  14. alert("上传成功");
  15. })
  16. }

四、JSONP实现跨域请求

跨域请求:

  • 为了安全, 通过脚本发起的请求必须基于”同源策略”
  • 浏览器禁止使用 JS 脚本(Ajax)发起跨域请求(跨域资源共享)
  • 但是通过html中带有src属性的标签跨域请求不能禁止的,毕竟这是互联网发明初衷。

同源策略: 协议, 域名, 端口 三者完全相同, 则认为他们遵循了”同源策略”,否则如下面则认为不同源。

1、JSONP原理演示

JSONP:(JSON with Padding)

  • script标签允许跨域请求脚本: <script src="...."></script>
  • 动态生成<script>元素,并将需要跨域访问的 URL,赋值给 script 元素的 src 属性
  • 在跨域访问的服务器脚本中(如 php),将数据转为 json 格式,直接返回给前端处理就可以了
  1. <!-- 前端 -->
  2. <div class="container">
  3. <form action="" onsubmit="return false;">
  4. <label for="name">名字:</label>
  5. <input type="text" name="name" value="" id="name" required />
  6. <label for="email">密码:</label>
  7. <input type="email" name="email" value="" id="email" />
  8. <button>跨域请求</button>
  9. </form>
  10. </div>
  11. <script>
  12. const form = document.querySelector('form');
  13. const btn = document.querySelector('button');
  14. btn.addEventListener('click', createScript, false);
  15. function createScript() {
  16. const script = document.createElement('script');
  17. let name = form.children[1].value;
  18. if (name.length < 1) return false;
  19. script.src = `http://blog.io/index.php?name=${name}&jsonp=show`;
  20. document.head.appendChild(script);
  21. }
  22. function show(data) {
  23. console.log(data);
  24. form.children[3].value = data.email;
  25. }
  26. </script>
  1. // 后端
  2. <?php
  3. $users = [
  4. ['id' => 1, 'name' => 'admin', 'email' => 'admin@php.cn'],
  5. ['id' => 2, 'name' => 'peter', 'email' => 'peter@php.cn'],
  6. ['id' => 3, 'name' => 'jack', 'email' => 'jack@php.cn'],
  7. ];
  8. // 查询条件
  9. $name = $_GET['name'];
  10. // js回调
  11. $callback = $_GET['jsonp'];
  12. foreach ($users as $user) {
  13. if ($user['name'] == $name) {
  14. $result = $user;
  15. break;
  16. }
  17. }
  18. $data = json_encode($result);
  19. // 创建一条js函数的调用语句返回
  20. // echo "函数名(参数)";
  21. echo "{$callback}({$data})";

jsonp

2、 JSONP的应用场景

写这篇文章之前这个问题深深困扰着我,老师只介绍了JSONP的原理,对其应用只是一句话带过,说可以查询天气。因为我是做后端的,知道RestfulAPI一般都会设置支持跨域,其实PHP支持跨域很简单,就是在PHP源码中加上下面代码就可以了,也就是CORS。

  1. // 跨域请求
  2. header('Content-Type: text/html;charset=utf-8');
  3. // 代表允许任何网址请求
  4. header('Access-Control-Allow-Origin:*');
  5. // 允许请求的类型
  6. header('Access-Control-Allow-Methods:POST,GET,OPTIONS,DELETE');
  7. // 设置是否允许发送 cookies
  8. header('Access-Control-Allow-Credentials: true');
  9. // 设置允许自定义请求头的字段
  10. header('Access-Control-Allow-Headers: Content-Type,Content-Length,Accept-Encoding,X-Requested-with, Origin');

对于后端支持跨域请求的,就没必要在前端再跨域请求了,那么后端不支持跨域请求的,如天气网站的数据,我们JSONP的回调函数的键名如何得到,是后端公开吗?明显不可能,还有就是API的格式。最后我在一篇介绍各搜索引擎的JSONP时才发现,JSONP能工作要有以下几点:

  • 首先要抓取提供JSONP服务的api,包括关键字和回调键名,这样我们可以提供查询数据和声明回调函数。
  • 通过上面JSONP的script的创建,模拟后端合法的用户 ,后端在执行echo时会调用我们的声明函数,从而得到数据。
  • 对于不提供JSONP服务的,如主流的RestfulAPI的,JSONP就没办法了。

下面实战演示搜索时调用百度的搜索词联想功能

  1. <style>
  2. * {
  3. margin: 0px;
  4. padding: 0px;
  5. }
  6. li {
  7. list-style: none;
  8. }
  9. #wrap {
  10. width: 600px;
  11. height: 40px;
  12. margin: 100px auto;
  13. }
  14. #text {
  15. width: 500px;
  16. height: 34px;
  17. margin: 0 auto;
  18. line-height: 34px;
  19. }
  20. #list {
  21. width: 500px;
  22. border: 1px solid #ccc;
  23. }
  24. #list > li {
  25. width: 500px;
  26. height: 30px;
  27. line-height: 30px;
  28. }
  29. #list > li > a {
  30. text-decoration: none;
  31. }
  32. </style>
  33. <div id="wrap">
  34. <input type="text" id="text" placeholder="请输入搜索关键字" />
  35. <ul id="list"></ul>
  36. </div>
  37. <script type="text/javascript">
  38. //wd 查询关键字
  39. //cd 返回函数 回调函数
  40. let oInput = document.querySelector('#text'),
  41. oList = document.querySelector('#list');
  42. oInput.onblur = function () {
  43. let val = this.value; //获取当前input框里的内容
  44. console.log('input=>', val);
  45. if (val) {
  46. let oS = document.createElement('script'); //创造script标签
  47. oS.src = `https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=${val}&cb=getDate`;
  48. document.body.appendChild(oS);
  49. oS.onload = function () {
  50. //在script标签加载完后删除标签
  51. document.body.removeChild(oS);
  52. };
  53. }
  54. };
  55. function getDate(data) {
  56. //data是后台发送过来的函数调用里的参数
  57. console.log(data);
  58. let arr = data.s, //获取联想词
  59. str = '',
  60. len = arr.length;
  61. for (let i = 0; i < len; i++) {
  62. str += `<li><a href="">${arr[i]}</a></li>`;
  63. }
  64. oList.innerHTML = str;
  65. }
  66. </script>

baidu0

baidu1

目前的问题: 就是JSONP的api的接口的抓取 ,这个第三方是不提供 的 ,早上在群里讨论是有的同学把它误认为RestfulAPI了,对于公开的RestfulAPI我们可以直接使用,当然付费也是公开的api,只不过在后端会验证你的token令牌是否合法而已。而JSONP要做的事是第三方不允许的,所有需要自己获取api。网上那篇文章只是说了通过chrome开发者的Network面板,没具体演示,我自己测试了下,还不是很会就不说了,以后明白了再补充,当然欢迎会的同学来指教。
补充: 网上查询JSONP时,说可以访问RestfulApi,我自己测试了下,还是出现跨域禁止。他们可能是加上其它中转环节,也许我不知道,我也想它能访问普通API,有会的望不吝赐教。

3、JSONP的不足

JSONP通过script请求返回JSON实际上是脚本注入。它虽然解决了跨域问题,但它不是万能的。这里我只是简单借用下网上说的。

  • 不能接受HTTP状态码
  • 不能使用POST提交(默认GET
  • 不能发送和接受HTTP头
  • 不能设置同步调用(默认异步)
  • 其最严重的就是不能提供错误处理,如果请求的代码正常执行那么会得到正确的结果。如果请求失败,如404,500之类,那么可能什么都不会发生。

五、学习后的总结

  • 重新认识了json,也明白了前后端交互的实质。传输中是json字符串,前后端接受数据后再转换为自己的对象处理,完后再转为json对象进行交互。解决了我以前在处理ajax请求数据时老是报错找不到时原因的问题。
  • 原生js实现了get和post请求,熟悉了请求流程,以后再用框架找问题就不再话下了
  • jsonp跨域访问,不得不说,它原理演示很简单,就是远程调用了我们声明的函数。但实际应用理解却非常困难
    • 首先是JSONP是非法获取,它模仿第三方合法用户来获取数据,这就涉及不公开的api怎么获取?,通过chrome或其它类似的抓取,自己测试了下,目前会简单抓取天气网站的,而且是无意中看到,其它网站看不出来。
    • JSONP对RestfulAPI的跨域访问,我看网站上是jquery封装的跨域访问RestfulAPI,而我在本地是用原生JS的JSONP测试是无效的,有待以后再补充吧。
    • JSONP上面列举了不少缺点,但我测试后发现有个优点 :就是后端不CORS情况下,对部分API可以通过JSONP的方式提供给其它用户调用,毕竟在CORS情况下好像不能控制哪些API支持跨域。当然这个观点有待验证,先提出吧,等其它人验证。

留点疑问:postman访问没跨域限制,chrome跨域插件能将直接实际跨域如何实现??

题外话: 关于PHP端的json_encode和json_decode使用
我原本以为接受到数据后就json_decode,处理完数据返回给客户端前json_encode转为json字符串就可以。但在实际过程中却发现有时这样反而错误。

  • 解决方案:可后端使用print_r或var_dump打印数据,若是字符串,返回给客户端就不要再json_encode了,对于接受的数据也可以按这个方法,若是json字符串就转,不是就不用转
Correcting teacher:天蓬老师天蓬老师

Correction status:qualified

Teacher's comments:FormData只是表单数据序列化的一种方案罢了, 这样的方案还有不少... 做为web开发的里程碑技术Ajax,目前也即将走到了尽头, 抽空多关注 一下fetch API, promise
Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
1 comments
吾逍遥 2020-11-07 16:38:40
我查询ajax时,看到fetch,正准备了解呢
1 floor
Author's latest blog post