從零開始學習前端JAVASCRIPT — 13、Ajax-前后端異步交互以及Promise-異步編程的改進


(注:本章講解涉及部分后端知識,將以php提供數據的方式進行相應的demo實現)

一、ajax的概念

全稱:Asynchronous Javascript And Xml

AJAX不是一種新的編程語言,而是一種用於創建更快更好以及交互性更強的WEB應用程序技術,該技術在98年前后得到了應用。通過AJAX,你的JS可以通過JS的XMLHttpRequest對象在頁面不重載的情況下與服務器直接進行通信。這樣可以在服務器請求到想要的數據,而不是整個頁面。AJAX的核心就是JS的XMLHttpRequest對象。xhr對象是在IE5中首次引入,它是一種支持異步請求的對象。

二、ajax的優勢

  • 無刷新更新數據。
  • 異步與服務器通信。
  • 基於標准被廣泛支持。
  • 前端與后端分離。
  • 節省帶寬。

三、編寫步驟

1.創建XMLHttpRequest對象。

所有現代瀏覽器(IE7+,chrome,firefox,opera,safari)均內建XMLHttpRequest對象。但是IE5、6使用ActiveXObject對象。

function getAjax() { var  xmlhttp = null; if(window.ActiveXObject){    //針對IE
 xmlhttp = new ActiveXObject(’Microsoft.XMLHTTP’); } else if(window.XMLHttpRequest){ xmlhttp = new XMLHttpRequest(); } return xmlhttp; }

2.打開與Server的連接,指定發送方式、URL以及權限等。

open方法:創建新的HTTP請求,並指定此請求的方法,URL以及驗證信息。

xhr.open(type, url, async, user, password);

type:HTTP請求方式,GET、POST等。大小寫不敏感。

url:請求地址。(get請求如果有傳值直接以url?param=value的方式傳遞。post的方式直接在發送指令的時候傳遞)

async:布爾型,請求是否為異步方式。默認為true。如果為真,當狀態改變時會調用onreadystatechange屬性指定的回調函數。(可選)

注釋:當您使用 async=false 時,請不要編寫 onreadystatechange 函數 - 把代碼放到 send() 語句后面即可:

xmlhttp.open("GET","test.txt",false); xmlhttp.send(); document.getElementById("myDiv").innerHTML=xmlhttp.responseText;

     user:如果服務器需要驗證,此處指定用戶名,如果未指定,當服務器需要驗證時,會彈出驗證窗口。少用僅了解

password:驗證信息中的密碼部分,如果用戶名為空,則此值將會被忽略。(少用僅了解)

注:在AJAX中,其實我們就是來模擬正常的表單提交數據。正常的表單在POST數據時,會發送Content-Type字段,所以我們在AJAX中就要指定該字段值為application/x-www-form-urlencoded。並且對字段名稱和值進行編碼處理在發送。使用setRequestHeader:單獨指定請求的某個HTTP頭。

注:一些特殊字符可能與代碼中的字符沖突(如URL中分隔符&等等),故數據應使用encocdeURIComponent()函數進行編碼。

3.發送指令。

send():發送請求到HTTP服務器並接收回應。

此方法的同步或異步方式取決於open方法中的async參數,如果async為true,此方法將立即返回,如果為false,此方法將會等待請求完成或者超時時才會返回。

xhr.send(body);

body:通過此請求發送的數據。GET請求設置為null即可。

post和get方式實現ajax的不同之處 

xmlhttp.open("GET","demo_get2.asp?fname=Bill&lname=Gates",true); xmlhttp.send(); xmlhttp.open("POST","ajax_test.asp",true); xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xmlhttp.send("fname=Bill&lname=Gates"); //傳參方式的不同以及編碼方式的不同post提交需要被別,則需要指定編碼方式與get的方式一樣

   4.等待並接收服務器返回的處理結果。

5.客戶端接收。

6.釋放XMLHttpRequest對象。

四、回調函數

通過onreadystatechange屬性指定readystate屬性改變時的事件處理回調函數。

xhr.onreadystatechange = function(){}

readyState屬性:返回請求的當前狀態。

  • 0:對象已建立,尚未初始化(未調用open方法)。
  • 1:對象已建立,尚未調用send方法。
  • 2:send方法已調用。但是當前的狀態以及HTTP狀態未知。
  • 3:開始接收數據,因為響應以及HTTP頭不全,這時通過responseBody和responseText獲取部分數據會出現錯誤。
  • 4:數據接收完畢,此時可以通過responseBody和responseText獲取完整的響應數據。

