從在地址欄輸入網址到看到頁面的過程&&AJAX基礎|腦圖梳理

「不畏懼,不講究,將來的日子好好努力」 ;你們好,我是小芝麻😄javascript

本文腦圖較多,方便梳理,適合:前端小白php

今天小芝麻主要想闡述的就是一個問題:css

當用戶在地址欄輸入了網址,到看到頁面中間都經歷了什麼?

request請求階段html

  • 一、URL 地址解析
  • 二、DNS 域名解析
  • 三、和服務器創建 TCP 鏈接
  • 四、發送 HTTP 請求

response 響應階段前端

  • 五、服務器獲得並處理請求

斷開鏈接java

  • 六、和服務器斷開 TCP 鏈接

渲染頁面編程

  • 七、客戶端渲染服務器返回的內容

下面咱們會詳細闡述以上幾個方面具體都是啥;json

客戶端/服務器端

在開始正題以前,咱們先了解如下,這兩個概念;後端

  • 客戶端:能夠向服務器發請求,並接收返回的內容進行處理
  • 服務器端:可以接收客戶端請求,而且把相關資源信息返回給客戶端的

具體交互如圖: 跨域

好的下面進入正題

1、URL 地址解析

前面的文章裏寫過關於 URL參數處理的問題,那到底什麼是URL呢?

一、URL/URN/URI概念區分

  • URL:全稱:Uniform Resource Locator(統一 資源 定位)
    • 定義:統一資源定位符,根據這個地址能找到對應的資源
  • URN:全稱:Uniform Resource Name(統一 資源 名稱)
    • 定義:統一資源名稱,通常指國際上通用的(標準的)一些名字(例如:國際統一發版的編號)
  • URI:全稱:Uniform Resource Identifier(統一 資源 標識)
    • 定義:統一資源標識符,URL 和 URN 是 URI 的子集

二、URL 地址解析

一個完整的URL所包含的內容

-1)協議:(http://)

  • 定義:傳輸協議就是,可以把客戶端和服務器端通訊的信息,進行傳輸的工具(相似於快遞小哥)
  • 經常使用的傳輸協議:
    • http 超文本傳輸協議:除了傳遞文本,還能夠傳遞媒體資源文件(或者流文件)及XML格式數據
    • https 更加安全的http:通常涉及支付的網站都要採用https協議(s:ssl 加密傳輸)
    • ftp 文件傳輸協議:通常應用於把本地資源上傳到服務器端

-2)域名:(www.baidu.cn)

給服務器通網後,會有兩個 IP 地址:

  • 內網IP:局域網內訪問
  • 外網IP:外部用戶基於外網IP訪問到服務器(例如:125.39.174.200)
  • 定義:就是一個讓用戶方便記憶的名字(不經過域名,直接用服務器的外網IP也能訪問到服務器,可是外網IP很難被記住)
  • 種類:
    • 頂級域名:例如 qq.com
    • 一級域名: 例如 www.qq.com
    • 二級域名: 例如 sports.qq.com
    • 三級域名: 例如 kbs.sports.qq.com
    • .com 國際域名
    • .cn 中文域名
    • .com.cn
    • .edu 教育
    • .gov 政府(千萬不要在這種域名下幹什麼事情哦😄)
    • .io 博客
    • .org 官方組織
    • .net 系統類
    • ......

一級、二級、三級...域名都是由頂級域名衍生出來的,因此,公司在註冊域名的時候,只要買下頂級域名就能夠了,就例如 qq.com ,只須要買下 qq.com 就能夠用 www.qq.com、sports.qq.com、kbs.sports.qq.com...等域名了。

-3)端口號:(:80)

  • 定義:用端口號來區分同一臺服務器上的不一樣項目(端口號的取值範圍0~65535)
  • 默認端口號:
    • http默認端口號:80
    • https默認端口號:443
    • ftp默認端口號:21

若是項目採用的就是默認端口號,咱們在書寫地址的時候,不用加端口號,瀏覽器在發送請求的時候會幫咱們默認給加上

-4)請求資源路徑名稱:(/stu/index.html)

  • 默認的路徑或者名稱:xxx.com/stu/ 不指定資源名,服務器會找默認的資源,通常默認資源名是default.html、index.html... 固然這些能夠在服務器端本身配置

  • 注意僞URL地址的處理:

  • 僞 URL 的目的:URL重寫技術是爲了增長SEO搜索引擎優化的,動態的網址通常不能被搜索引擎收錄(因此咱們要把動態網址靜態化,此時須要的是重寫URL)

-5)問號傳參信息:(?from=wx&lx=1)

  • 做用:
    • 客戶端想把信息傳遞給服務器,有不少的方式(URL地址問號傳參)和(請求報文傳輸(請求頭和請求主體))
    • 也能夠不一樣頁面之間的信息交互
  • 最主要的目的:通訊 => 信息傳輸
    • 用於 客戶端和服務端的信息通訊;頁面於頁面之間的信息通訊

-6)HASH值:(#zhenyu)

  • 做用:
    • 也能充當信息傳輸的方式
    • 錨點定位
    • 基於HASH實現路由管控(不一樣的HASH值,展現不一樣的組件和模塊)

三、URL 編碼

請求的地址中若是出現非有效 UNICODE 編碼內容,現代版瀏覽器會默認的進行編碼

編碼方式:

  • 一、encodeURI/decodeURI
    • 基於encodeURI編碼,咱們能夠基於decodeURI解碼,
    • 咱們通常用encodeURI編碼的是整個URL,這樣整個URL中的特殊字符都會自動編譯
  • 二、encodeURIComponent/decodeURIComponent
    • 它相對於encodeURI來講,不用於給整個URL編碼,而是給URL部分信息進行編碼
    • 通常都是問號傳參的值編碼

客戶端和服務器端進行信息傳輸的時候,若是須要把請求的地址和信息編碼,咱們則基於以上兩種方式處理, 服務器端也存在這些方法,這樣就能夠統一編碼解碼了

  • 三、escape/unescape

    • 客戶端存在的一種方式,針對於中文的編碼方式 escape/unescape
    • 通常只應用於客戶端頁面之間本身的處理

    例如:從列表跳轉到詳情,咱們能夠把傳遞的中文信息基於這個編碼,詳情頁獲取編碼後的信息再解碼,再好比咱們在客戶端種的cookie信息,若是信息是中文,咱們也基於這種辦法編碼...

2、DNS 域名解析

  • DNS服務器:又叫作域名解析服務器,
  • 在這個服務器上存儲着 域名<=>服務器外網IP 的相關記錄

而咱們發送請求時候所謂的DNS解析,其實就是根據域名,在DNS服務器上查找到對應服務器的外網IP

  • 本地解析查找過程:

  • 非本地解析:

  • DNS優化:

DNS緩存:通常瀏覽器會在第一次解析後,默認創建緩存,時間很短,只有一分鐘左右

  • 減小DNS解析次數:一個網站中咱們須要發送請求的域名和服務器儘量少便可
  • DNS預獲取(dns-prefetch):在頁面加載開始的時候,就把當前頁面中須要訪問其餘域名(服務器)的信息進行提早DNS解析,之後加載到具體內容部分能夠不用解析了

3、創建 TCP 鏈接(TCP的三次握手)

  • 目的:三次握手以後,就能保證前端後端 可以正常交流(至關於修路 通道打通,數據才能正常交互)

  • 第一次握手:
    • 創建鏈接時,客戶端發送syn包(seq=x)到服務器,並進入SYN_SENT狀態,等待服務器確認;SYN:同步序列編號(Synchronize Sequence Numbers)
  • 第二次握手:
    • 服務器收到syn包,必須確認客戶的SYN(ack=x+1),同時本身也發送一個SYN包(seq=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態。
  • 第三次握手:
    • 客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP鏈接成功)狀態,完成三次握手。

以上是說的太過正式,下面說個白話版的👇:其實就相似於兩我的打招呼的過程;

  • 第一次握手:
    • 由瀏覽器發起:告訴服務器我要發送請求了
    • 傳 SYN = 1 seq = x ;(先出個題考考服務器)
  • 第二次握手:
    • 由服務器發起:告訴瀏覽器我準備接受了,你趕忙發送吧
    • 傳 SYN = 1,ACK = 1 ack = x + 1,seq = y ;(看到瀏覽器出的題,解答完成,而且也給瀏覽器出了個題,一塊兒返回給瀏覽器)
  • 第三次握手:
    • 由瀏覽器發送:告訴服務器,我立刻要發了,準備接收吧
    • 傳 ACK = 1 ack = y + 1;(作完服務器給出得題,返回給服務器)

到此雙方試探成功,創建友好的關係👬;(這條路算是打通了)

沒有錯,不要懷疑,就是這麼簡單~~

有了路以後,怎麼傳遞數據呢,咱們還須要個快遞小哥;(HTTP 就是快遞小哥了)

4、發送 HTTP 請求

HTTP 這個快遞小哥運輸的東西就是 「報文」

一、HTTP 報文

HTTP 報文是由 請求報文+響應報文 共同組成

  • 請求報文:
    • 定義:全部通過傳輸協議,客戶端傳遞給服務器的內容,都被成爲請求報文
    • 包含:
      • 起始行
      • 請求頭(請求首部)
      • 請求主體
  • 響應報文:
    • 定義:全部通過傳輸協議,服務器返回給客戶端的內容,都被成爲響應報文
    • 包含:
      • HTTP狀態碼
      • 響應頭
      • 響應主體

至於報文是啥?在哪查看咱們一會再說,先看下經常使用的狀態碼有哪些;

二、HTTP 狀態碼

1~5開頭,三位數字

  • 1 開頭的表明處理中,通常見不到
  • 2開頭:都是成功
    • 200:OK:成功
    • 201:CREATED:通常應用於告訴服務器建立一個新文件,最後服務器建立成功後返回的狀態碼
    • 204:NO CONTENT:對於某些請求(例如:PUT或者DELETE),服務器不想處理,能夠返回空內容,而且用204狀態碼告知
  • 3開頭:表明成功,只不過中間須要中轉一下
    • 301:Moved Permanently:永久重定向(永久轉移)
    • 302:Moved Temporarily:臨時轉移,很早之前基本上用302來作,可是如今主要用307來處理這個事情,
    • 307的意思就是臨時重定向Temporary Redirect =>主要用於:服務器的負載均衡等
    • 304:Not Modified:設置HTTP的協商緩存
  • 4開頭:都是失敗
    • 400:Bad Request:傳遞給服務器的參數錯誤
    • 401:Unauthorized:無權限訪問
    • 404:Not Found:請求地址錯誤
  • 5開頭:都是失敗
    • 500:Internal Server Error:未知服務器錯誤
    • 503:Service Unavailable:服務器超負荷

三、這些報文都能在 控制檯 Network 看見

=>谷歌瀏覽器F12

=>Network(全部客戶端和服務器端的交互信息在這裏均可以看到)

=>點擊某一條信息,在右側能夠看到全部的HTTP報文信息

上圖就是了😄,接下來咱們看幾個經常使用的

  • Headers:包含的基本上都是頭,首部信息(包括請求首部和響應首部)
    • General:通用首部,兩個都有的,也是基本的
      • Request URL :請求地址
      • Request Method:請求方式
      • Status Code:返回的狀態碼
      • Remote Address:當前服務器的外網 IP
      • ......
    • Response Headers:響應頭,服務器返回給客戶端的
      • Date:服務器把信息返回給客戶端時的時間
      • ETag:協商性緩存
      • Server:當前服務器基於什麼作的
      • ......
    • Request Headers:請求頭,客戶端給瀏覽器的
      • Accept:容許傳輸的類型
      • Accept-Encoding:容許的編碼和壓縮方式
      • Accept-Language:容許的語言
      • Connection:TCP 協議長鏈接
      • Cookie:把本地的信息傳給服務器
      • Host:當前的域
      • User-Agent:當前瀏覽器的版本信息
      • ......
  • Response:響應主體,服務器返回給客戶端的
  • Timing:記錄了每一個階段須要的時間

四、HTTP的請求方式

主要分兩種:

  • GET 系列請求
  • POST 系列請求

不論GET和POST均可以把信息傳遞給服務器,也能從服務器獲取到結果

GET 與 POST 系列請求的區別(所謂的區別都是約定俗成的,並無必定要這樣作的規定)

  • GET 系列請求:通常用於從服務器獲取信息(GET:給的少,拿的多)
    • GET
    • DELETE:通常應用於告訴服務器,從服務器上刪除點東西
    • HEAD:只想獲取響應頭內容,告訴服務器響應主體內容不要了
    • OPTIONS:試探性請求,發個請求給服務器,看看服務器能不能接收到,能不能返回
  • POST 系列請求:通常用於給服務器推送信息(POST:給的多,拿的少)
    • POST
    • PUT:和DELETE對應,通常是想讓服務器把我傳遞的信息存儲到服務器上(通常應用於文件和大型數據內容)

本質區別

  • GET系列傳遞給服務器信息的方式通常採用:問號傳參

  • POST系列傳遞給服務器信息的方式通常採用:設置請求主體

本質區別所致使的問題

  • 一、GET傳遞給服務器的內容比POST少,由於URL有最長大小限制
    • GET:xhr.open('GET','/list?name=xiaozhima&year=18&xxx=xxx...')
      • IE瀏覽器通常限制2KB,谷歌瀏覽器通常限制4~8KB,超過長度的部分自動被瀏覽器截取了
    • POST:xhr.send('....')
      • 請求主體中傳遞的內容理論上沒有大小限制,可是項目中,爲了保證傳輸的速度,咱們會本身限制一些
  • 二、GET會產生緩存(緩存不是本身可控制的)
    • 由於請求的地址(尤爲是問號傳遞的信息同樣),瀏覽器有時候會認爲你要和上次請求的數據同樣,拿的是上一次信息;
    • 這種緩存咱們不指望有,咱們指望的緩存是本身可控制的;因此項目中,若是一個地址,GET請求屢次,咱們要去除這個緩存;
    • 解決辦法:設置隨機數
      • xhr.open('GET','/list?name=xiaozhima&_='+Math.random());
  • 三、GET相比較POST來講不安全(只是相對他倆來講)
    • GET是基於問號傳參傳遞給服務器內容,有一種技術叫作URL劫持,這樣別人能夠獲取或者篡改傳遞的信息;
    • 而POST基於請求主體傳遞信息,不容易被劫持;

好了,HTTP請求就先說到這裏😄

服務器接收到請求後:

  • 一、根據端口號找到對應的項目
  • 二、根據請求資源的路徑名稱找到資源文件
  • 三、讀取資源文件中的內容
  • 四、把內容返回

5、斷開 TCP 協議(TCP四次揮手)

上面咱們說 創建 TCP 鏈接,就是修路,HTTP 就是快遞小哥,在小哥送完快遞後,咱們就要把以前修好的路拆掉,就有了 TCP 四次揮手

  • 目的:四次揮手後,斷開客戶端與服務器的鏈接

  • 第一次揮手:
    • 客戶端A發送一個FIN,用來關閉客戶A到服務器B的數據傳送。
  • 第二次揮手:
    • 服務器B收到這個FIN,它發回一個ACK,確認序號爲收到的序號加1。和SYN同樣,一個FIN將佔用一個序號。
  • 第三次揮手:
    • 服務器B關閉與客戶端A的鏈接,發送一個FIN給客戶端A。
  • 第四次揮手:
    • 客戶端A發回ACK報文確認,並將確認序號設置爲收到序號加1。

白話文翻譯👇:

  • 第一次揮手:
    • 由瀏覽器發起,發送給服務器,我請求報文發送完了,你準備關閉吧
  • 第二次揮手:
    • 由服務器發起,告訴瀏覽器,我接收完請求報文,我準備關閉,你也準備吧
  • 第三次揮手:
    • 由服務器發起,告訴瀏覽器,我響應報文發送完畢,你準備關閉吧
  • 第四次揮手:
    • 由瀏覽器發起,告訴服務器,我響應報文接收完畢,我準備關閉,你也準備吧

爲何創建鏈接協議是三次握手,而關閉鏈接倒是四次握手呢?

  • 這是由於服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求後,它能夠把ACK和SYN(ACK起應答做用,而SYN起同步做用)放在一個報文裏來發送。
  • 但關閉鏈接時,當收到對方的FIN報文通知時,它僅僅表示對方沒有數據發送給你了;
  • 但未必你全部的數據都所有發送給對方了,因此你能夠未必會立刻會關閉SOCKET,
  • 也即你可能還須要發送一些數據給對方以後,再發送FIN報文給對方來表示你贊成能夠關閉鏈接了,因此它這裏的ACK報文和FIN報文多數狀況下都是分開發送的。

6、瀏覽器渲染頁面的步驟

這篇咱們以前寫過瀏覽器渲染頁面的主體流程,此次咱們把主要的步驟在寫一下。

一、生成 DOM 樹

渲染了全部的 HTML 標籤

  • 轉換
    • 經過 HTTP 返還的首先是十六進制的編碼字節數據
    • 拿到字節數據後,瀏覽器會經過內部的機制,把數據轉換成咱們能看到的代碼
  • 令牌
    • 按照 W3C 規範(第五代版本的規範H5)的規則,轉換成咱們能看懂的標籤
  • 詞法分析
    • 經過轉換後的標籤,進行詞法解析,生成DOM節點
  • DOM構建
    • 最後經過查找每一個DOM節點之間的關係,生成DOM

二、生成 CSSOM 樹

請求回來 CSS 後,渲染完 CSS

同生成DOM樹同樣的過程生成CSSOM

三、DOM 樹 + CSSOM 樹 => RENDER-TREE(渲染樹)

四、 佈局(Layout)或 重排/迴流(reflow)

按照 RENDER-TREE 在設備的視口中進行結構和位置的相關計算=>佈局(Layout)或 重排/迴流(reflow)

五、繪製(painting)或 柵格化(rasterizing)

根據渲染樹以及迴流獲得的幾何信息,獲得節點的絕對像素=>繪製(painting)或 柵格化(rasterizing)

到此 「當用戶在地址欄輸入了網址,到看到頁面中間都經歷了什麼?」這道題咱們算是解答完了。

做爲前端咱們在寫代碼的時候,都是經過 AJAX 向服務器發送請求,下面咱們在說一下 AJAX 的基礎知識和操做

AJAX 的基礎知識和操做

1、什麼是 AJAX

全稱:async javascript and xml(異步的 JS 和 XML)

  • 此處的異步指的是:局部刷新(對應的是全局刷新)

    • 全局刷新:服務器渲染

    • 局部刷新:客戶端渲染

  • XML:可擴展的標記語言,用本身自定義的標籤來存儲數據的

    • (在很早之前,咱們基於 AJAX 和服務器進行交互的數據格式通常都已 XML 格式爲主,由於它能清晰展現出對應的數據和結構層級;
    • 可是到後面,流行了一種新的數據格式 JSON,它不只比 XML 更清晰展現數據的結構,並且一樣的數據存儲,JSON 更加輕量,也方便解析和相關的操做,因此如今先後端的數據交互都已 JSON 格式爲主)

XML 格式

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <student>
        <name>張三</name>
        <age>25</age>
        <score>
            <english>95</english>
        </score>
    </student>
    <student>
        <name>張三</name>
        <age>25</age>
    </student>
    <student>
        <name>張三</name>
        <age>25</age>
    </student>
</root>
複製代碼

JSON 格式

[{
    "name": "張三",
    "age": 25,
    "score": {
        "english": 95
    }
}, {
    "name": "張三",
    "age": 25
}, {
    "name": "張三",
    "age": 25
}]
複製代碼

2、AJAX的基礎操做

AJAX任務:發送一個請求給服務器,從服務器獲取到對應的內容是從SEND後開始到XHR.READYSTATE===4的時候算任務結束

核心四步

  • 1.建立AJAX實例

    • 語法:let xhr=new XMLHttpRequest;
    • IE低版本瀏覽器中用的是 new ActiveXObject() (高程三中JS惰性編程思想,關於XHR的兼容處理)
  • 2.打開URL(配置發送請求的信息)

    • 語法:xhr.open('GET','./json/xxx.json',true);
    • 參數:
      • 一、METHOD:HTTP請求方式
      • 二、URL:請求地址(API接口地址)
      • 三、ASYNC:設置同步或者異步(默認是TRUE異步,FALSE同步)
      • 四、USER-NAME:傳遞給服務器的用戶名(基本不用)
      • 五、USER-PASS:傳遞給服務器的密碼(基本不用)
  • 3.監聽AJAX狀態

    • 在狀態爲X的時候,獲取服務器響應的內容
    • AJAX 狀態碼:
      • 獲取狀態碼:xhr.readyState
      • 0:unsend:未發送(建立一個XHR,初始狀態是0)
      • 1:opened:已經打開(執行了xhr.open)
      • 2:headers_received:響應頭信息已經返回給客戶端(發送請求後,服務器會依次返回響應頭和響應主體的信息)
      • 3:loading:等待服務器返回響應內容
      • 4:done:響應主體信息已經返回給客戶端
  • 4.發送請求

    • 語法:xhr.send(null);
    • SEND中放的是請求主體的內容
//1.建立AJAX實例
let xhr=new XMLHttpRequest; 

//2.打開URL(配置發送請求的信息)
xhr.open('GET','./json/xxx.json',true);

//3.監聽AJAX狀態,在狀態爲X的時候,獲取服務器響應的內容
xhr.onreadystatechange=function(){
  if(xhr.readyState===4 && /^(2|3)\d{2}$/.test(xhr.status)){
    let result = xhr.responseText;
  }
}

//4.發送請求
//SEND中放的是請求主體的內容
xhr.send(null);

//=> AJAX任務(發送一個請求給服務器,從服務器獲取到對應的內容)
//=> 從SEND後開始,到XHR.READYSTATE===4的時候算任務結束
複製代碼

3、AJAX對象的幾個方法

  • XMLHttpRequest 原型上的方法
    • open()
    • abort():手動中斷AJAX的請求(通常不用,瀏覽器自動會中斷)
    • getAllResponseHeaders():獲取全部的響應頭信息(結果是全部信息的字符串)
    • getResponseHeader():獲取響應頭信息,一個一個獲取
    • overrideMimeType():重寫Mime類型
    • xhr.setRequestHeader():設置請求頭信息(設置的請求頭信息值不能是中文)
    • send()
  • xhr 上的私有屬性:
    • timeout:
      • xhr.timeout = 10
      • 設置AJAX等待時間,超過這個時間算AJAX延遲
    • withCredentials:
      • xhr.withCredentials = true
      • 在跨域請求中是否容許攜帶證書(攜帶COOKIE)(默認是false)
    • readyState/status/statusText
    • response/responseText/responseXML/responseType/responseURL

客戶端怎麼把信息傳遞給服務器?

  • 問號傳參:xhr.open('GET','/getdata?xxx=xxx&xxx=xxx')
  • 設置請求頭:xhr.setRequestHeader([key],[value])
  • 設置請求主體:xhr.send(請求主體信息)

服務器怎麼把信息返回給客戶端?

  • 經過響應頭
  • 經過響應主體(大部分信息都是基於響應主體返回的)

4、倒計時搶購案例

/* * 倒計時搶購要注意的細節知識點: * 1.計算剩餘多久須要的時間不能是客戶端本機的時間,須要獲取服務器的時間 * (基於AJAX請求向服務器獲取,可是也不能每隔1秒都去請求一次 * 【咱們是第一次加載頁面的時候,從服務器獲取服務器時間,存儲起來,後期咱們把 * 這個時間自動的每隔1秒進行累加,頁面刷新還須要從服務器獲取】) * 2.爲了保證絕對安全,在購買的時候,服務器須要二次校驗時間的合法性 * 3.下降服務器返回時間和真實時間的偏差(減小服務器的響應時間) * 1)請求方式基於HEAD,只獲取響應頭信息便可 * 2)在AJAX狀態爲2的時候處理,無需等到狀態爲4 */
複製代碼

HTML

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
  <title>Document</title>
  <!-- IMPORT CSS -->
  <style> * { margin: 0; padding: 0; } #box { box-sizing: border-box; padding: 20px; width: 200px; margin: 20px auto; background: lightblue; line-height: 50px; } </style>
</head>

<body>
  <div id="box">
    距離秒殺還差:
    <span id="content">00:00:00</span>
  </div>

  <!-- IMPORT JS -->
  <script src="1.js"></script>
</body>

</html>
複製代碼

JS

let box = document.querySelector('#box'),
  content = document.querySelector('#content'),
  timer = null;

// 獲取服務器時間
function getServerTime() {
  return new Promise(resolve => {
    let xhr = new XMLHttpRequest;
    xhr.open('head', './data.json?_=' + Math.random());
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 2 && /^(2|3)\d{2}$/.test(xhr.status)) {
        let time = xhr.getResponseHeader('date');
        // 獲取的TIME是格林尼治時間 GMT(北京時間 GMT+0800)
        time = new Date(time);
        resolve(time);
      }
    };
    xhr.send(null);
  });
}

// 根據服務器時間計算倒計時
function computed(time) {
  // time從服務器獲取的當時間
  // target是搶購的目標時間
  // spanTime兩個時間的毫秒差
  let target = new Date('2020/05/13 15:57:35'),
    spanTime = target - time;
  if (spanTime <= 0) {
    // 已經到達搶購的時間節點了
    box.innerHTML = "開始搶購吧!";
    clearInterval(timer);
    return;
  }
  // 計算出毫秒差中包含多少小時、多少分鐘、多少秒
  let hours = Math.floor(spanTime / (60 * 60 * 1000));
  spanTime = spanTime - hours * 60 * 60 * 1000;
  let minutes = Math.floor(spanTime / (60 * 1000));
  spanTime = spanTime - minutes * 60 * 1000;
  let seconds = Math.floor(spanTime / 1000);
  hours < 10 ? hours = '0' + hours : null;
  minutes < 10 ? minutes = '0' + minutes : null;
  seconds < 10 ? seconds = '0' + seconds : null;
  content.innerHTML = `${hours}:${minutes}:${seconds}`;
}

getServerTime().then(time => {
  // 獲取到服務器時間後,計算倒計時
  computed(time);

  // 每間隔1秒中,讓獲取的時間累加1秒,在從新計算倒計時結果
  timer = setInterval(() => {
    time = new Date(time.getTime() + 1000);
    computed(time);
  }, 1000);
});
複製代碼

今天的文章到此結束,小芝麻自知還有不少不足,提高空間還有很大很大,感謝打什麼批評指正🙏

相關文章
相關標籤/搜索