Ajax及跨域

zwkt3118 7年前
   <h2>概念</h2>    <h3>Ajax</h3>    <p>Ajax,Asynchronus JavaScript and XML,字母意思:异步的 JavaScript 和 XML,是指一种创建交互式网页应用的网页开发技术。</p>    <p>用于异步地去获取XML作为数据交换的格式,当然,现在的 ajax 并不仅仅局限于XML作为数据交换格式,还可以像纯文本、XML、HTML、JSON 等格式均可。</p>    <h3>特点</h3>    <ol>     <li>使用脚本操纵HTTP和Web服务器进行数据交换,不会导致页面重载。</li>     <li>避免页面重载(这是Web初期的标准做法)的能力使Web应用感觉更像传统的桌面应用。</li>     <li>Web应用可以使用Ajax技术把用户的交互数据记录到服务器中;也可以开始只显示简单页面,之后按需加载额外的数据和页面组件来提升应用的启动时间。</li>    </ol>    <h3>异步原理</h3>    <p>Ajax 的 A 就是 asynchronous 的简写,表示异步。</p>    <p>同步和异步:</p>    <pre>  <code class="language-xml">同步,按照代码书写的顺序,一个任务一个任务的来执行。  异步,并不是按照代码书写的顺序,通常会结合回调和事件来执行相应代码。    在同步中,如果有一个任务耗时较长,整个的后面任务都需要等待。  在异步中,可以将耗时较长先放起来,执行其他的,其他的执行完毕,回头再执行这个。    同步:提交请求->等待服务器处理->处理完毕返回 阻塞模式。  异步:请求通过事件触发->服务器处理->处理完毕。非阻塞模式。</code></pre>    <h2>目的</h2>    <h3>为什么需要Ajax</h3>    <p>首先,我们都了解软件的两种架构形态,C/S(Client/Server)和B/S(Browser/Server)。它们的区别如下:</p>    <p>C/S,在客户端安装了客户端软件之后,整个程序的运行,分担到客户端和服务器端。用户在操作的时候,体验更好,响应速度非常快。</p>    <p>B/S,所务的服务都放在服务器端,通过浏览器使用服务器,用户在操作的时候,体验不太好,响应速度特别慢。</p>    <p>B/S的好处,就是不需要安装客户端,软件维护和更新比较方便,用户体验不好。</p>    <p>C/S的好处,用户体验够好,响应及时,但是软件的维护和更新比较麻烦。</p>    <p>随着互联网的发展,越来越多的C/S应用慢慢 转成的B/S,这也是未来的趋势。但是矛盾很突出,B/S的响应速度慢。需要B/S模式具备C/S模式的快速响应特点。</p>    <p>Ajax的出现就是为了解决这个问题-----异步刷新。</p>    <p>在异步刷新机制,整个页面不用跳转,只需要更新需要变化的地方。</p>    <h3>Ajax 能干什么</h3>    <ul>     <li>表单交互</li>     <li>动态标签页</li>     <li>及时编辑</li>     <li>地图类应用</li>     <li>移动APP</li>     <li>...</li>    </ul>    <p>宗旨:提升页面的访问速度,提高用户体验。</p>    <h2>发展历程</h2>    <p>2005年2月,Adaptive Path公司的Jesse James Garrett最早提出这个概念。它出现在Garrett的文章“Ajax: A New Approach to Web Applications”中。这篇文章描述了混合使用XHTML、CSS、JavaScript、DOM、XMLHttpRequest进行Web开发将会成为一种新的趋势。</p>    <p>同年,Google在三大产品中使用了Ajax技术:</p>    <ul>     <li>Google Suggest</li>     <li>Google Maps</li>     <li>Gmail。</li>    </ul>    <p>实际上,Ajax发展经历了三个时代:</p>    <ul>     <li>Ajax前传,使用iframe实现局部刷新技术;</li>     <li>Ajax正传,传统的Ajax;</li>     <li>Ajax新传,下一代Ajax,反向Ajax。</li>    </ul>    <h2>早起 Ajax 实现</h2>    <p>2005年,没有 Ajax 的概念,但是异步刷新的需要还是非常多。常用的方式就是 iframe。</p>    <h3>实现原理</h3>    <p>第一,iframe有一个src属性,通过设置src属性,发出请求,发出请求的时候不会跳转;</p>    <p>第二,iframe其实就是指向单独的页面,可以在其中进行任意的操作,可以在iframe中书写js代码,可以去操作父页面。</p>    <h3>iframe 的优缺点</h3>    <p>优点,实现起来特别简单,兼容性好。</p>    <p>缺点,功能比较弱,在服务端需要写大量的代码。</p>    <p>简单示例:</p>    <p>前台代码:</p>    <pre>  <code class="language-xml"><body>  <h2>用户注册</h2>  <form action="/reg" method="post">      <p>          <label for="username">用户名:</label>          <input type="text" id="username" name="username">          <span id="msg"></span>      </p>      <p>          <label for="password">密    码:</label>          <input type="password" id="password" name="password">      </p>      <p><input type="submit" value="注册"></p>  </form>    <iframe src="" frameborder="0" width="0" height="0" id="iframe1"></iframe>    <script>      var iframe1 = document.getElementById('iframe1');      var username = document.getElementById('username');        username.addEventListener('blur',function(){          iframe1.src = "/check?username=" + this.value;      });  </script>  </body></code></pre>    <p>服务器端代码:</p>    <pre>  <code class="language-xml">const express = require('express');  const bodyParser = require('body-parser');  const path = require('path');    const app = express();    app.get('/',(req,res)=>{      res.sendFile(path.join(__dirname,'login.html'));  });    app.post('/reg',(req,res)=>{      let username = req.body.username;      let password = req.body.password;      // 检测用户名是否可用,典型的做法需要去连接数据库,取出所有用户名,进行查询比较      // TODO      // 这种方式,可以满足需求,但是用户体验极差      // 每次都是填写完所有的表单内容,才能做一些检测      // 整个页面已经发生刷新跳转  });  // 模仿数据库  let users = ['admin','test'];  app.get('/check',(req,res)=>{      let username = req.query.username;      if (inArray(username,users)) {          res.send("<script>window.parent.document.getElementById('msg').innerHTML='对不起,用户名已被占用,请重新注册!'</script>");      } else {          res.send("<script>window.parent.document.getElementById('msg').innerHTML='恭喜,用户名可用!'</script>");      }  });    app.listen(3000,()=>{      console.log('server is listening in port 3000...');  });    // 简单封装一个方法,用于检测一个值是否在数组中存在  function inArray(str,arr){      for(let i=0; i<arr.length; i++){          if(str == arr[i]){              return true;              break;          }      }      return false;  }</code></pre>    <h2>完整的Ajax通信流程</h2>    <ol>     <li>首先要创建一个 Ajax 对象</li>    </ol>    <p>var xhr = new XMLHttpRequest();</p>    <p>状态: readyState: status: responseText:</p>    <ol>     <li>初始化</li>    </ol>    <p>状态: readyState: status: responseText:</p>    <ol>     <li>调用 open() 方法,开启一个请求,但没有向服务器端发起请求</li>    </ol>    <p>xhr.open(method, url [,async = true]);</p>    <p>状态: readyState: 1 status: responseText:</p>    <ol>     <li>调用 send() 方法,正式向服务器端发起请求</li>    </ol>    <ul>     <li>get</li>    </ul>    <p>xhr.send([data=null]);</p>    <ul>     <li>post</li>    </ul>    <p>xhr.setRequestHeader(header,value);</p>    <p>header:content-type<br> 表单 value:application/x-www-form-urlencoded<br> 文件上传 value:multipart/form-data</p>    <p>状态: readyState:2 status: responseText:</p>    <ol>     <li>当服务器端返回数据,浏览器端接收数据时</li>    </ol>    <p>状态: readyState:3 status: responseText:</p>    <ol>     <li> <p>当浏览器端结束请求的时候</p> <pre>  <code class="language-xml">xhr.onreadystatechange = function(callback){      if(xhr.readyState == 4){          if( (xhr.status >= 200 && xhr.status < 300) || xhr.status==304 ){              callback(xhr.responseText);          } else {                  alert('request was unsuccessful:' + xhr.status);          }      }  }</code></pre> </li>    </ol>    <p>状态: readyState:4 status:200 responseText:<!DOCTYPE html>响应返回值</p>    <h2>XMLHttpRequest 对象</h2>    <h3>定义</h3>    <ul>     <li>通用定义:XMLHttpRequest是一套可以在Javascript、VbScript、Jscript等脚本语言中通过http协议传送或从接收XML及其他数据的一套API。</li>     <li>来自MSDN的解释:XMLHttpRequest提供客户端同http服务器通讯的协议。客户端可以通过该对象向http服务器发送请求并使用XML文档对象模型处理回应。</li>    </ul>    <h3>属性</h3>    <pre>  <code class="language-xml">onreadystatechange* 指定当readyState属性改变时的事件处理句柄。只写     readyState  返回当前请求的状态,只读.     responseBody  将回应信息正文以unsigned byte数组形式返回.只读     responseStream 以Ado Stream对象的形式返回响应信息。只读     responseText 将响应信息作为字符串返回.只读     responseXML 将响应信息格式化为Xml Document对象并返回,只读     status 返回当前请求的http状态码.只读     statusText  返回当前请求的响应行状态,只读</code></pre>    <h3>方法</h3>    <pre>  <code class="language-xml">abort 取消当前请求     getAllResponseHeaders 获取响应的所有http头     getResponseHeader 从响应信息中获取指定的http头     open 创建一个新的http请求,并指定此请求的方法、URL以及验证信息(用户名/密码)     send 发送请求到http服务器并接收回应     setRequestHeader 单独指定请求的某个http头</code></pre>    <h3>创建xhr对象</h3>    <p>Microsoft最早把XMLHttpRequest对象引入到IE5中,且在IE5和IE6中它只是一个ActiveX对象。IE7之前的版本不支持非标准的XMLHttpRequest()构造函数,兼容如下:</p>    <pre>  <code class="language-xml">function createXHR(){      var xhr = null;      try{          xhr = new XMLHttpRequest();      }catch(e1){          try{              xhr = new ActiveXObject("Microsoft.XMLHTTP");          }catch(e2){              try{                  xhr = new ActiveXObject("Msxml2.XMLHTTP");              }catch(e3){                  throw new Error("XMLHttpRequest is not supported");              }          }      }      return xhr;  }</code></pre>    <h2>请求方式</h2>    <p>两种请求方式,有所不同。</p>    <p>get 请求</p>    <ul>     <li>数据大小,受限于浏览器,大部分2k的限制,但每个浏览器不一样,chrome 是8k</li>     <li>数据形式,查询字符串方式,名值对的形式,中间用 & 链接</li>     <li>安全性,不太安全</li>     <li>数据的传递通过查询字符串,在url后面用 ? 跟上</li>     <li>xhr.send([data=null])</li>    </ul>    <p>post请求</p>    <ul>     <li>数据大小,原则上没有限制,php.ini中默认上限是8M</li>     <li>数据形式,把 form 表单的数据给请求出来以xml形式传递给服务器</li>     <li>数据的传递得使用 send 方法,其中数据以键值对的形式来传递</li>     <li>安全性,比较安全</li>     <li>需要设置请求头信息</li>     <li>需要设置请求头部 xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");</li>     <li>xhr.send(Formdata);</li>    </ul>    <p>post请求注意事项</p>    <ul>     <li>使用setRequestHeader()把传递的数据组织为xml格式</li>     <li>调用send()方法时,需要传递数据</li>     <li>该方式请求的同时也可以传递get参数信息,使用get方式来接收该信息即可</li>    </ul>    <h2>请求参数序列化</h2>    <p>get请求中,如果参数是对象,那么我们需要对它进行转换,编码之后,不需要解码,浏览器会自动解码;</p>    <pre>  <code class="language-xml">var ulr = "example.json?" + serialize(formdata);  xhr.open('get', url, true);  xhr.send(null);</code></pre>    <p>post请求中,有特殊符号(=和&)和中文需要编码, encodeURIComponent 方法</p>    <pre>  <code class="language-xml">xhr.open('post', 'example.json', true);  xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded');  xhr.send(serialize(formdata));</code></pre>    <p>序列化方法:</p>    <pre>  <code class="language-xml">function serialize(data){      if(!data) return '';      var pairs = [];      for(var name in data){          if(!data.hasOwnProperty(name)) continue;          if(typeof data[name] === 'function') continue;          var value = data[name].toString();          name = encodeURIComponent(name);          value = encodeURIComponent(value);          pairs.push(name + '=' + value);      }      return pairs.join('&');  }</code></pre>    <h2>状态说明</h2>    <p>0(未初始化)</p>    <p>此阶段确认xhr对象是否创建成功,为调用open方法做好准备,值为0表示对象已经存在,否则浏览器报错</p>    <p>1(载入)</p>    <p>对xhr进行初始化,调用open方法,根据参数完成对象状态的设置,并调用send开始向服务器端发送请求,值为1表示正在向服务器端发送请求</p>    <p>2(载入完成)</p>    <p>接收服务器端的响应数据,但获得的还是服务器响应的原始数据,并不能在客户端使用。值为2表示已经接收完全部响应数据,并为下一阶段对数据进行解析做好准备</p>    <p>3(交互)</p>    <p>解析接收到的服务器端响应数据,根据服务器头部返回的MIME把数据转换成对应格式,为在客户端调用做好准备。3表示正在解析数据</p>    <p>4(完成)</p>    <p>确认全部数据已经解析为客户端可用的格式,解析已经完成。值为4表示数据解析完毕,可以通过xhr对象的属性取得数据</p>    <h2>返回数据格式</h2>    <p>xml responseXML 和 <strong>text</strong> responseText</p>    <p>作为文本返回时,返回的是字符串,但有些字符串比较特殊,如html代码、json对象,所以有如下几种形式</p>    <ul>     <li>纯文本字符串,如1、0、yes、no等,直接处理</li>     <li>返回html字符串,以innerHTML的方式写回到页面中</li>     <li>json格式,由js解析</li>    </ul>    <h2>跨域资源访问</h2>    <h3>同源策略</h3>    <p>两个页面拥有相同的协议(protocol)端口(port)和主机(host),那么这两个页面就属于同一个源(origin)</p>    <p>不满足同源策略的资源访问,叫跨域资源访问</p>    <h3>注意</h3>    <p>如果是协议和端口造成的跨域问题“前台”是无能为力的</p>    <p>在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。</p>    <p>(“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。)</p>    <h3>实现方法</h3>    <p>CORS</p>    <p>CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。</p>    <p>CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。</p>    <p>目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。</p>    <p>对于开发者来说,CORS通信与同源的Ajax通信没有差别,代码完全一样。</p>    <p>浏览器一旦发现Ajax请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。</p>    <h3>JSONP</h3>    <p>JSONP(JSON with Padding)是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。单向的数据请求。</p>    <p>JSONP的原理与实现思路</p>    <ol>     <li>Web页面通过调用外部js文件,可实现跨域请求。扩展一下:但凡有src属性的标签都具有跨域能力,例如 <script><img><iframe> 。</li>     <li>跨域服务器动态生成数据并存入js文件(通常json后缀),返回给客户端,供客户端调用;因为对json数据格式支持度都非常好。</li>     <li>为了便于客户端使用数据,形成一个非正式传输协议,称为JSONP。该协议重点是允许用户传递一个callback参数给服务器,然后服务器返回数据时 将此callback参数作为函数名包裹住JSON数据,使得客户端可以随意定制自己的函数来自动处理返回数据。</li>    </ol>    <p>因为通过script标签引入的js是不受同源策略的限制的。所以我们可以通过script标签引入一个js或者是一个其他后缀形式(如php,jsp等)的文件,此文件返回一个js函数的调用</p>    <p>示例如下:(服务器端用node.js)</p>    <p>浏览器端代码:</p>    <p>动态生成 script 标签,创建一个 callback ,以查询字符串形式跟在 src 的 url 后边</p>    <pre>  <code class="language-xml"><!DOCTYPE html>  <html lang="en">  <head>      <meta charset="UTF-8">      <title>JSONP</title>  </head>  <body>      <button id="btn">JSONP</button>      <script>          var btn = document.getElementById('btn');          function show(data){              console.log(data);          }          btn.onclick = function(){              var url = "http://localhost:4000/test?callback=show";              var script = document.createElement('script');              script.setAttribute('src',url);              document.getElementsByTagName('head')[0].appendChild(script);          }      </script>  </body>  </html></code></pre>    <p>服务器端代码:</p>    <pre>  <code class="language-xml">const express = require('express');  const path = require('path');    const app = express();    app.get('/',(req,res)=>{      res.sendFile(path.join(__dirname,'index.html'));  });    app.listen(3000,()=>{      console.log('http server is listening in port 3000');  });</code></pre>    <p>跨域服务器端代码:获取请求 url 中的 callback ,把 callback 和 json 数据组合后返回,这里需要注意,node.js返回数据的时候,设置 res.type("text/javascript"); ,</p>    <pre>  <code class="language-xml">const express = require('express');  const path = require('path');    const app = express();    app.get('/test',(req,res)=>{      let test = require('./test.json');      const callback = req.query.callback;      res.type("text/javascript");      res.send(callback+'('+JSON.stringify(test)+')');  });    app.listen(4000,()=>{      console.log('http server is listening in port 4000');  });</code></pre>    <p>json数据:</p>    <pre>  <code class="language-xml">{      "title" : "JSONP",      "time" : "2017-1-1"  }</code></pre>    <p>结果:</p>    <p>图1</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/361b4a8ef0b1ce7f05503545a21b4e04.png"></p>    <p>图2</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a67a72589d113501a913ed13cc6c6782.png"></p>    <p>优缺点</p>    <p>JSONP的优点:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;</p>    <p>它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;</p>    <p>并且在请求完毕后可以通过调用callback的方式回传结果</p>    <p>JSONP的缺点:它只支持GET请求而不支持POST等其它类型的HTTP请求;</p>    <p>它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题</p>    <p>CORS和JSONP对比</p>    <ul>     <li>JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。</li>     <li>使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。</li>     <li>JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS。</li>     <li>CORS与JSONP相比,无疑更为先进、方便和可靠。</li>    </ul>    <p>其他实现方法</p>    <ul>     <li>通过document.domain跨域</li>     <li>通过location.hash跨域</li>     <li>通过HTML5的postMessage方法跨域</li>     <li>通过window.name跨域</li>     <li>CSST (CSS Text Transformation)</li>    </ul>    <p>一种用 CSS 跨域传输文本的方案</p>    <p>优点:相比 JSONP 更为安全,不需要执行跨站脚本。</p>    <p>缺点:没有 JSONP 适配广,CSST 依赖支持 CSS3 的浏览器。</p>    <p>原理:通过读取 CSS3 content 属性获取传送内容。通过创建一个 link 请求到 css 文件,然后通过 computedStyle = window.getComputedStyle 获取到指定元素的 style 对象,再通过 computedStyle .content 获取到内容</p>    <p> </p>    <p>来自:http://www.cnblogs.com/zhangxiongcn/p/6237889.html</p>    <p> </p>