status屬性:返回當前請求的狀態碼。

  • 200 OK:請求文檔已經找到,並正確返回。
  • 304 Not Modified:擁有一個本地的緩存副本,服務器端內容與此相同。
  • 403 Forbidden:請求者對所請求的文檔不具有相應的權限。
  • 404 Not Found:請求的文檔沒找到。

statusText屬性:返回當前請求的響應行信息。

responseXML屬性:將響應信息格式化為XML Document對象返回。

responseText屬性:將響應信息作為字符串返回。

 

五、JS解析JSON 

 JSON:Javascript Object Notation,一種輕量級的基於文本的數據交換格式,易於人閱讀和編寫,也能提高網絡傳輸速率。

 ES5新增的兩個方法:

 JSON.parse:將JSON字符串數據轉換為JSON對象。

 JSON.stringify:將JSON對象轉換為JSON字符串。

 注:1、瀏覽器支持:IE8+。

  2、JSON格式的字符串里面的key或者字符串型的value都必須用雙引號包裹

六、局部數據刷新 

demo講解上述提到的知識點:操作相應的DOM節點(例如評論列表的分頁效果)

html頁面

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>新聞列表</title>
    <style type="text/css">
        *{ margin:0; padding:0; } .box{ width: 80%; margin: 0 auto; } .newsTitle{ font-size: 24px; text-align: center; margin: 0 auto; padding: 20px 10px; font-weight: normal; letter-spacing: 10px; } .newsList{ display: block; margin: 0 auto; line-height: 50px; font-size: 16px; list-style: none; } .newsList li{ border-top: 1px dashed #f4f4f4; padding: 0 20px; } .newsList li:nth-child(odd){ background: #f3f3f3; } .newsPage{ display: block; margin: 0 auto; text-align: center; } .newsPage li{ margin:20px 5px; display: inline-block; padding: 5px 10px; border:1px solid #f3f3f3; font-size: 14px; } </style>
</head>
<body>
    <div class="box">
        <h3 class="newsTitle">新聞列表</h3>
        <ul id="newsList" class="newsList">
            <li>東航首位外籍女機長:來中國是最好的選擇</li>
            <li>東航首位外籍女機長:來中國是最好的選擇</li>
            <li>東航首位外籍女機長:來中國是最好的選擇</li>
            <li>東航首位外籍女機長:來中國是最好的選擇</li>
            <li>東航首位外籍女機長:來中國是最好的選擇</li>
        </ul>    
        <ul id="newsPage" class="newsPage">
            <li data-page="1">1</li>
            <li data-page="2">2</li>
            <li data-page="3">3</li>
            <li data-page="4">4</li>
            <li data-page="5">5</li>
            <li data-page="6">6</li>
            <li data-page="7">7</li>
            <li data-page="8">8</li>
            <li data-page="9">9</li>
            <li data-page="10">10</li>
        </ul>        
    </div>
</body>
<script type="text/javascript" src="ajax.js"></script>
</html>

 js頁面

var oNewsPage=document.getElementById('newsPage'); var oNewsPage=Array.from(oNewsPage.children); var oNewsList=document.getElementById('newsList'); oNewsPage.forEach(function (value) { value.onclick=function () { oNewsList.innerHTML = ""; //獲取頁碼值
        var pageNum = value.getAttribute("data-page"); /*步驟一:創建XMLHttpRequest對象*/
        var xhr = new XMLHttpRequest(); /*步驟二:請求配置*/
        var pageNum = "pageNum="+ pageNum; //xhr.open('get','ajax.php?'+pageNum,true);
        xhr.open('post','ajax.php',true); /*步驟四:接受返回結果*/ xhr.onreadystatechange=function () { //根據狀態返回碼判定請求的狀態
            if (xhr.readyState === 4 && xhr.status === 200) { //獲取返回結果
                var newsList = JSON.parse(xhr.responseText)//返回結果通過json將字符串進行轉換 //數據結果渲染到頁面
                newsList.forEach( v => { var oLi = document.createElement('li'); oLi.innerHTML = v.title; oNewsList.appendChild(oLi); }); } } /*步驟三:發送請求*/
        //post請求需要設置編碼格式 否則無法解析數據
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send(pageNum);//post方式將參數傳遞的方式
 } })

 php后台頁面

<?php $pageNum=$_POST['pageNum']; //接收參數頁碼 //通過頁碼判斷加載的內容
    if ($pageNum == 1) { //通過二維數組存儲數據
        $arr = [ [ 'id' => 1, 'title' => '東航首位外籍女機長:來中國是最好的選擇' ], [ 'id' => 2, 'title' => '南航首位外籍女機長:來中國是最好的選擇' ], [ 'id' => 3, 'title' => '國際航空首位外籍女機長:來中國是最好的選擇' ] ]; } else if ($pageNum == 2) { $arr=[ [ 'id' =>4, 'title'=>'世界那么大不如出去走走' ], [ 'id'=>5, 'title'=>'今天陽光明媚適合出去走走' ] ]; } else{ $arr=[ [ 'id' =>6, 'title'=>'我是第三頁以后的內容' ], [ 'id'=>7, 'title'=>'我是第三頁以后的內容' ] ]; } echo json_encode($arr); ?>

七、前后端分離(ajax函數的封裝)

 后台只管數據輸出和業務邏輯處理,前端負責交互邏輯和界面展示。簡單的說:前端靜態頁面中沒有有后台程序代碼,后台輸出不帶有HTML標簽的數據。前后端分離靠ajax來實現數據的交互。

demo通過封裝的ajax實現加載更多的功能以及通過事件源對象刪除單條數據(本來打算將源碼托管到GitHub,賬號丟失)

*文件夾目錄 

 1.html頁面

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>新聞列表</title>
    <link rel="stylesheet" href="styles/ajax.css">
</head>
<body>
    <div class="box">
        <h3 class="newsTitle">新聞列表</h3>
        <ul id="newsList" class="newsList">
            <li> 冬天來了,春天還會遠么 <a class="deleteBtn" href="#">&times;</a>
            </li>
            <li> 面朝大海,幸福花開 <a class="deleteBtn" href="#">&times;</a>
            </li>
            <li> Nothing is impossible! 
                <a class="deleteBtn" href="#">&times;</a>
            </li>
            <li> 水能載舟亦能覆舟 <a class="deleteBtn" href="#">&times;</a>
            </li>
            <li> 中國是最好的選擇 <a class="deleteBtn" href="#">&times;</a>
            </li>
        </ul>    
        <div id="loadMoreInfo" class="loadMoreInfo">
            <img id="loadingImg" class="loading" src="images/timg.gif" /> 加載更多... </div>    
    </div>
</body>
<script type="text/javascript" src="scripts/common.js"></script>
<script type="text/javascript" src="scripts/ajax.js"></script>
</html>
ajax.html

 2.css文件

@charset "utf-8"; /* CSS Document */
*{ margin:0; padding:0; } body{ background: #7CCD7C; } .box{ width: 80%; margin: 0 auto; } .newsTitle{ font-size: 24px; text-align: center; margin: 0 auto; padding: 20px 10px; font-weight: normal; letter-spacing: 10px; } .newsList{ display: block; margin: 0 auto; line-height: 50px; font-size: 16px; list-style: none; } .newsList li{ padding: 0 20px; box-shadow: 0 0 10px #eeeeee; } .box .newsList li:hover{ background: #B0E2FF; } .newsList li:nth-child(odd){ background: #f3f3f3; } .newsList li:nth-child(even){ background: #eeeeee; } .deleteBtn{ float: right; text-decoration: none; font-size: 16px; color: #555555; } .loadMoreInfo{ margin-top: 10px; text-align: center; line-height: 50px; font-size: 16px; background: #ffffff; cursor: pointer; vertical-align: middle; margin-bottom: 20px; } .loadMoreInfo img{ display: none; width: 20px; height: 20px; vertical-align: middle; }
ajax.css

 3.公共js(封裝的$函數以及ajax函數)

/* 封裝$函數 參數說明: id:標簽的id屬性值 */ function $(id) { return document.getElementById(id); } /* 封裝ajax函數 參數說明:對象作為參數不用考慮參數順序問題 type:請求類型; url:請求訪問地址; param:請求數據,支持對象也支持字符串; asyn:是否異步加載,boolean類型,true異步,false同步 beforesend:回調函數在發送請求之前執行 success:回調函數,請求成功時執行 complete:回調函數,請求完成后執行 */ function ajax({type,url,param,asyn=true,beforesend,success,complete}) { //創建XMLHttpRequest對象
    var xhr = new XMLHttpRequest(); //param參數為對象時進行拼接字符串
    if (param && typeof(param) === 'object') { var str; for(var child in param){ //encodeURIComponent編碼參數,避免特殊字符分割時丟失
            str+=encodeURIComponent(child) + '=' + encodeURIComponent(param[child]) + '&' } param=str.slice(0,-1);//截取拼接完成后最后的&符
 } //get請求對url傳參進行拼接
    if (type.toUpperCase()==="GET" && param) { url+='?'+param; } //請求配置
 xhr.open(type , url, asyn); //獲取返回的數據
    xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { //獲取返回的數據並轉換
 success(xhr.responseText); } //請求完成后的函數
        complete && complete(); } //發送請求
    beforesend && beforesend(); if (type.toUpperCase()==="POST") { //post請求設置編碼方式
        xhr.setRequestHeader("Content-Type",'application/x-www-form-urlencoded') //post請求時通過send向body傳參(無論param參數是否為空,必須傳)
 xhr.send(param); } else{ //get請求發送請求
 xhr.send(); } }
common.js

 4.頁面js

var oLoadMoreInfo = $("loadMoreInfo"); var oNewsList = $("newsList"); var oLoadingImg = $("loadingImg"); oLoadMoreInfo.onclick=function () { ajax({ type:'post', url:'php/ajax.php', beforesend:function () { //發送前顯示加載過程中的動態效果
            oLoadingImg.style.display="inline-block" }, success:function (data) { //接受返回的數據進行轉換並渲染到頁面
            var data=JSON.parse(data); data.forEach(function (value) { var li=document.createElement("li"); li.innerHTML=value+'<a class="deleteBtn" href="#">&times;</a>'; oNewsList.appendChild(li); }) }, //完成后顯示渲染出來的網頁效果,關閉等待效果
 complete:function () { oLoadingImg.style.display="none" } }) } newsList.onclick=function (ev) { var e=ev||window.event; //獲取事件原對象
    var tar=e.target||e.srcElement; //判斷事件原對象是否為a標簽nodenName的值都是大寫
    if (tar.nodeName==="A") { oNewsList.removeChild(tar.parentNode); } }
ajax.js

 5.php后台

<?php sleep(2);//本地測試資源加載速度過快無法看出加載的動態效果
  $news=[ '東航首位外籍女機長:來中國是最好的選擇', '南航首位外籍女機長:來中國是最好的選擇', '國際航空首位外籍女機長:來中國是最好的選擇', '東航首位外籍女機長:來中國是最好的選擇', '南航首位外籍女機長:來中國是最好的選擇', '國際航空首位外籍女機長:來中國是最好的選擇', '東航首位外籍女機長:來中國是最好的選擇', '南航首位外籍女機長:來中國是最好的選擇', '國際航空首位外籍女機長:來中國是最好的選擇', '東航首位外籍女機長:來中國是最好的選擇', '南航首位外籍女機長:來中國是最好的選擇', '國際航空首位外籍女機長:來中國是最好的選擇' ]; echo json_encode($news, JSON_UNESCAPED_UNICODE); /* PHP的json_encode來處理中文的時候, 中文都會被編碼, 變成不可讀的, 類似”\u***”的格式,PHP5.4,JSON_UNESCAPED_UNICODE:Json不要編碼Unicode*/
ajax.php

 6.圖片資源

 

八、ajax的依賴調用

在使用ajax異步調用的時候,可能碰到同時調用多個ajax的情況,而且多個ajax之間還存在依賴關系。

引用回調函數的嵌套說明ajax的以來調用: 

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>回調函數的嵌套</title>
</head>
<body>
    
</body>
<script type="text/javascript"> function a(d, e) { d && d(e); } function b(f) { console.log('函數b!'); f && f(); } a(b, function() { console.log('實參e(回調函數)'); }); /*回調函數的簡單理解:函數在方法中以參數的方式進行傳遞*/
/*這里提供兩種形式理解回調函數的嵌套*/
/* function a(d, e) { d && d(e); } a( function b(f){ console.log('函數b!'); f && f(); }, function() { console.log('實參e(回調函數)'); } );*/

</script>
</html>
回調函數的嵌套

 demo:逐個異步驗證驗證碼、用戶名和密碼,只有用戶通過,才能異步驗證密碼。

 此處只給出部分重要代碼逐個調用封裝的ajax方法(前后端分離的demo封裝的ajax方法)進行異步驗證驗證碼、用戶名和密碼: 

            // 表單提交事件用純ajax實現表單驗證 // 驗證驗證碼
            var sCaptcha = oCaptcha.value; ajax({ type: 'POST', url: 'login.php', data: {captcha: sCaptcha}, success: function (data) { if(data == 1) { // 驗證賬號
                        var sAccount = oAccount.value; ajax({ type: 'POST', url: 'login.php', data: {account: sAccount}, success: function (data) { if(data == 1) { // 驗證密碼
                                    var sPassword = oPassword.value; ajax({ type: 'POST', url: 'login.php', data: {password: sPassword}, success: function (data) { if(data == 1) { oShow.innerHTML = '登錄成功!'; } else { oShow.innerHTML = '密碼錯誤!'; } } }); } else { oShow.innerHTML = '賬號錯誤!'; } } }); } else { oShow.innerHTML = '驗證碼錯誤!'; } } });
ajax異步調用demo 

九、Promise介紹

Promise函數,是一個構造函數,它實際上是對回調函數的一種封裝 對異步編程的一種改進。

1>Promise對象的特點:

1.Promise對象代表一個異步操作,有三種狀態:Pending(進行中)、Resolved(已完成,又稱Fulfilled)和Rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。

2.一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從Pending變為Resolved和從Pending變為Rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果。就算改變已經發生了,你再對Promise對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

Promise的優點:

  Promise對象可以將異步操作以同步操作的流程表達出來,避免層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操作更加容易。

Promise的缺點:

1.無法取消Promise,一旦新建它就會立即執行,無法中途取消。

2.如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。

3.當處於Pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

2>Promise的基本用法 

Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由JavaScript引擎提供,不用自己部署。(Promise新建后就會立即執行。)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Promise</title>
</head>
<body>
    <script>
        // 創建promise對象(創建后就被執行)
        var oP = new Promise(function (resolve, reject) { console.log('立即執行'); resolve(); reject(); }); </script>
</body>
</html>

 

resolve函數的作用是,將Promise對象的狀態從“未完成”變為“成功”(即從Pending變為Resolved),在異步操作成功時調用,並將異步操作的結果,作為參數傳遞出去;

reject函數的作用是,將Promise對象的狀態從“未完成”變為“失敗”(即從Pending變為Rejected),在異步操作失敗時調用,並將異步操作報出的錯誤,作為參數傳遞出去。

1)Promise的then方法:指定Resolved狀態和Reject狀態的回調函數。 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Promise</title>
</head>
<body>
    <script>
    // 創建promise對象
    var oP = new Promise(function (resolve, reject) { console.log('立即執行'); // resolve();
        /*狀態由pending 變為執行成功, 調用then函數的第一個回調函數*/ reject(); /*想要查看reject對應的then的回調函數, 可將resolve()函數注釋*/
        /*reject函數單獨執行異常,將then的第二 個參數回調函數注釋可查看*/ }); oP.then(function() { console.log('第一個'); //只執行第一個then回調函數(一旦執行狀態就無法改變)
 }, function () { console.log('第二個'); }); </script>
