Ajax及跨域


概念

Ajax

Ajax,Asynchronous JavaScript and XML,字面意思:異步的 JavaScript 和 XML,是指一種創建交互式網頁應用的網頁開發技術。
用於異步地去獲取XML作為數據交換的格式,當然,現在的 ajax 並不僅僅局限於XML作為數據交換格式,還可以像純文本、XML、HTML、JSON 等格式均可。

特點

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

異步原理

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

同步和異步:

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

在同步中,如果有一個任務耗時較長,整個的后面任務都需要等待。
在異步中,可以將耗時較長先放起來,執行其他的,其他的執行完畢,回頭再執行這個。

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

目的

為什么需要Ajax

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

C/S,在客戶端安裝了客戶端軟件之后,整個程序的運行,分擔到客戶端和服務器端。用戶在操作的時候,體驗更好,響應速度非常快。

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

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

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

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

Ajax 能干什么

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

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

發展歷程

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對象,所以有如下幾種形式

  • 純文本字符串,如1、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 獲取到內容


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM