你們好,這裏是「 從零開始學 Web 系列教程 」,並在下列地址同步更新......javascript
- github:https://github.com/Daotin/Web
- 微信公衆號:Web前端之巔
- 博客園:http://www.cnblogs.com/lvonve/
- CSDN:https://blog.csdn.net/lvonve/
在這裏我會從 Web 前端零基礎開始,一步步學習 Web 相關的知識點,期間也會分享一些好玩的項目。如今就讓咱們一塊兒進入 Web 前端學習的冒險之旅吧!php
跨域這個概念來自一個叫 「同源策略」 的東西。同源策略是瀏覽器上爲了安全考慮實施的很是重要的安全機制。html
Ajax 默認只能獲取到同源的數據,對於非同源的數據,Ajax是獲取不到的。前端
什麼是同源?java
協議、域名、端口所有相同。jquery
好比一個界面地址爲:http://www.example.com/dir/page.html 這個網址,在這個地址中要去訪問下面服務器的數據,那麼會發生什麼狀況呢?git
URL | 結果 | 緣由 |
---|---|---|
https://www.example.com/dir/other.html | 不一樣源 | 協議不一樣,https 和 http |
http://en.example.com/dir/other.html | 不一樣源 | 域名不一樣 |
http://www.example.com:81/dir/other.html | 不一樣源 | 端口不一樣 |
http://www.example.com/dir/page2.html | 同源 | 協議,域名,端口都相同 |
http://www.example.com/dir2/page.html | 同源 | 協議,域名,端口都相同 |
若是使用 Ajax 獲取非同源的數據,會報錯,報錯信息以下:github
Failed to load http://hr.pcebg.efoxconn.com/checkUsername.php?uname=176: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access. The response had HTTP status code 404.
那麼。想要獲取非同源地址的數據,就要使用跨域。不管是 Ajax 仍是跨域,都是爲了訪問服務器的數據。簡單的來講, Ajax 是爲了訪問本身服務器的數據,跨域是爲了訪問別人服務器的數據(好比獲取天氣信息,航班信息等)。web
咱們能夠經過 script 標籤,用 script 標籤的屬性引入一個外部文件,這個外部文件是不涉及到同源策略的影響的。ajax
<script src="http://www.example.com/dir/xxx.js"></script>
而後,這個外部文件中有一個或幾個方法的調用,這些方法的定義在本身的界面文件中,而咱們想要的是方法的參數,能夠在本身定義的方法中拿到。這就是跨域的本質。
script 引入的應該是 js 文件,若是咱們想要引入 php 文件的話,就須要在 php 代碼中,返回 js 格式的代碼。
<?php echo "var str = 'hello'"; echo "func('123')"; ?>
在咱們 html 文件中:
<script> function func(data) { // 就爲了獲取參數 console.log(data); } </script> <script src="http://www.example.com/xxx.php"></script>
再進一步,若是咱們在 PHP 地址中傳入了參數:
<?php $city = $_GET["city"]; if($city == "beijing") { echo "func('獲取到北京天氣')"; } else { echo "func('爲獲取到天氣信息')"; } ?>
html 文件:
<script> function func(data) { // 就爲了獲取參數 console.log(data); } </script> <script src="http://www.example.com/xxx.php?city=beijing"></script>
固然,若是隻是手動的在php文件後面傳入參數,就太固定了,那麼咱們可不能夠根據用戶的輸入來獲取不一樣城市天氣信息呢?
答案是確定的。咱們能夠採起動態建立 script 的方式來獲取用戶想要的信息。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>天氣查詢</h1><br> <input type="text" placeholder="請輸入城市" id="txt"><br> <input type="button" value="獲取天氣" id="btn"> <script> function func(data) { console.log(data); } document.getElementById("btn").onclick = function () { var city = document.getElementById("txt").value; var script = document.createElement("script"); script.src = "http://hr.pcebg.efoxconn.com/checkUsername.php?city=" + city; document.getElementsByTagName("head")[0].appendChild(script); }; </script> </body> </html>
還記得咱們 html 中有個回調函數的定義嗎?這個函數的名稱是固定的,咱們可不能夠動態指定呢?答案也是確定的,咱們既然能夠在 php 地址傳遞參數過去,就能夠順便把回調函數的名稱也傳遞過去,動態的指定回調函數的名稱。
//... function foo (data) { console.log(data); } script.src = "http://hr.pcebg.efoxconn.com/checkUsername.php?city=" + city + "&callback=foo"; //...
外部 php 代碼:
<?php $city = $_GET["city"]; $callback = $_GET["callback"]; if($city == "beijing") { echo $callback . "('獲取到北京天氣')"; } else { echo $callback . "('爲獲取到天氣信息')"; } ?>
以後,再看咱們在 script 裏面寫的 foo 函數的定義,會不會以爲很突兀?咱們把它改爲 window 的方法就能夠了。
window["foo"] = function(data) { console.log(data); };
而後把它放到按鈕的點擊事件中,這樣就和按鈕的點擊事件融爲一體了。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>天氣查詢</h1><br> <input type="text" placeholder="請輸入城市" id="txt"><br> <input type="button" value="獲取天氣" id="btn"> <script> document.getElementById("btn").onclick = function () { window["foo"] = function(data) { console.log(data); }; var city = document.getElementById("txt").value; var script = document.createElement("script"); script.src = "http://hr.pcebg.efoxconn.com/checkUsername.php?city=" + city + "&callback=foo"; document.getElementsByTagName("head")[0].appendChild(script); }; </script> </body> </html>
在修改回調函數的名稱時,只需修改兩個部分就能夠了(window["foo"] 和 "&callback=foo";),php 的代碼不須要修改。
淘寶提示詞接口
地址 | https://suggest.taobao.com/sug |
---|---|
做用描述 | 獲取淘寶提示詞接口 |
請求類型 | get 請求 |
參數 | q:關鍵詞; callback:回調函數名稱 |
返回數據格式 | jsonp格式 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="dv"> <h1>淘寶提示詞</h1> <input type="text" placeholder="請輸入關鍵詞" id="txt"> <input type="button" value="查詢" id="btn"> <ul id="uu"></ul> </div> <script> document.getElementById("btn").onclick = function () { var script = document.createElement("script"); var txt = document.getElementById("txt").value; script.src = "https://suggest.taobao.com/sug?q="+txt+"&callback=sug"; window["sug"] = function (data) { var str = ""; if(data.result.length !== 0) { for (var i = 0; i < data.result.length; i++) { str += "<li>"+data.result[i]+"</li>"; } document.getElementById("uu").innerHTML = str; } else { str = "<li>未找到關鍵詞</li>"; document.getElementById("uu").innerHTML = str; } }; document.querySelector("head").appendChild(script); }; </script> </body> </html>
百度提示詞接口
地址 | http://suggestion.baidu.com/su |
---|---|
做用描述 | 獲取百度提示詞接口 |
請求類型 | get 請求 |
參數 | wd:關鍵詞; cb:回調函數名稱 |
返回數據格式 | jsonp格式 |
PS:與淘寶提示詞代碼相同,只須要修改地址、參數便可。
咱們從以前的 Ajax 的代碼知道,這樣的代碼太過於冗餘,咱們須要對代碼進行封裝。
咱們將實現的代碼封裝成一個 js 文件。
//my-sug.js 文件 function myAjaxCross(obj) { var defaults = { url: "#", //地址 data: {}, // 業務邏輯參數 ,好比:wd=web&pwd=123 success: function (data) {}, // 參數傳遞回來的處理函數 jsonp: "callback", // 獲取方法名的key值。是一個回調函數,由後端接口文檔指定 jsonpCallback: "sug" // 獲取方法名的value值,也就是方法名字 }; // 由 obj 傳入的對象覆蓋 defaults for (var key in obj) { defaults[key] = obj[key]; } var script = document.createElement("script"); // 將 data 裏面的參數拼接到 url 後面 var params = ""; for (var attr in defaults.data) { params += attr + "=" + defaults.data[attr] + "&"; } // 去掉最後多出來的 & 符號 if (params && (params !== "")) { params = params.substring(0, params.length - 1); } // 再在地址後面拼接回調函數 script.src = defaults.url + "?" + params + "&" + defaults.jsonp + "=" + defaults.jsonpCallback; console.log(script.src); // 對回調函數進行定義,將參數傳入本身定義的處理數據函數中 window[defaults.jsonpCallback] = function (data) { defaults.success(data); }; document.querySelector("head").appendChild(script); }
下面以百度提示詞爲例,使用這個封裝好的 js 文件。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="dv"> <h1>百度提示詞</h1> <input type="text" placeholder="請輸入關鍵詞" id="txt"> <input type="button" value="查詢" id="btn"> <ul id="uu"></ul> </div> <script src="my-sug.js"></script> <script> document.getElementById("btn").onclick = function () { myAjaxCross({ url: "http://suggestion.baidu.com/su", data: {wd:document.getElementById("txt").value}, success: function (data) { console.log(data); var str = ""; if(data.s.length !== 0) { for (var i = 0; i < data.s.length; i++) { str += "<li>"+data.s[i]+"</li>"; } document.getElementById("uu").innerHTML = str; } else { str = "<li>未找到關鍵詞</li>"; document.getElementById("uu").innerHTML = str; } }, jsonp: "cb", jsonpCallback: "sug" }); }; </script> </body> </html>
相似 jQuery 封裝好了 Ajax 同樣,jQuery 也對跨域數據的獲取進行了封裝,調用方法跟 Ajax 如出一轍。
咱們仍是以百度提示詞舉例,使用 jQuery 來獲取數據。
$.ajax({ url: "http://suggestion.baidu.com/su", data: {wd: document.getElementById("txt").value}, success: function (data) { console.log(data); var str = ""; if(data.s.length !== 0) { for (var i = 0; i < data.s.length; i++) { str += "<li>"+data.s[i]+"</li>"; } document.getElementById("uu").innerHTML = str; } else { str = "<li>未找到關鍵詞</li>"; document.getElementById("uu").innerHTML = str; } }, dataType: "jsonp", // 重點 jsonp: "cb", // 根據需求指定,默認爲:callback jsonpCallback: xxx // 能夠省略 });
一、dataType: "jsonp" 是重點,當 dataType 的類型爲 jsonp 的時候,才能實現 jQuery 的跨域獲取數據,不然只能使用同源策略。
二、jsonp: "cb" :根據後端需求指定
三、jsonpCallback: xxx:能夠不須要。
主要借鑑了 jQuery 的處理方法,判斷 dataType 的值。
//跨域數據obj dataType=jsonp function myAjax(obj){ if(obj.dataType == "jsonp") { myAjax4Across(obj); } else { myAjax4Normal(obj); } } function myAjax4Across(obj){ var defaults = { type:"get", url:"#", data:{}, success:function(data){}, jsonp:"callback", jsonpCallback:"hehe" }; for(var key in obj) { defaults[key] = obj[key]; } var params = ""; for(var attr in defaults.data){ params += attr + "=" + defaults.data[attr] + "&"; } if(params) { params = params.substring(0,params.length-1); defaults.url += "?" + params; } defaults.url += "&"+defaults.jsonp+"=" + defaults.jsonpCallback; console.log(defaults.url); var script = document.createElement("script"); script.src = defaults.url; window[defaults.jsonpCallback] = function(data){ defaults.success(data); }; var head = document.querySelector("head"); head.appendChild(script); } function myAjax4Normal(obj) { var defaults = { type:"get", url:"#", dataType:"json", data:{}, async:true, success:function(result){console.log(result);} }; //obj中的屬性,覆蓋到defaults中的屬性 //一、若是有一些屬性只存在obj中,會給defaults中增長屬性 //二、若是有一些屬性在obj和defaults中都存在,會將defaults中的默認值覆蓋 //三、若是有一些屬性只在defaults中存在,在obj中不存在,這時候defaults中將保留預約義的默認值 for(var key in obj){ defaults[key] = obj[key]; } var xhr = null; if(window.XMLHttpRequest) { xhr = new XMLHttpRequest(); } else { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } //獲得params // data:{ // uname:"zhangsan", // age:"18" // }// uname=zhangsan&age=18 var params = ""; for(var attr in defaults.data){ params += attr + "=" + defaults.data[attr] + "&"; } if(params) { params = params.substring(0,params.length - 1); } if(defaults.type == "get") { defaults.url += "?" + params; } xhr.open(defaults.type,defaults.url,defaults.async); if(defaults.type == "get") { xhr.send(null); } else if(defaults.type == "post") { xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); xhr.send(params); } if(defaults.async) { xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { if(xhr.status == 200) { var result = null; if(defaults.dataType == "json") { result = xhr.responseText; result = JSON.parse(result); } else if(defaults.dataType == "xml") { result = xhr.responseXML; } else { result = xhr.responseText; } defaults.success(result); } } }; } else { if(xhr.readyState == 4) { if(xhr.status == 200) { var result = null; if(defaults.dataType == "json") { result = xhr.responseText; result = JSON.parse(result); } else if(defaults.dataType == "xml") { result = xhr.responseXML; } else { result = xhr.responseText; } defaults.success(result); } } } }
咱們以前作的全部工做都是爲了獲取服務器的數據,不論是同源的數據仍是跨域的數據。獲取到數據以後,咱們就須要將其在頁面上展現出來。前端的界面都是由標籤構成的,這種展現的過程其實最主要的就是生成 html 標籤。
咱們以前顯示獲取到的數據是使用字符串拼接成 li 標籤,而後將 li 標籤添加到 ul 標籤的方式。這種作法有個弊端,就是當界面特別複雜的時候,使用字符串拼接的方式就會很複雜,對於後期的維護也會很困難。
下面介紹的模板引擎就能夠很方便的生成 html 標籤。
模板引擎的本質是:將數據和模板結合來生成 html 片斷。
這裏介紹一款效率很高的模板引擎:artTemplate,這個模板引擎是騰訊公司出品的開源模板引擎。
使用步驟:
一、引入 js 文件
二、定義模板
三、將數據和模板結合起來生成 html 片斷
四、將 html 片斷渲染到界面中
仍是以百度提示詞爲例:
好比我想生成類型以下格式標籤的代碼片斷:
<li> <div> <span>索引</span> <span>索引對應的值</span> </div> </li>
源代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="dv"> <h1>百度提示詞</h1> <input type="text" placeholder="請輸入關鍵詞" id="txt"> <input type="button" value="查詢" id="btn"> <ul id="uu"></ul> </div> <script src="../jquery-1.12.4.min.js"></script> <script src="template.js"></script> <!--定義模板--> <!--一、指定type類型爲type="text/html",而不是jacascript--> <!--二、指定一個id值--> <!--三、循環遍歷接收到的數據,生成html片斷--> <!--each 就是循環遍歷data中的數組,在百度案例裏面,data中的數組是s,因此遍歷s --> <!--as 是關鍵字,i 是索引,value是索引的值。--> <!--在代碼片斷中使用的時候,記得要加兩個大括號來使用變量的值--> <script type="text/html" id="myart"> {{each s as value i}} <li> <div> <span>結果{{i}} --- </span> <span>{{value}}</span> </div> </li> {{/each}} </script> <script> document.getElementById("btn").onclick = function () { $.ajax({ url: "http://suggestion.baidu.com/su", data: {wd: document.getElementById("txt").value}, success: function (data) { // 將數據和模板結合起來 // template 是模板引擎提供的 // 第一個參數:自定義的模板的id值 // 第二個參數:接收到的後端的數據 var html = template("myart", data); document.getElementById("uu").innerHTML = html; }, dataType: "jsonp", // 重點 jsonp: "cb" // 根據需求指定,默認爲:callback }); }; </script> </body> </html>
示例1:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>模板引擎</title> <script type="text/javascript" src="./template.js"></script> <script type="text/html" id="resultTemplate"> <h1>{{title}}</h1> {{each books as value i}} <div>{{value}}</div> {{/each}} </script> <script type="text/javascript"> window.onload = function(){ var data = { title : '四大名著圖書信息', books:['三國演義','水滸傳','西遊記','紅樓夢'] }; var html = template("resultTemplate",data); var container = document.querySelector("#container"); container.innerHTML = html; } </script> </head> <body> <div id="container"> </div> </body> </html>
在定義的模板裏面,使用的是 data 裏面的屬性,能夠直接使用,好比 title。
示例2:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script type="text/javascript" src="./template.js"></script> <script id="test" type="text/html"> {{if isAdmin}} <h1>{{title}}</h1> <h2>一共有{{count}}條數據</h2> <ul> {{each list as value i}} <li>索引 {{i + 1}} :{{value}}</li> {{/each}} </ul> {{/if}} {{if !isAdmin}} <h1>沒有任何數據</h1> {{/if}} </script> <script> window.onload = function(){ var data = { title: '條件判斷基本例子', isAdmin: true, list: ['文藝', '博客', '攝影', '電影', '民謠', '旅行', '吉他'] }; data.count = data.list.length; var html = template("test",data); document.querySelector("#content").innerHTML = html; } </script> </head> <body> <div id="content"> </div> </body> </html>
一、定義的模板裏面也能夠加條件判斷語句:{{if data裏面的屬性}}。
二、能夠將獲得的數據進行處理,好比增長 count 屬性,而後在定義的模板裏面直接使用。
實例3:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script type="text/javascript" src="./template.js"></script> <!-- data.data --> <script id="test" type="text/html"> <ul> {{each arr as value i}} <li>{{value}}</li> {{/each}} </ul> </script> <script> window.onload = function(){ var data = ['文藝', '博客', '攝影', '電影', '民謠', '旅行', '吉他']; var temp = {}; temp.arr = data; var html = template("test",temp);//data.xxx document.querySelector("#content").innerHTML = html; } </script> </head> <body> <div id="content"> <ul> <li>文藝</li> <li>博客</li> </ul> </div> </body> </html>
一、當咱們獲取的數據沒有內部屬性的時候,好比上面的例子,不能夠直接使用 data,否則程序會認定爲 data.data 屬性,而這個屬性是不存在的。
二、咱們能夠經過增長一個對象,增長這個對象的一個屬性 arr,其值爲 data,而後遍歷 arr 來使用。
示例4:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script type="text/javascript" src="./template.js"></script> <script id="test" type="text/html"> <p>轉義:{{#value}}</p> <p>不轉義: {{value}}</p> </script> <script> window.onload = function(){ // 這裏的數據當中包含特殊字符 var data = { value: '<span style="color:#F00">hello world!</span>' }; var html = template('test', data); document.getElementById('content').innerHTML = html; } </script> </head> <body> <div id="content"></div> </body> </html>
一、咱們獲取到的數據也多是 html 的代碼。
二、在定義的模板中調用的時候,經過在屬性前加 「#」 能夠將 html 代碼轉義處理。不然只會理解成字符串。
MOB:www.mob.com,裏面有個 MobAPI 服務,有不少好玩的 API 接口,好比天氣、電影、汽車等。
通常第三方的接口都須要先註冊,而後得到 appkey,才能使用。
問題:若是第三方接口返回的是 json 而不是 jsonp 格式的數據的話,怎麼辦麼?
咱們知道 Ajax 須要返回的是函數的調用,函數的參數是 json 格式的,若是第三方直接返回一個 json 的字符串怎麼辦呢?因爲不是返回的函數調用,按照跨域的方式確定是會報錯的。
解決辦法:經過本身的服務器做爲中介來實現。
首先,本身的服務器後臺,不論是 PHP 仍是 JSP,來獲取第三方的數據,因爲後臺不受同源策略的限制,因此本身的服務器獲取到 json 數據後,echo 回來,而後咱們前端再使用 Ajax 的四步驟來獲取後臺返回的 json 類型的字符串。