</body>
</html>

then方法可以接受兩個回調函數作為參數。 

第一個回調函數:Promise對象的狀態變為Resolved時調用 

第二個回調函數:Promise對象的狀態變為Reject時調用。其中,第二個函數是可選的,不一定要提供。這兩個函數都接受Promise對象傳出的值作為參數。

then方法支持鏈式寫法且同時調用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Promise</title>
</head>
<body>
    <script>
        // 創建promise對象
        var oP = new Promise(function (resolve, reject) { console.log('立即執行'); resolve(); reject(); }); // 鏈式寫法
 oP.then(function() { console.log('第一個'); }, function () { console.log('第二個'); }).then(function() { console.log('第三個'); }).then(function () { console.log('第四個'); }); </script>
</body>
</html>

 

只有被Promise封裝過了,回調才能保證順序。 也就是Promise(意為承諾)設計的初衷。但前一個方法必須在它最后執行resolve(),后一個方法才可以開始。 如果執行了reject(),則進入catch()方法。這里不能單純的理解為 resolve就是success,reject就是error 現就職於阿里的大名鼎鼎的阮一峰老師,喜歡管它叫狀態機,這是非常恰當的叫法。

2)catch方法:catch方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。Promise對象的錯誤具有“冒泡”性質,會一直向后傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲。一般來說,不要在then方法里面定義Reject狀態的回調函數(即then的第二個參數),總是使用catch方法。如果沒有使用catch方法指定錯誤處理的回調函數,Promise對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Promise</title>
</head>
<body>
    <script>
        // 創建promise對象
        var oP = new Promise(function (resolve, reject) { console.log('立即執行'); // resolve();
 reject(); }); // 鏈式寫法
 oP.then(function() { console.log('第一個'); }, function () { console.log('第二個'); }) // catch捕獲錯誤
        oP.catch(function () { console.log('出錯啦!'); }); // console.dir(oP);
    </script>
