一、AJAX示例
AJAX全稱為“Asynchronous JavaScript And XML”(異步JavaScript和XML) 是指一種創建交互式網頁應用的開發技術、改善用戶體驗,實現無刷新效果。
1.1、優點
不需要插件支持
優秀的用戶體驗
提高Web程序的性能
減輕服務器和帶寬的負擔
1.2、缺點
瀏覽器對XMLHttpRequest對象的支持度不足,幾乎所有瀏覽器現在都支持
破壞瀏覽器“前進”、“后退”按鈕的正常功能,可以通過簡單的插件彌補
對搜索引擎的支持不足
1.3、jQuery AJAX示例
在HTML5中對原生的AJAX核心對象XMLHttpRequest進行升級,也就是XHR2,功能更加強大。
jQuery對AJAX封裝的非常好,這里以簡單的商品管理為示例使用jQuery完成AJAX應用。
Product.java bean:
package com.gomall.bean; /*** * 產品 * * @author Administrator * */ public class Product { /** 編號 */ private int id; /** 名稱 */ private String name; /** 價格 */ private double price; /** 圖片 */ private String picture; /** 詳細 */ private String detail; @Override public String toString() { return "Product [id=" + id + ", name=" + name + ", price=" + price + ", picture=" + picture + ", detail=" + detail + "]"; } public Product(int id, String name, double price, String picture) { super(); this.id = id; this.name = name; this.price = price; this.picture = picture; } public Product(int id, String name, double price, String picture, String detail) { super(); this.id = id; this.name = name; this.price = price; this.picture = picture; this.detail = detail; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public String getPicture() { return picture; } public void setPicture(String picture) { this.picture = picture; } public String getDetail() { return detail; } public void setDetail(String detail) { this.detail = detail; } }
IProductService.java:
package com.gomall.service; import java.util.List; import com.gomall.bean.Product; public interface IProductService { /**獲得所有*/ List<Product> getAll(); /**添加 * @return */ boolean add(Product entity); /**根據編號獲得產品對象*/ Product findById(int id); /**根據編號獲得產品對象 * @return */ boolean deleteById(int id); }
ProductService.java:
package com.gomall.service; import java.util.ArrayList; import java.util.List; import java.util.Random; import com.gomall.bean.Product; public class ProductService implements IProductService { public static ArrayList<Product> products; static { products = new ArrayList<>(); Random random = new Random(); for (int i = 1; i <= 10; i++) { Product product = new Product(i, "華為Mate9MHA-AL00/4GB RAM/全網通華為超級閃充技術雙后攝設計" + random.nextInt(999), random.nextDouble() * 1000, "pic(" + i + ").jpg", "產品詳細"); products.add(product); } } /* * (non-Javadoc) * * @see com.gomall.service.IProductService#getAll() */ @Override public List<Product> getAll() { return products; } /* * (non-Javadoc) * * @see com.gomall.service.IProductService#add(com.gomall.bean.Product) */ @Override public boolean add(Product entity) { try { entity.setId(products.size() + 1); entity.setPicture("pic(" + entity.getId() + ").jpg"); // uploadify // 上傳圖片 products.add(entity); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /* * (non-Javadoc) * * @see com.gomall.service.IProductService#findById(int) */ @Override public Product findById(int id) { for (Product product : products) { if (product.getId() == id) { return product; } } return null; } /* * (non-Javadoc) * * @see com.gomall.service.IProductService#deleteById(int) */ @Override public boolean deleteById(int id) { try { Product product = findById(id); if (product != null) { products.remove(product); } } catch (Exception e) { e.printStackTrace(); return false; } return true; } }
ProductAction.java:
package com.gomall.action; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.codehaus.jackson.map.ObjectMapper; import com.gomall.bean.Product; import com.gomall.service.IProductService; import com.gomall.service.ProductService; @WebServlet("/Product") public class ProductAction extends HttpServlet { private static final long serialVersionUID = 1L; public ProductAction() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { /*模擬網絡延時*/ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); String act = request.getParameter("act"); IProductService productService = new ProductService(); /**用於序列化json*/ ObjectMapper mapper = new ObjectMapper(); PrintWriter out=response.getWriter(); if (act.equals("getAll")) { String json = mapper.writeValueAsString(productService.getAll()); out.append(json); } else if (act.equals("area")) { String callback=request.getParameter("callback"); out.append(callback+"('"+new Date()+"')"); } else if (act.equals("getJSONP")) { String callback=request.getParameter("callback"); String json = mapper.writeValueAsString(productService.getAll()); out.append(callback+"("+json+")"); } else if (act.equals("getAllCORS")) { /**向響應的頭部中添加CORS信息*/ response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Methods", "GET,POST"); String json = mapper.writeValueAsString(productService.getAll()); out.append(json); } else if(act.equals("del")){ /**向響應的頭部中添加CORS信息*/ response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Methods", "GET,POST"); int id=Integer.parseInt(request.getParameter("id")); String json = mapper.writeValueAsString(productService.deleteById(id)); out.append(json); } else if(act.equals("add")){ /**向響應的頭部中添加CORS信息*/ response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Methods", "GET,POST"); String name=request.getParameter("name"); double price=Double.parseDouble(request.getParameter("price")); String detail=request.getParameter("detail"); Product entity=new Product(0, name, price, "",detail); String json = mapper.writeValueAsString(productService.add(entity)); out.append(json); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
客戶端跨域調用:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>AJAX</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <style type="text/css"> @CHARSET "UTF-8"; * { margin: 0; padding: 0; font-family: microsoft yahei; font-size: 14px; } body { padding-top: 20px; } .main { width: 90%; margin: 0 auto; border: 1px solid #777; padding: 20px; } .main .title { font-size: 20px; font-weight: normal; border-bottom: 1px solid #ccc; margin-bottom: 15px; padding-bottom: 5px; color: blue; } .main .title span { display: inline-block; font-size: 20px; background: blue; color: #fff; padding: 0 8px; background: blue; } a { color: blue; text-decoration: none; } a:hover { color: orangered; } .tab td, .tab, .tab th { border: 1px solid #777; border-collapse: collapse; } .tab td, .tab th { line-height: 26px; height: 26px; padding-left: 5px; } .abtn { display: inline-block; height: 20px; line-height: 20px; background: blue; color: #fff; padding: 0 5px; } .btn { height: 20px; line-height: 20px; background: blue; color: #fff; padding: 0 8px; border: 0; } .abtn:hover, .btn:hover { background: orangered; color: #fff; } p { padding: 5px 0; } fieldset { border: 1px solid #ccc; padding: 5px 10px; } fieldset legend { margin-left: 10px; font-size: 16px; } .pic { height: 30px; width: auto; } #divFrom{ display: none; } </style> </head> <body> <div class="main"> <h2 class="title"><span>商品管理</span></h2> <table border="1" width="100%" class="tab" id="tabGoods"> <tr> <th>編號</th> <th>圖片</th> <th>商品名</th> <th>價格</th> <th>詳細</th> <th>操作</th> </tr> </table> <p style="color: red" id="message"></p> <p> <a href="#" class="abtn" id="btnSave">添加</a> <input type="submit" value="刪除選擇項" class="btn" /> </p> <div id="divFrom"> <form id="formPdt"> <fieldset> <legend>添加商品</legend> <p> <label for="name"> 名稱: </label> <input type="text" name="name" id="name" /> </p> <p> <label for="price"> 價格: </label> <input type="text" name="price" id="price" /> </p> <p> <label for="detail"> 詳細: </label> <textarea id="detail" name="detail" cols="60"></textarea> </p> </fieldset> </form> </div> </div> <link rel="stylesheet" type="text/css" href="js/artDialog6/ui-dialog.css" /> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script src="js/artDialog6/dialog-min.js" type="text/javascript" charset="utf-8"></script> <!--[if (IE 8)|(IE 9)]> <script src="js/jquery.transport.xdr.min.js" type="text/javascript" charset="utf-8"></script> <![endif]--> <script type="text/javascript"> var app = { url: "http://localhost:8087/JavaScript001/", //提供服務的域名 add: function() { var d = dialog({ title: '添加商品', content: $('#divFrom').html(), okValue: '添加', modal:true, backdropOpacity:0.3, ok: function() { var that = this; $.ajax({ type: "post", data: $(".ui-dialog #formPdt").serialize() + "&act=add", success: function(data) { if(data) { app.log("添加成功!"); app.loadAll(); that.close(); } else { app.log("添加失敗!"); } } }); return false; }, cancelValue: '關閉', cancel: function() { alert('你點了取消按鈕') }, onclose:function(){ alert("關閉了"); } }); d.show(); }, del: function() { //closest離當前元素最近的td父元素 id = $(this).closest("td").data("id"); var that = $(this); $.ajax({ type: "get", data: { "id": id, "act": "del" }, success: function(data) { if(data) { that.closest("tr").remove(); app.log("刪除成功!"); } else { app.log("刪除失敗!"); } } }); }, loadAll: function() { $.ajax({ type: "get", data: { "act": "getAllCORS" }, success: function(data) { $("#tabGoods tr:gt(0)").remove(); $.each(data, function(index, obj) { var tr = $("<tr/>"); //行 $("<td/>").html(obj.id).appendTo(tr); //編號 var imgTd = $("<td/>"); $("<img/>", { "src": app.url + "images/" + obj.picture, "class": "pic" }).appendTo(imgTd); //圖片 imgTd.appendTo(tr); $("<td/>").html(obj.name).appendTo(tr); $("<td/>").html(Math.ceil(obj.price)).appendTo(tr); $("<td/>").html(obj.detail).appendTo(tr); $("<td/>").html("<a href='#' class='abtn del'>刪除</a>").data("id", obj.id).appendTo(tr); $("#tabGoods").append(tr); }); } }); }, init: function() { /*動態綁定刪除事件*/ $("#tabGoods").on("click", "a.del", {}, app.del); /*綁定添加事件*/ $("#btnSave").click(app.add); /*設置全局AJAX默認值*/ $.ajaxSetup({ dataType: "json", url: app.url + "Product?type=meat-and-filler&format=json", beforeSend: app.ajaxBefore, complete: app.clearMsg, error: function(xhr, textStatus, errorThrown) { app.log("錯誤" + textStatus + errorThrown); } }); this.loadAll(); }, clearMsg: function() { this.box.remove(); }, ajaxBefore: function() { this.box=dialog({ modal:true }); this.box.show(); }, log: function(msg) { $("#message").html(msg); } }; app.init(); </script> </body> </html>
運行結果:
刪除:
二、延遲對象(Deferred)
deferred對象就是jQuery1.5版以后新增加的回調函數解決方案。
2.0、方法作為參數傳遞
在靜態語言如C#中可以使用委托將方法作為參數傳遞,C++中可以使用方法指針,因為JavaScript是動態語言,方法作為參數傳遞非常便捷,示例如下:
示例1:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> //將方法作為參數帶給另一個方法 //C# 委托 //C++ 方法指針 //Java 沒有,匿名內部類,接口 function handler(arr, f1) { for(var i = 0; i < arr.length; i++) { arr[i] = f1(arr[i]); } return arr; } function add(n) { return n + 1; } function sub(n) { return n - 1; } var result = handler([1, 2, 3], function(n) { return n * 3; }); console.log(result); </script> </body> </html>
結果:
示例2:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> function hello(f){ console.info("問好:"); var x=100; f(x); }; hello(function(n){ console.log("Hello!"+n); }); hello(function(n){ console.log("你好!"+n); }); </script> </body> </html>
結果:
函數自動提升
在Javascript中定義一個函數,有兩種寫法:
function foo() { }
和
var foo = function () { }
兩種寫法完全等價。但是在解析的時候,前一種寫法會被解析器自動提升到代碼的頭部,因此違背了函數應該先定義后使用的要求,所以建議定義函數時,全部采用后一種寫法。
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> console.log(typeof f1); f1(); console.log(typeof f2); //undefined f2(); var f2 = function() { console.log('f2'); } //函數自動提升 function f1() { console.info('f1'); } </script> </body> </html>
結果:
當函數作為參數傳遞時應該在調用前判斷,避免異常,示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script type="text/javascript"> //將方法作為參數帶給另一個方法 //C# 委托 //C++ 方法指針 //Java 沒有,匿名內部類,接口 function handler(arr, f1) { if(f1&&typeof f1==="function") { for(var i = 0; i < arr.length; i++) { arr[i] = f1(arr[i]); } } else{ throw "未指定參數"; } return arr; } function add(n) { return n + 1; } function sub(n) { return n - 1; } var result = handler([1, 2, 3], function(n) { return n * 3; }); console.log(result); result=handler([2, 3, 8],function(){}); console.log(result); </script> </body> </html>
結果:
回調方法在異步請求中的使用:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script src="../js/jQuery1.12.3/jquery-1.12.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> var fun=function(student){ console.log(student.name); }; function getStudents(){ //異步 $.getJSON("student.json",function(data){ if(fun) { fun(data); } }); } var student=getStudents(function(student){ console.log(student.name); }); </script> </body> </html>
結果:
2.1、回調函數
先看一個示例:
首先,為什么要使用Deferred?
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>回調</title> </head> <body> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> var student; $.get("student.json",function(data){ student=data; },"json"); alert(student); </script> </body> </html>
student.json文件:{"name":"tom","id":"01"}
運行結果:
因為AJAX是異步執行的,類似高級語言中的多線程,當發起ajax請求時會有網絡延遲,而代碼並沒有在$.get的位置被阻塞,alert先執行,但數據並沒有從遠程獲取到,所以結果是undefined。
其實初學者經常會犯這種錯誤,如:
function getStudentById(id){ $.get("students.do",{"id":id},function(stu){ return stu; },"json"); }
上面的代碼是有問題的,原因如前面的示例是一樣的。怎么解決,如果你認為是異步帶來的問題,當然通過同步是可以解決的,如:
$.ajax({ type:"get", url:"student.json", async:false, /*非異步,同步*/ success:function(data){ student=data; } });
結果:
如果將所有的ajax請求修改為同步的,則ajax的好處就大打折扣了,如果即要異步又要解決上面的問題,可以使用回調方法。
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>回調</title> </head> <body> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> /*callback是一個當ajax請求成功時的回調方法*/ function getStudent(callback) { $.get("student.json", function(data) { callback(data); }, "json"); } /*調用getStudent函數,傳入參數,參數也是一個函數*/ getStudent(function(stu){ alert(stu.id); }); /*把一個方法作為參數傳遞給另一個方法可以認為是委托,如C++中的函數指針類似*/ getStudent(function(stu){ alert(stu.name); }); </script> </body> </html>
結果:
從這里看回調很完美,其實不然,實際開發中要復雜得多,如當第一個ajax請求完成才可以完成第二個,當第二個完成才可以完成第三個,可能最一個請求要等前面的所有請求都成功時才允許執行或才有條件執行,如
使用ajax編輯用戶信息,先加載用戶對象,再加載省,加載市,加縣,可能代碼會這樣寫:
$.get("url1",function(){ $.get("url2",function(){ $.get("url3",function(){ ... }); }); });
當回調越來越多,嵌套越深,代碼可讀性就會越來越差。如果注冊了多個回調,那更是一場噩夢,幸好從jQuery1.5開始出現了延遲對象(deferred),可以解決這個問題。
2.2、deferred.done
$.ajax()操作完成后,如果使用的是低於1.5.0版本的jQuery,返回的是XHR對象,你沒法進行鏈式操作;如果高於1.5版,返回的是deferred對象,可以進行鏈式操作。
當延遲成功時調用一個函數或者數組函數,功能與原success類似。
語法:deferred.done(doneCallbacks[,doneCallbacks])
返回值:Deferred Object
該參數可以是一個函數或一個函數的數組。當延遲成功時,doneCallbacks被調用。回調執行是依照他們添加的順序。一旦deferred.done()返回延遲對象,延遲對象的其它方法也可以鏈接到了這里,包括增加.done()方法。當延遲解決,doneCallbacks執行使用參數提供給 resolve或 resolveWith方法依照添加的順序調用。
示例代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>延遲對象(deferred)</title> </head> <body> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> console.log("使用方法一"); $.get("student.json", "json").done(function(stu) { console.log(stu.id); }).done(function(stu) { console.log(stu.name); }); console.log("使用方法二"); $.get("student.json", "json").done(function(stu) { console.log(stu.id); }, function(stu) { console.log(stu.name); }); </script> </body> </html>
運行結果:
2.3、deferred.fail
語法:deferred.fail(failCallbacks[,failCallbacks])
返回值:Deferred Object
當延遲失敗時調用一個函數或者數組函數,功能與原回調方法error類似。
該參數可以是一個函數或一個函數的數組。當延遲失敗時,doneCallbacks被調用。回調執行是依照他們添加的順序。一旦deferred.fail()返回延遲對象,延遲對象的其它方法也可以鏈接到了這里,包括增加.done()方法。當延遲解決,doneCallbacks執行使用參數提供給 resolve或 resolveWith方法依照添加的順序調用。
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>延遲對象(deferred)</title> </head> <body> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> $.get("stu.json", "json").done(function(stu) { console.log(stu.name); }).fail(function(xhr, status, errorThrown){ alert("xhr:"+xhr+",status:"+status+",errorThrown:"+errorThrown); }); </script> </body> </html>
運行結果:
2.4、deferred.always
語法:deferred.always(alwaysCallbacks,[alwaysCallbacks])
返回值:Deferred Object
當遞延對象是解決(成功, resolved)或拒絕(失敗,rejected)時被調用添加處理程序,與回調方法complete類似。
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>延遲對象(deferred)</title> </head> <body> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> $.get("student.j", "json").done(function(stu) { console.log(stu.name); }).fail(function(data, status, errorThrown){ console.log("data:"+data+",status:"+status+",errorThrown:"+errorThrown); }).always(function(data, textStatus){ console.log("ajax執行完成,完成狀態:"+textStatus); }); </script> </body> </html>
運行結果
成功時:
失敗時:
2.5、deferred.then
deferred.then(doneFilter [, failFilter ] [, progressFilter ])
添加處理程序被調用時,遞延對象得到解決或者拒絕,一次指定多個事件。
所有三個參數(包括progressCallbacks ,在jQuery的1.7 )可以是一個單獨的函數或一個函數的數組。 其中一個參數,也可以為空,如果沒有該類型的回調是需要的。或者,使用.done()或.fail()僅設置doneCallbacks或failCallbacks。當遞延解決,doneCallbacks被調用。若遞延代替拒絕,failCallbacks被調用。回調按他們添加的順序執行。一旦deferred.then返回延遲對象,延遲對象的其它方法也可以鏈接到了這里,包括增加.then()方法。
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>延遲對象(deferred)</title> </head> <body> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> $.get("student.jsonx", "json").then(function(stu) { console.log(stu.name); }, function(data, status, errorThrown) { console.log("data:" + data + ",status:" + status + ",errorThrown:" + errorThrown); }); </script> </body> </html>
結果:
2.6、應用延遲對象
前面的示例中我們都是使用jQuery ajax返回的deferred對象,其實我們也可以在自定義的代碼中使用deferred對象,恰當的使用deferred對象或以優雅的解決不少問題。
示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>延遲對象(deferred)</title> </head> <body> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> var myTask=function(){ //通過jQuery創建一個延遲對象 var def=$.Deferred(); setTimeout(function(){ //隨機產生一個整數,如果是偶數 var n=Math.round(Math.random()*100); if(n%2==0) { console.log("一個耗時的操作終於完成了,n="+n); def.resolve(); //任務成功完成 }else{ console.log("一個耗時的操作失敗,n="+n); def.reject(); //拒絕,失敗 } },3000); //返回延遲對象,防止中間修改 return def.promise(); } /*當方法myTask執行完成后,添加回調方法*/ $.when(myTask()).done(function(){ console.log("執行成功"); }).fail(function(){ console.log("執行失敗"); }); </script> </body> </html>
失敗時:
成功時:
promise()在原來的deferred對象上返回另一個deferred對象,后者只開放與改變執行狀態無關的方法(比如done()方法和fail()方法),屏蔽與改變執行狀態有關的方法(比如resolve()方法和reject()方法),從而使得執行狀態不能被改變。
示例2:

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Deferred</title> </head> <body> <script src="../js/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> var task = function() { //創建延遲對象 var def = $.Deferred(); //隨機產生一個數 var n = Math.round(Math.random() * 10000); setTimeout(function() { if(n % 2 == 0) { console.log("成功,耗時的任務完成了"); def.resolve(n); //通知成功 } else { console.log("失敗,耗時的任務完成了"); def.reject(n); //通知失敗 } }, n); return def.promise(); }; $.when(task()).done(function(data) { console.log(data); }).fail(function(data) { console.log(data); }); </script> </body> </html>
結果2:
2.7、總結
(1) $.Deferred() 生成一個deferred對象。
(2) deferred.done() 指定操作成功時的回調函數
(3) deferred.fail() 指定操作失敗時的回調函數
(4) deferred.promise() 沒有參數時,返回一個新的deferred對象,該對象的運行狀態無法被改變;接受參數時,作用為在參數對象上部署deferred接口。
(5) deferred.resolve() 手動改變deferred對象的運行狀態為"已完成",從而立即觸發done()方法。
(6)deferred.reject() 這個方法與deferred.resolve()正好相反,調用后將deferred對象的運行狀態變為"已失敗",從而立即觸發fail()方法。
(7) $.when() 為多個操作指定回調函數。
(8)deferred.then() 有時為了省事,可以把done()和fail()合在一起寫,這就是then()方法。如果then()有兩個參數,那么第一個參數是done()方法的回調函數,第二個參數是fail()方法的回調方法。如果then()只有一個參數,那么等同於done()。
(9)deferred.always() 這個方法也是用來指定回調函數的,它的作用是,不管調用的是deferred.resolve()還是deferred.reject(),最后總是執行。
三、跨域
互聯網上的主機由IP來標識,為了方便記憶,創建了域名系統.域名與IP對應,域名的作用是不用讓你記復雜的IP地址,能唯一定位資源,URL的格式是協議://主機名.公司名稱.機構類型.地域類型:端口/路徑,如http://www.zhangguo.com.cn:8080/products/list.do?id=1#a1
3.1、什么是跨域
JavaScript同源策略的限制,A域名下的JavaScript無法操作B或是C域名下的對象,如下所示:
假設頁面:http://store.company.com/index.html
客戶端代碼d05.html,http://localhost:8087/jQuery601_JAVA/d05.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>6.4.9、跨域AJAX請求</title> </head> <body> <h2>6.4.9、跨域AJAX請求</h2> <h2 id="message"></h2> <button type="button" id="btnAjax">ajax請求</button> <script type="text/javascript" src="js/jQuery/jquery.min.js"></script> <script type="text/javascript"> $("#btnAjax").click(function() { $.get("http://localhost:12833/Action/FindUserById.ashx",{"id":1001},function(data){ log(data); },"text"); }); function log(msg) { $("#message")[0].innerHTML += msg + "<br/>"; } </script> </body> </html>
另一個域下面一般處理程序,http://localhost:12833/Action/FindUserById.ashx:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace jQuery601_DotNet.Action { /// <summary> /// 根據用戶編號獲得用戶 /// </summary> public class FindUserById : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; String name = ""; int id = Convert.ToInt32(context.Request.Params["id"]); if (id == 1001) { name = "Mark"; } else if (id == 1002) { name = "Jack"; } context.Response.Write(name); } public bool IsReusable { get { return false; } } } }
運行結果:
3.2、JSONP跨域
JSONP跨域是利用script腳本允許引用不同域下的js實現的,將回調方法帶入服務器,返回結果時回調。
2.1、JSONP跨域原理
客戶端:
<body> <script type="text/javascript"> /*回調方法*/ function show(data) { alert(data); } </script> <script src="http://localhost:8087/JavaScript001/Product?act=area&callback=show" type="text/javascript" charset="utf-8"></script> </body>
服務器:
String callback=request.getParameter("callback"); out.append(callback+"('"+new Date()+"')");
結果:
服務器返回一段javascript,通過指定的方法名調用。從圖中可以看出,使用JSONP的形式調用已經不再是通過XMLHTTPRequest對象,而是同步調用。
3.3、jQuery使用JSONP跨域
在jQuery中內置了實現JSONP跨域的功能,如果指定為json類型,則會把獲取到的數據作為一個JavaScript對象來解析,並且把構建好的對象作為結果返回。為了實現這個目的,他首先嘗試使用JSON.parse()。如果瀏覽器不支持,則使用一個函數來構建。JSON數據是一種能很方便通過JavaScript解析的結構化數據。如果獲取的數據文件存放在遠程服務器上(域名不同,也就是跨域獲取數據),則需要使用jsonp類型。使用這種類型的話,會創建一個查詢字符串參數 callback=? ,這個參數會加在請求的URL后面。服務器端應當在JSON數據前加上回調函數名,以便完成一個有效的JSONP請求。如果要指定回調函數的參數名來取代默認的callback,可以通過設置$.ajax()的jsonp參數。
頁面腳本:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>6.4.9、跨域AJAX請求</title> </head> <body> <h2>6.4.9、跨域AJAX請求</h2> <h2 id="message"></h2> <button type="button" id="btnAjax">ajax請求</button> <script type="text/javascript" src="js/jQuery/jquery.min.js"></script> <script type="text/javascript"> $("#btnAjax").click(function() { $.get("http://localhost:12833/Action/FindUserById.ashx?callback=?",{"id":1001},function(data){ log(data); },"jsonp"); }); function log(msg) { $("#message")[0].innerHTML += msg + "<br/>"; } </script> </body> </html>
服務器一般處理程序:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace jQuery601_DotNet.Action { /// <summary> /// FindUserById 的摘要說明 /// </summary> public class FindUserById : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; String name = ""; int id = Convert.ToInt32(context.Request.Params["id"]); if (id == 1001) { name = "Mark"; } else if (id == 1002) { name = "Jack"; } String callback = context.Request["callback"]; context.Response.Write(callback+"('"+name+"')"); } public bool IsReusable { get { return false; } } } }
運行結果:
服務器Servlet:
package com.gomall.action; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.codehaus.jackson.map.ObjectMapper; import com.gomall.service.IProductService; import com.gomall.service.ProductService; @WebServlet("/Product") public class Product extends HttpServlet { private static final long serialVersionUID = 1L; public Product() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); String act = request.getParameter("act"); IProductService productService = new ProductService(); ObjectMapper mapper = new ObjectMapper(); PrintWriter out=response.getWriter(); if (act.equals("getAll")) { String json = mapper.writeValueAsString(productService.getAll()); out.append(json); } else if (act.equals("area")) { String callback=request.getParameter("callback"); out.append(callback+"('"+new Date()+"')"); } else if (act.equals("getJSONP")) { String callback=request.getParameter("callback"); String json = mapper.writeValueAsString(productService.getAll()); out.append(callback+"("+json+")"); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
客戶端:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>AJAX</title> </head> <body> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> $.get("http://localhost:8087/JavaScript001/Product?callback=?",{act:"getJSONP"},function(data){ $.each(data,function(index,obj){ $("<p/>").html(obj.name).appendTo("body"); }); },"jsonp"); </script> </body> </html>
運行結果:
在jQuery中如果使用JSONP只需要將返回數據類型設置為jsonp就可以了,但是這種方法只支持get請求,不支持post請求;請求是同步的;服務器返回數據要處理,要添加回調函數,麻煩。
3.4、跨域資源共享(CORS)
同源策略(same origin policy)的限制下非同源的網站之間不能發送 ajax 請求的。
w3c 提出了跨源資源共享CORS即Cross Origin Resource Sharing(跨域源資源共享),就是我們所熟知的跨域請求。
跨域資源共享(CORS)是一種網絡瀏覽器的技術規范,它為Web服務器定義了一種方式,允許網頁從不同的域訪問其資源。
CORS與JSONP相比:
1、 JSONP只能實現GET請求,而CORS支持所有類型的HTTP請求。
2、 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得數據,比起JSONP有更好的錯誤處理。
3、 JSONP主要被老的瀏覽器支持,它們往往不支持CORS,而絕大多數現代瀏覽器都已經支持了CORS。
CORS 將請求分為兩類:簡單請求和非簡單請求:
3.4.1.簡單請求
支持get/post/put/delete請求,例如返回Access-Control-Allow-Origin:*,但是不允許自定義header且會忽略cookies,且post數據格式有限制,只支持‘text/plain','application/x-www-urlencoded'and'multipart/form-data',其中’text/plain'默認支持,后面兩種需要下面的預檢請求和服務器協商。
簡單請求對應該規則,因此對簡單請求的定義為:
(1) 請求方法是以下三種方法之一:
HEAD
GET
POST
(2)HTTP的頭信息不超出以下幾種字段:
Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
簡單請求的部分響應頭及解釋如下:
Access-Control-Allow-Origin(必含)- 不可省略,否則請求按失敗處理。該項控制數據的可見范圍,如果希望數據對任何人都可見,可以填寫"*"。
Access-Control-Allow-Credentials(可選) – 該項標志着請求當中是否包含cookies信息,只有一個可選值:true(必為小寫)。如果不包含cookies,請略去該項,而不是填寫false。這一項與XmlHttpRequest2對象當中的withCredentials屬性應保持一致,即withCredentials為true時該項也為true;withCredentials為false時,省略該項不寫。反之則導致請求失敗。
Access-Control-Expose-Headers(可選) – 該項確定XmlHttpRequest2對象當中getResponseHeader()方法所能獲得的額外信息。通常情況下,getResponseHeader()方法只能獲得如下的信息:
Cache-Control Content-Language Content-Type Expires Last-Modified Pragma
當你需要訪問額外的信息時,就需要在這一項當中填寫並以逗號進行分隔
示例:
服務器端:
package com.zhangguo.springmvc08.action; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet(name = "ProductServlet",value = "/pdt") public class ProductServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //設置允許CORS的域名,如果是所有則使用* response.addHeader("Access-Control-Allow-Origin","*"); response.getWriter().write("{\"name\":\"Book\"}"); } }
客戶端:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>跨域</title> </head> <body> <script src="../js/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> //跨域Get請求 $.get("http://localhost:8080/mvc08/pdt", function(data) { console.log("Get:"+data.name); }, "json"); //跨域Post請求 $.post("http://localhost:8080/mvc08/pdt", function(data) { console.log("Post:"+data.name); }, "json"); </script> </body> </html>
結果:
3.4.2.復雜請求
如說你需要發送PUT、DELETE等HTTP動作,或者發送Content-Type: application/json的內容就需要使用復雜請求了。
最先發送的是一種"預請求",此時作為服務端,也需要返回"預回應"作為響應。預請求實際上是對服務端的一種權限請求,只有當預請求成功返回,實際請求才開始執行。預請求以OPTIONS形式發送,當中同樣包含域,並且還包含了兩項CORS特有的內容
代碼:
<script type="text/javascript"> $.ajax({ type:"PUT", url:"http://localhost:8080/mvc08/pdt", contentType:"application/json;charset=utf-8", dataType:"json", success:function(data){ console.log(data); } }); </script>
結果:
Access-Control-Allow-Origin(必含) – 和簡單請求一樣的,必須包含一個域,不能是*號。
Access-Control-Allow-Methods(必含) – 這是對預請求當中Access-Control-Request-Method的回復,這一回復將是一個以逗號分隔的列表。盡管客戶端或許只請求某一方法,但服務端仍然可以返回所有允許的方法,以便客戶端將其緩存。
Access-Control-Allow-Headers(當預請求中包含Access-Control-Request-Headers時必須包含) – 這是對預請求當中Access-Control-Request-Headers的回復,和上面一樣是以逗號分隔的列表,可以返回所有支持的頭部。
Access-Control-Allow-Credentials(可選) – 和簡單請求當中作用相同。
Access-Control-Max-Age(可選) – 以秒為單位的緩存時間,允許時應當盡可能緩存。
3.4.3、CORSFilter
CORSFilter是Apache官方提供一個支持CORS跨域的過濾器,詳細說明: http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html
依賴:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zhangguo.cors</groupId> <artifactId>TestCors</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>TestCors Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Servlet核心包 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <!--JSP --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-catalina</artifactId> <version>8.5.33</version> </dependency> <!-- jackson --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.5.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.2</version> </dependency> </dependencies> <build> <finalName>TestCors</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.0.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.20.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.0</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
添加過濾器,盡量添加在最前面:
<filter> <filter-name>CorsFilter</filter-name> <filter-class>org.apache.catalina.filters.CorsFilter</filter-class> <init-param> <param-name>cors.allowed.origins</param-name> <param-value>https://www.apache.org</param-value> </init-param> <init-param> <param-name>cors.allowed.methods</param-name> <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value> </init-param> <init-param> <param-name>cors.allowed.headers</param-name> <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value> </init-param> <init-param> <param-name>cors.exposed.headers</param-name> <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value> </init-param> <init-param> <param-name>cors.support.credentials</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>cors.preflight.maxage</param-name> <param-value>10</param-value> </init-param> </filter> <filter-mapping> <filter-name>CorsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
如果使用了Spring MVC,請開啟Spring對OPTIONS的支持:
<init-param> <param-name>dispatchOptionsRequest</param-name> <param-value>true</param-value> </init-param>
客戶端:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>跨域</title> </head> <body> <script src="../js/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> $.ajax({ type:"PUT", url:"http://localhost:8080/mvc08/u", contentType:"application/json;charset=utf-8", dataType:"json", success:function(data){ console.log(data); } }); </script> </body> </html>
服務器:
package com.zhangguo.springmvc08.action; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; @WebServlet(name = "UserServlet", value = "/u") public class UserServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().write("{\"name\":\"Book\"}"); } @Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
結果:
web.xml

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!--welcome pages--> <welcome-file-list> <welcome-file>users/tom</welcome-file> </welcome-file-list> <!--配置springmvc DispatcherServlet(中心控制器)--> <servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!--Sources標注的文件夾下需要新建一個spring文件夾--> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/spring-mvc.xml</param-value> </init-param> <init-param> <param-name>dispatchOptionsRequest</param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>CORS</filter-name> <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class> <init-param> <param-name>cors.allowOrigin</param-name> <param-value>http://127.0.0.1:8020</param-value> </init-param> <init-param> <param-name>cors.supportedMethods</param-name> <param-value>POST,GET,OPTIONS,DELETE,PUT</param-value> </init-param> <init-param> <param-name>cors.supportedHeaders</param-name> <param-value>Content-Type,Accept,Origin,XRequestedWith,ContentType,LastModified</param-value> </init-param> <init-param> <param-name>cors.exposedHeaders</param-name> <param-value>SetCookie</param-value> </init-param> <init-param> <param-name>cors.supportsCredentials</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CORS</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
3.4.4、Servlet支持CORS
通過修改請求頭部門信息可以實現Servlet完成復雜跨域功能,示例如下:
后台:
package com.zhangguo.springmvc08.action; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; @WebServlet(name = "ProductServlet", value = "/pdt") public class ProductServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //獲得所有頭部信息 Enumeration<String> items=request.getHeaderNames(); String headers="Content-Type,Accept,Origin,XRequestedWith,ContentType,LastModified,Content-Type,ContentType,content-type"; while(items.hasMoreElements()){ headers+=","+items.nextElement(); } //設置允許CORS的域名,如果是所有則使用* response.addHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8020"); response.addHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, TRACE, OPTIONS,PUT,DELETE"); response.addHeader("Access-Control-Request-Headers", "Origin,X-Requested-With,Content-Type,Accept"); response.addHeader("Access-Control-Allow-Credentials", "true"); response.getWriter().write("{\"name\":\"Book\"}"); } @Override protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.addHeader("Access-Control-Allow-Headers", "Content-type"); doGet(req, resp); } @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } @Override protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
這里在實際使用中有遇到,所有支持的頭部一時可能不能完全寫出來,而又不想在這一層做過多的判斷,沒關系,事實上通過request的header可以直接取到Access-Control-Request-Headers,直接把對應的value設置到Access-Control-Allow-Headers即可
前台:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>跨域</title> </head> <body> <script src="../js/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> $.ajax({ type:"PUT", url:"http://localhost:8080/mvc08/pdt", contentType:"application/json;charset=utf-8", dataType:"json", success:function(data){ console.log(data); } }); </script> </body> </html>
結果:
3.4.5、Spring MVC4.2+ CORS注解
Spring MVC4.2 及以上增加了對CORS的支持
一個應用可能會有多個 CORS 配置,並且可以設置每個 CORS 配置針對一個接口或一系列接口或者對所有接口生效。
對第一種情況,如果想要對某一接口配置 CORS,可以在方法上添加 CrossOrigin 注解:
@CrossOrigin(origins = {"http://localhost:9000", "null"}) @RequestMapping(value = "/test", method = RequestMethod.GET) public String greetings() { return "{\"project\":\"just a test\"}"; }
第二種情況,如果想對一系列接口添加 CORS 配置,可以在類上添加注解,對該類聲明所有接口都有效:
@CrossOrigin(origins = {"http://localhost:8080", "null"}) @RestController public class HomeController{ }
第三種情況,添加全局配置,則需要添加一個配置類:
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:9000", "null") .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") .maxAge(3600) .allowCredentials(true); } }
可以通過添加 Filter 的方式,配置 CORS 規則,並手動指定對哪些接口有效。
@Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("http://localhost:9000"); config.addAllowedOrigin("null"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); // CORS 配置對所有接口都有效 FilterRegistrationBean bean = newFilterRegistrationBean(new CorsFilter(source)); bean.setOrder(0); return bean; }
也可以修改配置文件:
<mvc:cors> <mvc:mapping path="/**" allowed-origins="http://127.0.0.1:8020" allowed-methods="POST,GET, OPTIONS,DELETE,PUT" allowed-headers="Content-Type,ContentType,Access-Control-Allow-Headers, Authorization, X-Requested-With" allow-credentials="true"/> </mvc:cors>
員工管理的跨域綜合示例
后台REST服務:

package com.zhangguo.springmvc08.controller; import com.zhangguo.springmvc08.entity.User; import com.zhangguo.springmvc08.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.List; @RestController @RequestMapping(path = "/emps") public class EmpController extends BaseController { @Autowired UserService userService; @RequestMapping(path = "") public AjaxState getAllEmps(HttpServletRequest request, HttpServletResponse response) { List<User> users=userService.queryAllUsers(); boolean result=users!=null; return new AjaxState(result?"success":"error",users,result?"獲得數據成功!":"獲得數據失敗!"); } @RequestMapping(path = "/{id}", method = RequestMethod.GET) public AjaxState getEmpById(@PathVariable int id) { User user=userService.getUserById(id); boolean result=user!=null; return new AjaxState(result?"success":"error",user,result?"獲得數據成功!":"獲得數據失敗!"); } @RequestMapping(path = "", method = RequestMethod.POST) public AjaxState addEmp(@RequestBody User user) { boolean result=userService.addUser(user); return new AjaxState(result?"success":"error",user,result?"添加成功!":"添加失敗"); } @RequestMapping(path = "", method = RequestMethod.PUT) public AjaxState updateEmp(@RequestBody User user) { boolean result=userService.editUser(user); return new AjaxState(result?"success":"error",user,result?"修改成功!":"修改失敗"); } @RequestMapping(path = "/{id}", method = RequestMethod.DELETE) public AjaxState deleteEmpById(@PathVariable int id) { Boolean result=userService.deleteUser(id); return new AjaxState(result?"success":"error",id,result?"刪除成功!":"刪除失敗"); } } class AjaxState{ public String state; public Object data; public String message; public AjaxState(String state, Object data, String message) { this.state = state; this.data = data; this.message = message; } public AjaxState(){} }
Spring配置文件:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"> <!--啟用spring的一些annotation --> <context:annotation-config/> <!-- 自動掃描該包,使SpringMVC認為包下用了@controller注解的類是控制器 --> <context:component-scan base-package="com.zhangguo.springmvc08"> </context:component-scan> <!--HandlerMapping 無需配置,springmvc可以默認啟動--> <!--靜態資源映射--> <mvc:default-servlet-handler/> <!--如果發現是靜態資源的請求,就將該請求轉由Web應用服務器默認的Servlet處理,如果不是靜態資源的請求,才由DispatcherServlet繼續處--> <!--本項目把靜態資源放在了WEB-INF的statics目錄下,資源映射如下--> <!--<mvc:resources mapping="/css/**" location="/WEB-INF/statics/css/"/>--> <!--<mvc:resources mapping="/js/**" location="/WEB-INF/statics/js/"/>--> <!--<mvc:resources mapping="/image/**" location="/WEB-INF/statics/image/"/>--> <!--但是項目部署到linux下發現WEB-INF的靜態資源會出現無法解析的情況,但是本地tomcat訪問正常,因此建議還是直接把靜態資源放在webapp的statics下,映射配置如下--> <!--<mvc:resources mapping="/css/**" location="/statics/css/"/>--> <!--<mvc:resources mapping="/js/**" location="/statics/js/"/>--> <!--<mvc:resources mapping="/image/**" location="/statics/images/"/>--> <mvc:cors> <mvc:mapping path="/**" allowed-origins="http://127.0.0.1:8020" allowed-methods="POST,GET, OPTIONS,DELETE,PUT" allowed-headers="Content-Type,ContentType,Access-Control-Allow-Headers, Authorization, X-Requested-With" allow-credentials="true"/> </mvc:cors> <!-- 配置注解驅動 可以將request參數與綁定到controller參數上 --> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8"/> </bean> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"> <property name="failOnEmptyBeans" value="false"/> </bean> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> <!-- 對模型視圖名稱的解析,即在模型視圖名稱添加前后綴(如果最后一個還是表示文件夾,則最后的斜杠不要漏了) 使用JSP--> <!-- 默認的視圖解析器 在上邊的解析錯誤時使用 (默認使用html)- --> <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/views/"/><!--設置JSP文件的目錄位置--> <property name="suffix" value=".jsp"/> </bean> <!-- springmvc文件上傳需要配置的節點--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="20971500"/> <property name="defaultEncoding" value="UTF-8"/> <property name="resolveLazily" value="true"/> </bean> </beans>
前端:

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>員工管理</title> </head> <body> <h2>員工管理</h2> <table border="1" width="100%" id="tabEmps"> <tr> <th>編號</th> <th>姓名</th> <th>生日</th> <th>地址</th> <th>電話</th> <th>操作</th> </tr> </table> <p class="loading" style="display: none;"> <img src="img/loading.gif" align="absmiddle">努力加載中... </p> <form id="formEmps"> <fieldset> <legend>用戶信息</legend> <p> <label for="name">姓名:</label> <input name="name" id="name" type="text" required="required" maxlength="32"/> </p> <p> <label for="birthday">生日:</label> <input name="birthday" id="birthday" type="date" required="required" maxlength="8"/> </p> <p> <label for="address">地址:</label> <input name="address" id="address" type="text" required="required" maxlength="128"/> </p> <p> <label for="phone">電話:</label> <input name="phone" id="phone" type="text" required="required" maxlength="11"/> </p> <p> <input id="id" type="hidden" name="id" value=""/> <button type="button" id="btnSubmit">保存</button> </p> </fieldset> </form> <p class="message"> </p> <script src="../js/jquery-1.11.3.min.js"></script> <script> var app = { url: "http://localhost:8080/mvc08/emps", init: function () { $("#btnSubmit").click(app.save); $("#tabEmps").on("click", ".del", app.delete); $("#tabEmps").on("click", ".edit", app.edit); this.binddata(); }, ajax: function (actionType, callback, path, data) { $.ajax({ url: app.url + (path || ""), contentType: "application/json;charset=utf-8", data: JSON.stringify(data) || "{}", type: actionType || "get", dataType: "json", success: function (data) { if (data && data.state == "success") { app.info(data.message); } else if (data && data.state == "error") { app.info(data.message); } else { app.info(data); } if (callback) { callback(data); } }, error: function (XMLHttpRequest, textStatus, errorThrown) { app.info(textStatus + errorThrown); }, beforeSend: function () { $(".loading").show(200); } , complete: function () { $(".loading").hide(200); } }) ; }, binddata: function () { $("#tabEmps tr:gt(0)").remove(); this.ajax("get", function (data) { $.each(data.data, function (index, emp) { var tr = $("<tr/>").data("emp", emp).appendTo("#tabEmps"); $("<td/>").text(emp.id).appendTo(tr); $("<td/>").text(emp.name).appendTo(tr); $("<td/>").text(emp.birthday).appendTo(tr); $("<td/>").text(emp.address).appendTo(tr); $("<td/>").text(emp.phone).appendTo(tr); $("<td/>").html("<a class='del' href='#'>刪除</a> | <a class='edit' href='#'>編輯</a>").appendTo(tr); }); }); }, getEmp: function () { return { "id": $("#id").val(), "name": $("#name").val(), "birthday": $("#birthday").val(), "address": $("#address").val(), "phone": $("#phone").val() }; }, save: function () { var emp = app.getEmp(); if (emp.id) { $("#id").val(""); app.update(emp); } else { app.add(emp); } }, add: function (emp) { app.ajax("POST", function (data) { app.binddata(); }, "", emp); }, update: function (emp) { app.ajax("Put", function (data) { app.binddata(); }, "", emp); }, delete: function () { if (confirm("刪除嗎?")) { var tr = $(this).closest("tr"); var emp = tr.data("emp"); app.ajax("DELETE", function (data) { tr.remove(); }, "/" + emp.id); } }, edit:function(){ var emp = $(this).closest("tr").data("emp"); $("#id").val(emp.id); $("#name").val(emp.name); $("#birthday").val(emp.birthday); $("#address").val(emp.address); $("#phone").val(emp.phone); }, info: function (msg) { $(".message")[0].innerHTML += msg + "<br/>"; } }; app.init(); </script> </body> </html>
運行結果:
其它跨域示例:
.Net服務器一般處理程序代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace jQuery601_DotNet.Action { /// <summary> /// FindUserById 的摘要說明 /// </summary> public class FindUserById : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; context.Response.Headers.Add("Access-Control-Allow-Origin","*"); context.Response.Headers.Add("Access-Control-Allow-Methods", "GET,POST"); String name = ""; int id = Convert.ToInt32(context.Request.Params["id"]); if (id == 1001) { name = "Mark"; } else if (id == 1002) { name = "Jack"; } context.Response.Write(name); } public bool IsReusable { get { return false; } } } }
客戶端腳本:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>6.4.9、跨域AJAX請求</title> </head> <body> <h2>6.4.9、跨域AJAX請求</h2> <h2 id="message"></h2> <button type="button" id="btnAjax">ajax請求</button> <script type="text/javascript" src="js/jQuery/jquery.min.js"></script> <script type="text/javascript"> $("#btnAjax").click(function() { $.get("http://localhost:12833/Action/FindUserById.ashx",{"id":1001},function(data){ log(data); },"text"); }); function log(msg) { $("#message")[0].innerHTML += msg + "<br/>"; } </script> </body> </html>
運行結果:
從上圖可以看到實現跨域且為異步請求。
Java Servlet后台腳本:
package com.gomall.action; import java.io.IOException; import java.io.PrintWriter; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.codehaus.jackson.map.ObjectMapper; import com.gomall.service.IProductService; import com.gomall.service.ProductService; @WebServlet("/Product") public class Product extends HttpServlet { private static final long serialVersionUID = 1L; public Product() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); String act = request.getParameter("act"); IProductService productService = new ProductService(); ObjectMapper mapper = new ObjectMapper(); PrintWriter out=response.getWriter(); if (act.equals("getAll")) { String json = mapper.writeValueAsString(productService.getAll()); out.append(json); } else if (act.equals("area")) { String callback=request.getParameter("callback"); out.append(callback+"('"+new Date()+"')"); } else if (act.equals("getJSONP")) { String callback=request.getParameter("callback"); String json = mapper.writeValueAsString(productService.getAll()); out.append(callback+"("+json+")"); } else if (act.equals("getAllCORS")) { /**向響應的頭部中添加內容*/ response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Methods", "GET,POST"); String json = mapper.writeValueAsString(productService.getAll()); out.append(json); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
客戶端代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>AJAX</title> </head> <body> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> $.get("http://localhost:8087/JavaScript001/Product?act=getAllCORS",function(data){ alert(data); }); </script> </body> </html>
運行結果:
3.4.6、IE8實現CORS跨域的問題
a)、如果認為每次需要修改HTTP頭部比較麻煩,在java中可以使用過濾器,.Net可以使用Module或HttpHandler全局注冊(注冊到Web.Config中,部署時還需要注意)。
b)、如果需要考慮IE8實現CORS則要插件支持,因為IE8並沒有完全支持CORS。
插件名稱:javascript-jquery-transport-xdr
github: https://github.com/gfdev/javascript-jquery-transport-xdr
示例代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>AJAX</title> </head> <body> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <!--[if (IE 8)|(IE 9)]> <script src="js/jquery.transport.xdr.min.js" type="text/javascript" charset="utf-8"></script> <![endif]--> <script type="text/javascript"> $.get("http://localhost:8087/JavaScript001/Product?act=getAllCORS&type=meat-and-filler&format=json",{},function(data){ alert(data); },"json"); </script> </body> </html>
運行結果:
3.4.7、.Net Core跨域
方法一、修改Web.config文件

<system.webServer> <httpProtocol> <!--跨域設置--> <customHeaders> <remove name="Access-Control-Allow-Origin" /> <remove name="Access-Control-Allow-Headers" /> <remove name="Access-Control-Allow-Methods" /> <add name="Access-Control-Allow-Origin" value="*" /> <add name="Access-Control-Allow-Headers" value="Content-Type" /> <add name="Access-Control-Allow-Methods" value="*" /> </customHeaders> </httpProtocol> </system.webServer>
方法二、手動修改HttpResponse對象的頭部信息,與Java一樣
方法三、使用Microsoft.AspNetCore.Cors
#region 程序集 Microsoft.AspNetCore.Cors, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 // C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.cors\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Cors.dll #endregion using System; using Microsoft.AspNetCore.Cors.Infrastructure; namespace Microsoft.AspNetCore.Cors { // [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class EnableCorsAttribute : Attribute, IEnableCorsAttribute { // // 摘要: // Creates a new instance of the Microsoft.AspNetCore.Cors.EnableCorsAttribute with // the default policy name defined by Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions.DefaultPolicyName. public EnableCorsAttribute(); // // 摘要: // Creates a new instance of the Microsoft.AspNetCore.Cors.EnableCorsAttribute with // the supplied policy name. // // 參數: // policyName: // The name of the policy to be applied. public EnableCorsAttribute(string policyName); // public string PolicyName { get; set; } } }
1)、using Microsoft.AspNetCore.Cors;
2)、使用特性 [EnableCors]

using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace WebApplication1.Controllers { [Route("api/[controller]")] [ApiController] [EnableCors] public class TaskController : ControllerBase {} }
配置Startup.cs文件:
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddDefaultPolicy( builder => { builder.WithOrigins("http://www.163.com", "http://www.cnblogs.com"); }); options.AddPolicy("AnotherPolicy", builder => { builder.WithOrigins("http://www.baidu.com") .AllowAnyHeader() .AllowAnyMethod(); }); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }

