你们好,这里是「 从零开始学 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 类型的字符串。