</body>
</html>

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Promise</title>
</head>
<body>
    <script>
        // 創建promise對象
        var oP = function(){ return new Promise(function (resolve, reject) { //報錯x未定義,無輸出(為捕獲異常)
            resolve(x+2); })}; oP().then(function() { console.log('執行正確'); }) </script>
</body>
</html>

3>promise構造函數身上的方法

 1)Promise.all()Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。

 var p = Promise.all([p1,p2,p3]);

promise.all方法接受一個數組作為參數,p1、p2、p3都是Promise對象的實例。

p的狀態由p1、p2、p3決定,分成兩種情況。

(1)只有p1、p2、p3的狀態都變成fulfilled,p的狀態才會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。

(2)只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Promise</title>
</head>
<body>
    <script>
    var p1 = new Promise(function (resolve, reject) { console.log('p1'); resolve(); }); var p2 = new Promise(function (resolve, reject) { console.log('p2'); reject(); }); var p3 = new Promise(function (resolve, reject) { console.log('p3'); resolve(); }); var p4 = Promise.all([p1, p2, p3]); console.log(p4);//返回值reject
    </script>
</body>
</html>

2)Promise.race()將多個Promise實例,包裝成一個新的Promise實例。 

 var p = Promise.race([p1,p2,p3]);

上面代碼中,只要p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Promise</title>
</head>
<body>
    <script>
    var p1 = new Promise(function (resolve, reject) { setTimeout(function () { console.log('p1'); resolve(); }, 400); }); var p2 = new Promise(function (resolve, reject) { setTimeout(function () { console.log('p2'); reject(); }, 800); }); var p3 = new Promise(function (resolve, reject) { setTimeout(function () { console.log('p3'); resolve(); }, 2000); }); var p4 = Promise.race([p1, p2, p3]); /*p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。 率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。*/ console.log(p4);//返回值reject
    </script>
</body>
</html>

4>promise改造ajax依賴調用(以 8:ajax的依賴調用demo改寫為例

文件目錄結構:

1.html頁面

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用戶登錄</title>
    <style type="text/css">
        *{ margin: 0; padding: 0; } html,body{ height: 100%; overflow: hidden; } body{ position: relative; } .login-box{ position: fixed; left: 0; right:0; top: 0; bottom: 0; margin: auto; width: 90%; background: #f3f3f3; height: 90%; box-shadow: 0 0 10px #333333,0 0 8px #f3f3f3; border: 1px solid #eeeeee } .loginRegion{ width: 50%; height: 400px; position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; } .inputRegion{ margin: 10px auto; width: 90%; height:60px; font-size: 18px; border:1px solid #555555; background: #ffffff; } .remindInfo{ margin: 0 auto 30px; width: 90%; height:30px; line-height: 30px; font-size: 28px; background: #f3f3f3; color: #000000; text-align: center; font-weight: bold; letter-spacing: 10px; } .inputRegion label{ padding: 0 8px; font-size: 22px; vertical-align: middle; } .inputRegion input{ padding: 0; margin: 0; height: 100%; outline: none; border:none; font-size: 20px; width: 80%; vertical-align: middle; } .btnRegin input{ width: 100%; border:1px solid #90EE90; font-size: 24px; text-align: center; background: #9AFF9A; font-weight: normal; color: #ffffff; } </style>
</head>
<body>
    <div class="login-box">
        <form method="post" action="javascript:;" name="login" class="loginRegion">
            <div id="remindInfo" class="remindInfo"> 用戶登錄 </div>
            <div class="inputRegion account">
                <label>賬&nbsp;&nbsp;&nbsp;號</label><input type="text"  name="account" autocomplete="off" />
            </div>    
            <div class="inputRegion password">
                <label>密&nbsp;&nbsp;&nbsp;碼</label><input type="password"  name="password" autocomplete="off"/>
            </div>
            <div class="inputRegion captcha">
                <label>驗證碼</label><input type="text"  name="captcha" autocomplete="off"/>
            </div>            
            <div class="inputRegion btnRegin">
                <input type="submit"  name="submitBtn" value="登&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;錄"/>
            </div>
        </form>
    </div>
</body>
<script type="text/javascript" src="scripts/common.js"></script>
<script type="text/javascript" src="scripts/login.js"></script>
</html>
login.html

 2.js文件

1)公共js($函數的封裝,promise改寫ajax函數的封裝)

/* author:MR.Shi create date:2018/03/01 updater: update date: function name $ introduce param: id:id property value */ function $(id) { return document.getElementById(id); } /* author:MR.Shi create date:2018/03/01 updater: update date: function name:ajax introduce param: type:post/get url:post url/get url asyn:true/false default:true param:string type or object type */ function ajax({type,url,asyn=true,param,beforesend,complete}) { return new Promise(function (resolve,reject) { /*創建XMLHttpRequest對象*/
        var xhr = new XMLHttpRequest(); /*參數為對象時按照get請求時url?傳參的格式進行拼接字符串*/
        if (param&&typeof(param)==='object') { var sParam = ""; for(var key in param){ sParam += encodeURIComponent(key)+ "=" +encodeURIComponent(param[key]) + "&"; } /*去除字符串最后的&分割符號*/ param = sParam.slice(0,-1); } console.log(param) /*get請求時url地址拼接參數*/
        if (type.toUpperCase()==="GET" && param) { url += '?' + param; } /*配置請求*/ xhr.open(type,url,asyn); /*在發送請求之前執行的函數*/ beforesend&&beforesend(); /*發送請求*/
        if (type.toUpperCase()==="POST") { /*設置post請求時編碼規則,否則無法解析數據*/ xhr.setRequestHeader("content-type","application/x-www-form-urlencoded") /*post傳參時參數封裝到body通過send方法發送*/ xhr.send(param); } else{ /*get請求send直接發送*/ xhr.send(); } /*接收請求返回數據*/ xhr.onreadystatechange=function () { if (xhr.readyState === 4 && xhr.status === 200) { resolve(xhr.responseText) } if (xhr.status>=400) { reject(new error(xhr.status)); } complete&&complete(); } }); }
common.js

 2)頁面js文件(promise的鏈式調用)