services.AddCors(p => p.AddDefaultPolicy( adp => adp.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().AllowCredentials()) );
3.5、小結
當然除了兼容老瀏覽器的jsonp跨域與HTML5中的CORS跨域還有很多其它辦法如利用iframe和location.hash、window.name實現的跨域數據傳輸、使用HTML5 postMessage、利用flash等辦法。個人認為CORS應該才是未來主要的跨域選擇,其它的方法都只是hack。
四、彈出層
前面AJAX示例中添加功能如果放在一個彈出層中布局會更加緊湊一些,像登錄,提示信息經常會需要彈出層。
常見的彈出層有:FancyBox,LightBox,colorBox,artDialog,BlockUI,Layer等,這里介紹騰訊開源的artDialog,輕量,實用。
artDialog是一個設計得十分巧妙的對話框組件,小巧身材卻擁有豐富的接口與漂亮的外觀。
特點是自適應內容、優雅的接口、細致的體驗、跨平台兼容、輕量實用。
項目源碼: https://github.com/aui/artDialog
幫助信息: http://img0.zz91.com/huanbao/mblog/artDialog-5.0.4
文檔與示例: http://aui.github.io/artDialog/doc/index.html
AngularJS 版本: https://github.com/aui/angular-popups
使用方法:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>artDialog</title> </head> <body> <button onclick="btn_dialog()"> 彈出框 </button> <button onclick="btn_loading()"> 加載中 </button> <script src="js/jQuery1.11.3/jquery-1.11.3.js" type="text/javascript" charset="utf-8"></script> <script src="js/artDialog6/dialog-min.js" type="text/javascript" charset="utf-8"></script> <link rel="stylesheet" type="text/css" href="js/artDialog6/ui-dialog.css" /> <script type="text/javascript"> function btn_dialog() { var d = dialog({ title: '消息', content: '風吹起的青色衣衫,夕陽里的溫暖容顏,你比以前更加美麗,像盛開的花<br>——許巍《難忘的一天》', okValue: '確 定', ok: function() { var that = this; setTimeout(function() { that.title('提交中..'); }, 2000); return false; }, cancelValue: '取消', cancel: function() { alert('你點了取消按鈕') } }); d.show(); } function btn_loading(){ dialog({ modal:true }).show(); } </script> </body> </html>
運行結果:
屬性:
// 對齊方式 //align: 'bottom left', // 是否固定定位 //fixed: false, // 對話框疊加高度值(重要:此值不能超過瀏覽器最大限制) //zIndex: 1024, // 設置遮罩背景顏色 backdropBackground: '#000', // 設置遮罩透明度 backdropOpacity: 0.7, // 消息內容 content: '<span class="ui-dialog-loading">Loading..</span>', // 標題 title: '', // 對話框狀態欄區域 HTML 代碼 statusbar: '', // 自定義按鈕 button: null, // 確定按鈕回調函數 ok: null, // 取消按鈕回調函數 cancel: null, // 確定按鈕文本 okValue: 'ok', // 取消按鈕文本 cancelValue: 'cancel', cancelDisplay: true, // 內容寬度 width: '', // 內容高度 height: '', // 內容與邊界填充距離 padding: '', // 對話框自定義 className skin: '', // 是否支持快捷關閉(點擊遮罩層自動關閉) quickClose: false, // css 文件路徑,留空則不會使用 js 自動加載樣式 // 注意:css 只允許加載一個 cssUri: '../css/ui-dialog.css',
事件:
/** * 顯示對話框 * @name artDialog.prototype.show * @param {HTMLElement Object, Event Object} 指定位置(可選) */ /** * 顯示對話框(模態) * @name artDialog.prototype.showModal * @param {HTMLElement Object, Event Object} 指定位置(可選) */ /** * 關閉對話框 * @name artDialog.prototype.close * @param {String, Number} 返回值,可被 onclose 事件收取(可選) */ /** * 銷毀對話框 * @name artDialog.prototype.remove */ /** * 重置對話框位置 * @name artDialog.prototype.reset */ /** * 讓對話框聚焦(同時置頂) * @name artDialog.prototype.focus */ /** * 讓對話框失焦(同時置頂) * @name artDialog.prototype.blur */ /** * 添加事件 * @param {String} 事件類型 * @param {Function} 監聽函數 * @name artDialog.prototype.addEventListener */ /** * 刪除事件 * @param {String} 事件類型 * @param {Function} 監聽函數 * @name artDialog.prototype.removeEventListener */ /** * 對話框顯示事件,在 show()、showModal() 執行 * @name artDialog.prototype.onshow * @event */ /** * 關閉事件,在 close() 執行 * @name artDialog.prototype.onclose * @event */ /** * 銷毀前事件,在 remove() 前執行 * @name artDialog.prototype.onbeforeremove * @event */ /** * 銷毀事件,在 remove() 執行 * @name artDialog.prototype.onremove * @event */ /** * 重置事件,在 reset() 執行 * @name artDialog.prototype.onreset * @event */ /** * 焦點事件,在 foucs() 執行 * @name artDialog.prototype.onfocus * @event */ /** * 失焦事件,在 blur() 執行 * @name artDialog.prototype.onblur * @event */
該插件使用比較簡單,可以看示例與源代碼。
五、模板引擎
在AJAX示例中javascript中有大量的html字符串,html中有一些像onclick樣的javascript,這樣javascript中有html,html中有javascript,代碼的偶合度很高,不便於修改與維護,使用模板引擎可以解決問題。
模板引擎(這里特指用於Web開發的模板引擎)是為了使用戶界面與業務數據(內容)分離而產生的,它可以生成特定格式的文檔,用於網站的模板引擎就會生成一個標准的HTML文檔。前后端都有模板引擎,比如T4、FreeMarker、Velocity,這里主要講前端模板引擎:
上圖是常見的一些前端模板引擎,速度相對快的是artTemplate,與artDialog是同一個作者,當然一個好的模板引擎不僅是速度還有很多方面都關鍵。
源碼與幫助: https://github.com/aui/artTemplate
5.1、Hello World
示例代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>artTemplate</title> </head> <body> <div id="result"> </div> <script src="js/artTemplate3/template.js" type="text/javascript" charset="utf-8"></script> <script type="text/html" id="template1"> {{if isShow}} <h2>姓名:{{name}}</h2> <ul> {{each hobbies as hobby index}} <li> {{index+1}} {{hobby}} </li> {{/each}} </ul> {{/if}} </script> <script type="text/javascript"> var data={ isShow:true, name:"Tom", hobbies:["看書","上網","運動","電影","購物"] }; //用數據與模板渲染(render)出結果 var html=template("template1",data); document.getElementById("result").innerHTML=html; </script> </body> </html>
運行結果:
生成的代碼:
<h2>姓名:Tom</h2> <ul> <li> 1 看書 </li> <li> 2 上網 </li> <li> 3 運動 </li> <li> 4 電影 </li> <li> 5 購物 </li> </ul>
5.2、方法
當前最新的版本是4.2與3.x版本有一些區別,4.x版本的方法與選項如下:
// 模板名 filename: null, // 模板語法規則列表 rules: [nativeRule, artRule], // 是否開啟對模板輸出語句自動編碼功能。為 false 則關閉編碼輸出功能 // escape 可以防范 XSS 攻擊 escape: true, // 啟動模板引擎調試模式。如果為 true: {cache:false, minimize:false, compileDebug:true} debug: detectNode ? process.env.NODE_ENV !== 'production' : false, // bail 如果為 true,編譯錯誤與運行時錯誤都會拋出異常 bail: true, // 是否開啟緩存 cache: true, // 是否開啟壓縮。它會運行 htmlMinifier,將頁面 HTML、CSS、CSS 進行壓縮輸出 // 如果模板包含沒有閉合的 HTML 標簽,請不要打開 minimize,否則可能被 htmlMinifier 修復或過濾 minimize: true, // 是否編譯調試版 compileDebug: false, // 模板路徑轉換器 resolveFilename: resolveFilename, // 子模板編譯適配器 include: include, // HTML 壓縮器。僅在 NodeJS 環境下有效 htmlMinifier: htmlMinifier, // HTML 壓縮器配置。參見 https://github.com/kangax/html-minifier htmlMinifierOptions: { collapseWhitespace: true, minifyCSS: true, minifyJS: true, // 運行時自動合並:rules.map(rule => rule.test) ignoreCustomFragments: [] }, // 錯誤事件。僅在 bail 為 false 時生效 onerror: onerror, // 模板文件加載器 loader: loader, // 緩存中心適配器(依賴 filename 字段) caches: caches, // 模板根目錄。如果 filename 字段不是本地路徑,則在 root 查找模板 root: '/', // 默認后綴名。如果沒有后綴名,則會自動添加 extname extname: '.art', // 忽略的變量。被模板編譯器忽略的模板變量列表 ignore: [], // 導入的模板變量 imports: runtime
方法:
與3.x版本類似,但config方法被取消,更多新查看如下鏈接
https://aui.github.io/art-template/zh-cn/docs/api.html
方法:
1)、template(id, data)
根據 id 渲染模板。內部會根據document.getElementById(id)查找模板。
如果沒有 data 參數,那么將返回一渲染函數。
2)、template.compile(source, options)
將返回一個渲染函數。演示
3)、template.render(source, options)
將返回渲染結果。
4)、template.helper(name, callback)
添加輔助方法,讓模板引擎調用自定義的javascript方法。
5)、template.config(name, value)
示例代碼:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>artTemplate</title> </head> <body> <div id="result"> </div> <script src="js/artTemplate3/template.js" type="text/javascript" charset="utf-8"></script> <script type="text/html" id="template1"> $$if isShow## <h2>姓名:$$name##</h2> <ul> $$include "template2"## <!--包含模板2--> </ul> $$/if## </script> <script type="text/html" id="template2"> $$each hobbies as hobby index## <li> $$index+1## $$#hobby## <!--默認會轉義,加#號不轉義--> </li> $$/each## </script> <script type="text/javascript"> var data={ isShow:true, name:"Tom", hobbies:["看書","上網","運動","<b>電影</b>","<i>購物</i>"] }; //邏輯語法開始標簽 template.config("openTag","$$"); //邏輯語法結束標簽 template.config("closeTag","##"); //不轉義 template.config("escape",false); //用數據與模板渲染(render)出結果 var html=template("template1",data); document.getElementById("result").innerHTML=html; </script> </body> </html>
運行結果:
4.x示例:

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>模板引擎</title> </head> <body> <div id="div1"> </div> <script src="../js/artTemplate4/lib/template-web.js" type="text/javascript" charset="utf-8"></script> <script type="text/html" id="t1"> <div> {{if isShow}} <h2>{{name}}的愛好列表</h2> <table width="50%" border="1"> <tr> <th>序號</th> <th>名稱</th> <th>操作</th> </tr> {{each hobbies obj i}} <tr> <td>{{i+1}}</td> <td>{{obj}}</td> <td> <a href="#" data-index="{{i}}" class="del">刪除</a> </td> </tr> {{/each}} </table> {{/if}} </div> </script> <script type="text/javascript"> var data = { isShow: true, name: "張學友", hobbies: ["看書", "<b>上網</b>", "運動", "電影", "購物"] }; var src = document.getElementById("t1"); var html = template.render(src.innerHTML, data, { escape: false }); console.log(html); document.getElementById("div1").innerHTML = html; </script> </body> </html>
結果:
5.3、與AJAX結合應用
示例腳本:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>商品管理</title> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <style type="text/css"> @CHARSET "UTF-8"; * { margin: 0; padding: 0; font-family: microsoft yahei; font-size: 14px; } body { padding-top: 20px; } .main { width: 90%; margin: 0 auto; border: 1px solid #777; padding: 20px; } .main .title { font-size: 20px; font-weight: normal; border-bottom: 1px solid #ccc; margin-bottom: 15px; padding-bottom: 5px; color: blue; } .main .title span { display: inline-block; font-size: 20px; background: blue; color: #fff; padding: 0 8px; background: blue; } a { color: blue; text-decoration: none; } a:hover { color: orangered; } .tab td, .tab, .tab th { border: 1px solid #777; border-collapse: collapse; } .tab td, .tab th { line-height: 26px; height: 26px; padding-left: 5px; } .abtn { display: inline-block; height: 20px; line-height: 20px; background: blue; color: #fff; padding: 0 5px; } .btn { height: 20px; line-height: 20px; background: blue; color: #fff; padding: 0 8px; border: 0; } .abtn:hover, .btn:hover { background: orangered; color: #fff; } p { padding: 5px 0; } fieldset { border: 1px solid #ccc; padding: 5px 10px; } fieldset legend { margin-left: 10px; font-size: 16px; } .pic { height: 30px; width: auto; } #divFrom { display: none; } </style> </head> <body> <div class="main"> <h2 class="title"><span>商品管理</span></h2> <table border="1" width="100%" class="tab" id="tabGoods"> <tr> <th>編號</th> <th>圖片</th> <th>商品名</th> <th>價格</th> <th>詳細</th> <th>操作</th> </tr> </table> <p style="color: red" id="message"></p> <p> <a href="#" class="abtn" id="btnSave">添加</a> <input type="submit" value="刪除選擇項" class="btn" /> </p> <div id="divFrom"> <form id="formPdt"> <fieldset> <legend>添加商品</legend> <p> <label for="name"> 名稱: </label> <input type="text" name="name" id="name" /> </p> <p> <label for="price"> 價格: </label> <input type="text" name="price" id="price" /> </p> <p> <label for="detail"> 詳細: </label> <textarea id="detail" name="detail" cols="60"></textarea> </p> </fieldset> </form> </div> </div> <link rel="stylesheet" type="text/css" href="js/artDialog6/ui-dialog.css" /> <script src="js/artTemplate3/template.js" type="text/javascript" charset="utf-8"></script> <script src="js/jQuery1.11.3/jquery-1.11.3.min.js" type="text/javascript" charset="utf-8"></script> <script src="js/artDialog6/dialog-min.js" type="text/javascript" charset="utf-8"></script> <!--[if (IE 8)|(IE 9)]> <script src="js/jquery.transport.xdr.min.js" type="text/javascript" charset="utf-8"></script> <![endif]--> <script type="text/html" id="tmpl"> {{each list as pdt}} <tr> <td>{{pdt.id}}</td> <td><img src="http://localhost:8087/JavaScript001/images/{{pdt.picture}}" class="pic"></td> <td>{{pdt.name}}</td> <td>{{pdt.price | round:'¥'}}</td> <td>{{pdt.detail}}</td> <td> <a href="#" class="abtn del" data-id={{pdt.id}}>刪除</a> </td> </tr> {{/each}} </script> <script type="text/javascript"> var app = { url: "http://localhost:8087/JavaScript001/", //提供服務的域名 add: function() { var d = dialog({ title: '添加商品', content: $('#divFrom').html(), okValue: '添加', modal: true, backdropOpacity: 0.3, ok: function() { var that = this; $.ajax({ type: "post", data: $(".ui-dialog #formPdt").serialize() + "&act=add", success: function(data) { if(data) { app.log("添加成功!"); app.loadAll(); that.close(); } else { app.log("添加失敗!"); } } }); return false; }, cancelValue: '關閉', cancel: function() { alert('你點了取消按鈕') }, onclose: function() { alert("關閉了"); } }); d.show(); }, del: function() { id = $(this).data("id"); var that = $(this); $.ajax({ type: "get", data: { "id": id, "act": "del" }, success: function(data) { if(data) { that.closest("tr").remove(); app.log("刪除成功!"); } else { app.log("刪除失敗!"); } } }); }, loadAll: function() { $.ajax({ type: "get", data: { "act": "getAllCORS" }, success: function(data) { $("#tabGoods tr:gt(0)").remove(); $("#tabGoods").append(template("tmpl",{list:data})); } }); }, init: function() { /*動態綁定刪除事件*/ $("#tabGoods").on("click", "a.del", {}, app.del); /*綁定添加事件*/ $("#btnSave").click(app.add); /*設置全局AJAX默認值*/ $.ajaxSetup({ dataType: "json", url: app.url + "Product?type=meat-and-filler&format=json", beforeSend: app.ajaxBefore, complete: app.clearMsg, error: function(xhr, textStatus, errorThrown) { app.log("錯誤" + textStatus + errorThrown); } }); //為模板引擎定義輔助函數 template.helper("round",function(value,mark){ return (mark||"")+Math.round(value); }); this.loadAll(); }, clearMsg: function() { this.box.remove(); }, ajaxBefore: function() { this.box = dialog({ modal: true }); this.box.show(); }, log: function(msg) { $("#message").html(msg); } }; app.init(); </script> </body> </html>
運行結果:
六、示例下載
coding: https://coding.net/u/zhangguo5/p/javascript001/git
服務器: https://coding.net/u/zhangguo5/p/javascript001_java/git
github: https://github.com/zhangguo5/javascript01
https://git.dev.tencent.com/zhangguo5/javascriptpro.git
第二版:
前端:https://git.coding.net/zhangguo5/javascript_01.git
后台:https://git.coding.net/zhangguo5/SpringMVC08.git
七、視頻
http://www.bilibili.com/video/av17173253/
https://www.bilibili.com/video/av16991874/
八、作業
8.1、請完成一個簡單的分布式應用,使用Java作為服務器對外發布服務,PC客戶端實現“品牌或商品”的管理,移動端實現如下列表:
- a)、分析出數據庫的設計,建庫、建表 (MySQL)
- b)、創建后台項目,實現5個服務,可以使用RETSFul (IDEA)
- c)、創建PC Web項目(HBuilder),使用AJAX消費后台提供的5個服務 ,完成增加、刪除、修改、查詢功能
- d)、創建App項目(HBuilder),先完成頁面的靜態布局,使用AJAX調用服務
- e)、注意跨域、可以選擇三方的UI框架,但界面需完全一樣
- f)、在PC Web中,添加,刪除,編輯、詳細功能請使用artDialog彈出層
- g)、在PC Web與App中請使用artTemplate渲染頁面HTML
系統結構如下: