Ajax及跨域

概念

Ajax

Ajax,Asynchronous JavaScript and XML,字面意思:異步的 JavaScript 和 XML,是指一種建立交互式網頁應用的網頁開發技術。
用於異步地去獲取XML做爲數據交換的格式,固然,如今的 ajax 並不只僅侷限於XML做爲數據交換格式,還能夠像純文本、XML、HTML、JSON 等格式都可。javascript

特色

  1. 使用腳本操縱HTTP和Web服務器進行數據交換,不會致使頁面重載。
  2. 避免頁面重載(這是Web初期的標準作法)的能力使Web應用感受更像傳統的桌面應用。
  3. Web應用可使用Ajax技術把用戶的交互數據記錄到服務器中;也能夠開始只顯示簡單頁面,以後按需加載額外的數據和頁面組件來提高應用的啓動時間。

異步原理

Ajax 的 A 就是 asynchronous 的簡寫,表示異步。php

同步和異步:css

同步,按照代碼書寫的順序,一個任務一個任務的來執行。
異步,並非按照代碼書寫的順序,一般會結合回調和事件來執行相應代碼。

在同步中,若是有一個任務耗時較長,整個的後面任務都須要等待。
在異步中,能夠將耗時較長先放起來,執行其餘的,其餘的執行完畢,回頭再執行這個。

同步:提交請求->等待服務器處理->處理完畢返回 阻塞模式。
異步:請求經過事件觸發->服務器處理->處理完畢。非阻塞模式。

目的

爲何須要Ajax

首先,咱們都瞭解軟件的兩種架構形態,C/S(Client/Server)和B/S(Browser/Server)。它們的區別以下:html

C/S,在客戶端安裝了客戶端軟件以後,整個程序的運行,分擔到客戶端和服務器端。用戶在操做的時候,體驗更好,響應速度很是快。java

B/S,所務的服務都放在服務器端,經過瀏覽器使用服務器,用戶在操做的時候,體驗不太好,響應速度特別慢。node

B/S的好處,就是不須要安裝客戶端,軟件維護和更新比較方便,用戶體驗很差。ajax

C/S的好處,用戶體驗夠好,響應及時,可是軟件的維護和更新比較麻煩。chrome

隨着互聯網的發展,愈來愈多的C/S應用慢慢 轉成的B/S,這也是將來的趨勢。可是矛盾很突出,B/S的響應速度慢。須要B/S模式具有C/S模式的快速響應特色。
Ajax的出現就是爲了解決這個問題-----異步刷新。
在異步刷新機制,整個頁面不用跳轉,只須要更新須要變化的地方。數據庫

Ajax 能幹什麼

  • 表單交互
  • 動態標籤頁
  • 及時編輯
  • 地圖類應用
  • 移動APP
  • ...

宗旨:提高頁面的訪問速度,提升用戶體驗。express

發展歷程

2005年2月,Adaptive Path公司的Jesse James Garrett最先提出這個概念。它出如今Garrett的文章「Ajax: A New Approach to Web Applications」中。這篇文章描述了混合使用XHTML、CSS、JavaScript、DOM、XMLHttpRequest進行Web開發將會成爲一種新的趨勢。
同年,Google在三大產品中使用了Ajax技術:

  • Google Suggest
  • Google Maps
  • Gmail。

實際上,Ajax發展經歷了三個時代:

  • Ajax前傳,使用iframe實現局部刷新技術;
  • Ajax正傳,傳統的Ajax;
  • Ajax新傳,下一代Ajax,反向Ajax。

早起 Ajax 實現

2005年,沒有 Ajax 的概念,可是異步刷新的須要仍是很是多。經常使用的方式就是 iframe。

實現原理

第一,iframe有一個src屬性,經過設置src屬性,發出請求,發出請求的時候不會跳轉;

第二,iframe其實就是指向單獨的頁面,能夠在其中進行任意的操做,能夠在iframe中書寫js代碼,能夠去操做父頁面。

iframe 的優缺點

優勢,實現起來特別簡單,兼容性好。

缺點,功能比較弱,在服務端須要寫大量的代碼。

簡單示例:

前臺代碼:

<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>

服務器端代碼:

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;
}

完整的Ajax通訊流程

1.首先要建立一個 Ajax 對象

var xhr = new XMLHttpRequest();

狀態:readyState: status: responseText:

2.初始化

狀態:readyState: status: responseText:

3.調用 open() 方法,開啓一個請求,但沒有向服務器端發起請求

xhr.open(method, url [,async = true]);

狀態:readyState: 1 status: responseText:

4.調用 send() 方法,正式向服務器端發起請求

  • get

xhr.send([data=null]);

  • post

xhr.setRequestHeader(header,value);

header:content-type
表單value:application/x-www-form-urlencoded
文件上傳value:multipart/form-data

狀態:readyState:2 status: responseText:

5.當服務器端返回數據,瀏覽器端接收數據時

狀態:readyState:3 status: responseText:

6.當瀏覽器端結束請求的時候

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);
            }
        }
    }

狀態:readyState:4 status:200 responseText:<!DOCTYPE html>響應返回值

XMLHttpRequest 對象

定義

  • 通用定義:XMLHttpRequest是一套能夠在Javascript、VbScript、Jscript等腳本語言中經過http協議傳送或從接收XML及其餘數據的一套API。
  • 來自MSDN的解釋:XMLHttpRequest提供客戶端同http服務器通信的協議。客戶端能夠經過該對象向http服務器發送請求並使用XML文檔對象模型處理迴應。

屬性

onreadystatechange* 指定當readyState屬性改變時的事件處理句柄。只寫   
readyState  返回當前請求的狀態,只讀.   
responseBody  將回應信息正文以unsigned byte數組形式返回.只讀   
responseStream 以Ado Stream對象的形式返回響應信息。只讀   
responseText 將響應信息做爲字符串返回.只讀   
responseXML 將響應信息格式化爲Xml Document對象並返回,只讀   
status 返回當前請求的http狀態碼.只讀   
statusText  返回當前請求的響應行狀態,只讀

方法

abort 取消當前請求   
getAllResponseHeaders 獲取響應的全部http頭   
getResponseHeader 從響應信息中獲取指定的http頭   
open 建立一個新的http請求,並指定此請求的方法、URL以及驗證信息(用戶名/密碼)   
send 發送請求到http服務器並接收回應   
setRequestHeader 單獨指定請求的某個http頭

建立xhr對象

Microsoft最先把XMLHttpRequest對象引入到IE5中,且在IE5和IE6中它只是一個ActiveX對象。IE7以前的版本不支持非標準的XMLHttpRequest()構造函數,兼容以下:

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;
}

請求方式

兩種請求方式,有所不一樣。

get 請求

  • 數據大小,受限於瀏覽器,大部分2k的限制,但每一個瀏覽器不同,chrome 是8k
  • 數據形式,查詢字符串方式,名值對的形式,中間用 & 連接
  • 安全性,不太安全
  • 數據的傳遞經過查詢字符串,在url後面用 跟上
  • xhr.send([data=null])

post請求

  • 數據大小,原則上沒有限制,php.ini中默認上限是8M
  • 數據形式,把 form 表單的數據給請求出來以xml形式傳遞給服務器
  • 數據的傳遞得使用 send 方法,其中數據以鍵值對的形式來傳遞
  • 安全性,比較安全
  • 須要設置請求頭信息
  • 須要設置請求頭部 xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
  • xhr.send(Formdata);

post請求注意事項

  • 使用setRequestHeader()把傳遞的數據組織爲xml格式
  • 調用send()方法時,須要傳遞數據
  • 該方式請求的同時也能夠傳遞get參數信息,使用get方式來接收該信息便可

請求參數序列化

get請求中,若是參數是對象,那麼咱們須要對它進行轉換,編碼以後,不須要解碼,瀏覽器會自動解碼;

var url = "example.json?" + serialize(formdata);
xhr.open('get', url, true);
xhr.send(null);

post請求中,有特殊符號(=和&)和中文須要編碼,encodeURIComponent方法

xhr.open('post', 'example.json', true);
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
xhr.send(serialize(formdata));

序列化方法:

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('&');
}

狀態說明

0(未初始化)

此階段確認xhr對象是否建立成功,爲調用open方法作好準備,值爲0表示對象已經存在,不然瀏覽器報錯

1(載入)

對xhr進行初始化,調用open方法,根據參數完成對象狀態的設置,並調用send開始向服務器端發送請求,值爲1表示正在向服務器端發送請求

2(載入完成)

接收服務器端的響應數據,但得到的仍是服務器響應的原始數據,並不能在客戶端使用。值爲2表示已經接收徹底部響應數據,併爲下一階段對數據進行解析作好準備

3(交互)

解析接收到的服務器端響應數據,根據服務器頭部返回的MIME把數據轉換成對應格式,爲在客戶端調用作好準備。3表示正在解析數據

4(完成)

確認所有數據已經解析爲客戶端可用的格式,解析已經完成。值爲4表示數據解析完畢,能夠經過xhr對象的屬性取得數據

返回數據格式

xml responseXMLtext responseText