var oForm=document.login; var  oRemindInfo=$("remindInfo"); oForm.onsubmit=function () { ajax({ type:'post', url:'php/login.php', param:{captcha:oForm.captcha.value} }).then(function (returnValue) { console.log(returnValue); if (returnValue == 1) { return ajax({ type:'post', url:'php/login.php', param:{account:oForm.account.value} }); } else{ console.log("yanzhengma") oRemindInfo.style.color="#ff3333"; oRemindInfo.innerHTML="!驗證碼輸入有誤!"; return ; } }).then(function (returnValue) { console.log(returnValue); if (typeof(returnValue)!=="undefined") { if (returnValue == 1) { return ajax({ type:'post', url:'php/login.php', param:{password:oForm.password.value} }); } else{ oRemindInfo.style.color="#ff3333"; console.log(oRemindInfo.style.color) oRemindInfo.innerHTML="!賬號輸入有誤!"; } } }).then(function (returnValue) { console.log(returnValue); if (typeof(returnValue)!=="undefined") { if (returnValue == 1) { oRemindInfo.style.color="#000000"; oRemindInfo.innerHTML="√ 登錄成功!"; } else{ oRemindInfo.style.color="#ff3333"; oRemindInfo.innerHTML="!密碼輸入有誤!"; } } }).catch(function (err) { console.log("錯誤代碼:" + err) }); }
login.js

 3.php代碼文件(驗證的數據都是后台寫死的)

