從零開始學 Web 之 Ajax(七)跨域

你們好,這裏是「 從零開始學 Web 系列教程 」,並在下列地址同步更新......javascript

在這裏我會從 Web 前端零基礎開始,一步步學習 Web 相關的知識點,期間也會分享一些好玩的項目。如今就讓咱們一塊兒進入 Web 前端學習的冒險之旅吧!php

1、跨域

跨域這個概念來自一個叫 「同源策略」 的東西。同源策略是瀏覽器上爲了安全考慮實施的很是重要的安全機制。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

一、跨域的實現

1.一、引入外部 js 文件

咱們能夠經過 script 標籤,用 script 標籤的屬性引入一個外部文件,這個外部文件是不涉及到同源策略的影響的。ajax

<script src="http://www.example.com/dir/xxx.js"></script>

而後,這個外部文件中有一個或幾個方法的調用,這些方法的定義在本身的界面文件中,而咱們想要的是方法的參數,能夠在本身定義的方法中拿到。這就是跨域的本質。

1.二、引入外部 PHP 文件

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>

1.三、動態建立 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>

1.四、動態指定回調函數名稱

還記得咱們 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 獲取跨域數據

相似 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:能夠不須要。

五、完善myAjax方法達到能獲取同源數據和非同源數據

主要借鑑了 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 片斷渲染到界面中

6.一、改造百度提示案例

仍是以百度提示詞爲例:

好比我想生成類型以下格式標籤的代碼片斷:

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

6.二、artTemplate的經常使用語法

示例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 類型的字符串。

相關文章
相關標籤/搜索