做爲文本返回時,返回的是字符串,但有些字符串比較特殊,如html代碼、json對象,因此有以下幾種形式

  • 純文本字符串,如一、0、yes、no等,直接處理
  • 返回html字符串,以innerHTML的方式寫回到頁面中
  • json格式,由js解析

跨域資源訪問

同源策略

兩個頁面擁有相同的協議(protocol)端口(port)和主機(host),那麼這兩個頁面就屬於同一個源(origin)

不知足同源策略的資源訪問,叫跨域資源訪問

注意

若是是協議和端口形成的跨域問題「前臺」是無能爲力的

在跨域問題上,域僅僅是經過「URL的首部」來識別而不會去嘗試判斷相同的ip地址對應着兩個域或兩個域是否在同一個ip上。

(「URL的首部」指window.location.protocol +window.location.host,也能夠理解爲「Domains, protocols and ports must match」。)

實現方法

CORS

CORS(Cross-Origin Resource Sharing)跨域資源共享,定義了必須在訪問跨域資源時,瀏覽器與服務器應該如何溝通。
CORS背後的基本思想就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功仍是失敗。
目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10。整個CORS通訊過程,都是瀏覽器自動完成,不須要用戶參與。
對於開發者來講,CORS通訊與同源的Ajax通訊沒有差異,代碼徹底同樣。
瀏覽器一旦發現Ajax請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感受。

參考阮老師的文章:http://www.ruanyifeng.com/blog/2016/04/cors.html

JSONP

JSONP(JSON with Padding)是一個非官方的協議,它容許在服務器端集成Script tags返回至客戶端,經過javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。單向的數據請求。

JSONP的原理與實現思路

  1. Web頁面經過調用外部js文件,可實現跨域請求。擴展一下:但凡是有src屬性的標籤都具備跨域能力,例如<script><img><iframe>
  2. 跨域服務器動態生成數據並存入js文件(一般json後綴),返回給客戶端,供客戶端調用;由於對json數據格式支持度都很是好。
  3. 爲了便於客戶端使用數據,造成一個非正式傳輸協議,稱爲JSONP。該協議重點是容許用戶傳遞一個callback參數給服務器,而後服務器返回數據時 將此callback參數做爲函數名包裹住JSON數據,使得客戶端能夠隨意定製本身的函數來自動處理返回數據。

由於經過script標籤引入的js是不受同源策略的限制的。因此咱們能夠經過script標籤引入一個js或者是一個其餘後綴形式(如php,jsp等)的文件,此文件返回一個js函數的調用

示例以下:(服務器端用node.js)

瀏覽器端代碼:
動態生成script標籤,建立一個callback,以查詢字符串形式跟在srcurl後邊

<!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>

服務器端代碼:

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');
});

跨域服務器端代碼:獲取請求url中的callback,把callbackjson數據組合後返回,這裏須要注意,node.js返回數據的時候,設置res.type("text/javascript");

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');
});

json數據:

{
    "title" : "JSONP",
    "time" : "2017-1-1"
}

結果:

圖1

圖2

優缺點

JSONP的優勢:它不像XMLHttpRequest對象實現的Ajax請求那樣受到同源策略的限制;
它的兼容性更好,在更加古老的瀏覽器中均可以運行,不須要XMLHttpRequest或ActiveX的支持;
而且在請求完畢後能夠經過調用callback的方式回傳結果

JSONP的缺點:它只支持GET請求而不支持POST等其它類型的HTTP請求;
它只支持跨域HTTP請求這種狀況,不能解決不一樣域的兩個頁面之間如何進行JavaScript調用的問題

CORS和JSONP對比

  • JSONP只能實現GET請求,而CORS支持全部類型的HTTP請求。
  • 使用CORS,開發者可使用普通的XMLHttpRequest發起請求和得到數據,比起JSONP有更好的錯誤處理。
  • JSONP主要被老的瀏覽器支持,它們每每不支持CORS,而絕大多數現代瀏覽器都已經支持了CORS。
  • CORS與JSONP相比,無疑更爲先進、方便和可靠。

其餘實現方法

  • 經過document.domain跨域
  • 經過location.hash跨域
  • 經過HTML5的postMessage方法跨域
  • 經過window.name跨域
  • CSST (CSS Text Transformation)

一種用 CSS 跨域傳輸文本的方案

優勢:相比 JSONP 更爲安全,不須要執行跨站腳本。
缺點:沒有 JSONP 適配廣,CSST 依賴支持 CSS3 的瀏覽器。

原理:經過讀取 CSS3 content 屬性獲取傳送內容。經過建立一個 link 請求到 css 文件,而後經過 computedStyle = window.getComputedStyle 獲取到指定元素的 style 對象,再經過 computedStyle .content 獲取到內容

相關文章
相關標籤/搜索