<?php $captcha = isset($_POST['captcha']) ? $_POST['captcha'] : null; $account = isset($_POST['account']) ? $_POST['account'] : null; $password = isset($_POST['password']) ? $_POST['password'] : null; // 驗證驗證碼
if(!is_null($captcha)) { if($captcha == '11') { echo 1; } else { echo 0; } } // 驗證賬號
if(!is_null($account)) { if($account == '11') { echo 1; } else { echo 0; } } // 驗證密碼
if(!is_null($password)) { if($password == '11') { echo 1; } else { echo 0; } }
login.php

十、ajax的同源策略

同源策略(same origin policy)是一種約定,是由netscape公司引入瀏覽器的,他是瀏覽器最核心最基本的安全功能。現在所有支持JS的瀏覽器都會使用這個策略。

所謂同源:是指協議、域名、端口都相同。

目的:保證用戶信息的安全,防止惡意的網站竊取數據。

限制范圍:

1:cookie,localStorage和IndexDB無法讀取。

2:DOM無法獲得。

3:AJAX請求不能發送。


十一、jsonp跨域

JSONP = JSON WITH PADDING。一種數據調用的方式。

AJAX無論請求什么頁面,因為同源策略的限制,只要是跨域就一律不准。不過我們發現凡是擁有src這個屬性的標簽都擁有跨域的能力,如img,iframe,script。那我們就可以把數據裝載到JS文件內,供客戶端調用和進一步處理。

提供下百度和淘寶的跨域的方式(京東的可看不可用)

淘寶:

百度:

步驟:

1:定義一個回調函數。

2:創建script標簽,指定src地址,並添加到頁面中。

注:src必須跟着一個參數callback來指定回調函數名稱。(callback回調函數定義必須全局不能放到匿名函數或函數表達式,否則無法找到)

利用127.0.0.1和localhost本地環境演示jsonp跨域demo

(依據同源策略的概念可以看出127.0.0.1和localhost屬於跨域,可通過實際請求測試若不進行跨域訪問無法訪問到127.0.0.1資源)

1.html頁面 

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>jsonp跨域請求</title>
</head>
<body>    
</body>
<script type="text/javascript" src="http://127.0.0.1/project/jsonp/jsonp.js"></script>
/*凡是擁有src這個屬性的標簽都擁有跨域的能力,如img,iframe,script。那我們就可以把數據裝載到JS文件內,供客戶端調用和進一步處理。*/
</html>
jsonp.html

 2.jsonp.js文件 

/*回調函數(全局,否則無法找到)接收並處理跨域請求的返回的數據*/ function callback(data) { var data="跨域請求的數據結果:"+data console.log(data) document.body.innerHTML=data; } window.onload=function () { var oScript=document.createElement('script'); /*創建script標簽*/ oScript.src="http://127.0.0.1/project/jsonp/jsonp.php?callback=callback"; /*設置script標簽src屬性值為跨域請求的文件並指定接收返回結果的回調函數*/ document.body.appendChild(oScript); /*創建的標簽加載到body頁面*/ document.body.removeChild(oScript) /*跨域請求完成標簽廢棄,進行移除*/ }
jsonp.js

 3.php文件 

<?php $arr = array( '測試數據1!', '測試數據2!', '測試數據3!' ); $arr = json_encode($arr); //數組格式轉換為字符串
    $callback = $_GET['callback']; //獲取回調函數的名稱
    echo $callback.'('.$arr.')'; //返回回調函數並傳參
?>
jsonp.php 

十二、服務器端跨域

CORS是一個W3C標准,全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。

CORS需要瀏覽器和服務器同時支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低於IE10。整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。因此,實現CORS通信的關鍵是服務器。只要服務器實現了CORS接口,就可以跨源通信。

瀏覽器發起CORS請求,都會在請求頭信息中增加一個Origin字段。該字段用來說明本次請求來自哪個源(協議 + 域名 + 端口)。服務器根據這個值,來決定是否同意這次請求。

實現方式:

在服務器端指定Access-Control-Allow-Origin頭信息,該字段是必須的。它的值要么是請求時Origin字段的值,要么是一個*,表示接受任意域名的請求。如果Origin指定的值不在許可范圍內,服務器返回一個正常的HTTP響應。瀏覽器如果發現響應頭信息中沒有包含Access-Control-Allow-Origin字段就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。

向服務器發送Cookie: 

1.在服務器端指定Access-Control-Allow-Credentials頭信息,該字段可選。它的值是一個布爾值,表示是否允許發送Cookie。默認情況下,Cookie不包括在CORS請求之中。設為true,即表示服務器明確許可,Cookie可以包含在請求中,一起發給服務器。這個值也只能設為true,如果服務器不要瀏覽器發送Cookie,刪除該字段即可。 

2.開發者必須在AJAX中打開withCredentials字段。 

3.要發送cookie,Access-Control-Allow-Origin的值不能是*,必須指定明確的,與請求網頁一致的域名。同時cookie依然遵循同源策略,請求源與被請求源必須擁有同一個主域名,否則cookie還是不會被發送。

      case 1:www.a.com訪問bbs.a.com,並且cookie的domain設置為.a.com,那么cookie才會隨着CORS發送給后台。 

     case 2:www.a.com訪問www.b.com,這種情況下,cookie是不會隨着CORS發送給后台。

 (備注本想這里coding demo來說明,然沒主域名這樣的條件,后續有機會補上......)


十三、get和post提交

1:功能

get多用於從服務器上獲取數據,post多用於向服務器發送數據。

2:數據提交

GET請求的數據會附在URL之后(就是把數據放置在HTTP協議頭中),以?分割URL和傳輸數據,參數之間以&相連。POST把提交的數據則放置在是HTTP包的包體中。

3:數據大小

GET請求傳送的數據量比較小,達能大於2KB,這個受瀏覽器的限制。而POST請求傳送的數據量比較大,一般默認為不受限制。其實大小受服務器的限制。

4:安全性

GET安全性非常低,POST的安全性較高。


免責聲明!

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



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