主頁 > 企業開發 > 7.Page與History的綜合應用

7.Page與History的綜合應用

2020-10-15 16:51:43 企業開發

在傳統頁面中,每個url都是和頁面系結的,即使是單頁面,也應該有這種習慣,因此我們把Page物件作為不同的url,通過頁面的切換來更改url,url的變換代表著用戶在該應用中的探索路徑,用戶可以隨時的后退到上一個頁面,或者前進到后一個頁面,從上面的history篇章中,使用瀏覽器的提供的api可以很好的解決這個問題,然而在實際的使用中存在一個問題:比如用戶從一個頁面跳轉到另外一個頁面,然后后退到前一個頁面,發現上一個頁面并不是他之前訪問的頁面了,原因如下:

  1. 后退到上一個頁面,頁面重新渲染時得到的資料和上一次的資料不一樣;
  2. 后退到上一個頁面,無法讓頁面后退到之前瀏覽的那個位置;
  3. 渲染的頁面有問題,

一般第三點都不會發生,第二點也不是很重要,如果在切換頁面的時候存盤跳轉時的滾動地址,也可以很好的解決,第一點確實是個值得思考的問題,原因如下:

  1. 第一次頁面進入請求了,第二次是否還需要去請求資料;
  2. 如果第二次不去請求,直接渲染第一次顯示的資料,那怎么保證頁面的資料是最新的呢;
  3. 按照2的做法,意味著頁面不是重新渲染,恢復的頁面以及dom系結的事件和最后離開頁面的時候應該是保持一致的;
  4. 按照3的做法,我們必須要存盤歷史記錄中的頁面相關資料,比如快取dom,快取data等;
  5. 關于快取data,是否可以把data完全交給history.state,它不就是為了快取頁面資料而存在的么?不過這里并非采用了history.state,因為當我們要任意更改歷史記錄的時候,之前沒有保存在歷史記錄的data就無法被還原,通過記憶體存盤就能增加靈活性,當然history.state可以作為備案,要求快取的資料的可序列化的,

合理的歷史記錄一般不會有太多級,而且把歷史記錄中的頁面儲存起來,可以加快頁面的渲染速度,增強用戶體驗,

需求

通過上面的討論,我們需要實作如下的目標:

  1. 針對歷史記錄創建一層資料快取,用來實作頁面導航的時候可以快速切換頁面,并將其還原;
  2. 資料快取中主要存盤Page物件中的DOM,事件和data;
  3. 要保證快取資料和歷史記錄的資料保持高度一致,當新的頁面添加到歷史記錄的時候,快取記錄就會新增一條記錄,洗掉歷史記錄時,就洗掉快取中對應的頁面資料;
  4. 為了保證頁面資料和后臺資料的一致性,提供了一個可選的方法,主要是為了進行資料更新以及頁面的區域更新,這個可選方法只有在快速切換的時候才會呼叫,

實作思路

之前的Page物件實作中, 每次渲染一個新頁面的時候,都會new一個指定的頁面,現在我們只要在切換頁面的時候把它快取起來,等到需要的時候,再把它還原回去,只有重新創建一個頁面的時候或者歷史記錄中沒有快取頁面的時候,才會去new一個新Page物件,現在我們在Page的原型物件中新增兩個方法:

  1. save(), 將資料快取起來;
  2. restore(dom), 將資料還原到頁面上,

同時頁面的生命周期也產生了變化,因為從歷史記錄中還原頁面不再進行render,getDomObj和beforeInit流程了,通過restore方法,就快速的將頁面還原到瀏覽器了,為了保證資料一致性,添加了一個afterRestore可選方法,進行區域更新,保證前后端資料更新互動,

save方法是對Page物件的淺洗掉操作,將剩余的必要資訊快取起來,之前的destroy是深度洗掉操作,是為了洗掉所有的參考,相對應的,要進行快取的Page物件的生命周期是這樣的:

  1. restore(dom):將Page還原到頁面上;
  2. 如果存在afterRestore方法,呼叫之后,通過區域更新從而保證頁面一致;
  3. 呼叫init方法,初始化該頁面需要引入的插件;
  4. 日常的業務處理,等待用戶切換頁面;
  5. 首先呼叫dispose方法,這個方法主要是處理引入的插件的銷毀;
  6. save(),將資料快取起來, 如果存在beforeSave方法,先呼叫beforeSave,

如下代碼

save: function () {
   this.isSave = true; // 設定狀態
   this.destroy(false); // 淺洗掉
   this.parent.history.save(this); // 保存頁面于歷史快取中
   if (typeof this.beforeSave === "function") this.beforeSave();
   this.nodes = []; // 把dom全部快取起來
   for (var i = 0; i < this.parentDom.childNodes.length; i++) {
       this.nodes.push(this.parentDom.childNodes[i]);
   }
},
restore: function (dom) {
    dom.innerHTML = ''; // 清除
    // 保證dom是原來的dom
    var fragment = this.template.content;
    for (var i = 0; i < this.nodes.length; i++) {
        fragment.appendChild(this.nodes[i]);
    }
    dom.appendChild(fragment);
    this.nodes.length = 0;
    if (typeof this.afterRestore === "function") this.afterRestore();
    this._beforeInit();
    // 這方法后緊接著就是系結事件,
},
// 呼叫destroy(true)僅在歷史記錄沒有該頁面的時候呼叫,否則都是淺洗掉
destroy: function (isClean) {
    this.dispatchEvent("_dispose");
    if (isClean) {
        this.eventDispatcher.destroy();
        this._removeDom();
        this.template = null;
        this.nodes.length = 0;
        this.parent = null;
        this.data = https://www.cnblogs.com/stringWeb/p/{};
        this.parentDom = null;
    }
},
// _dispose方法放在_init里面定義,僅能被呼叫一次
_init: function () {
    this.isSave = false;
    this.eventDispatcher.clearListenerByType("_dispose"); // 清除這個事件,然后系結新的
    this.attachDiyEvent("_dispose", function () {
        if (typeof this.dispose === "function") this.dispose();
        this._removeEventListener();
        this.http.destroy();
    }, true) // 呼叫后銷毀
    if (typeof this.init === "function") this.init.apply(this, arguments);
},

新增一個HistoryStorage物件,用于存盤頁面快取,并且修改原先的History物件, 讓History專注于互動,HistoryStorage專注于存盤,并將之前的popstate事件轉到History物件下, 修改如下

function History(app) {
    this.app = app; 
    this.appStorage = new HistoryStorage();
    this.skipPop = false; // 是否要過濾popstate事件
    this.popBack = null; // 過濾popstate事件時執行的可變方法
    // 代表監聽popstate實作
    window.addEventListener("popstate", this._popHandler.bind(this)); 
}
History.prototype = {
    constructor: History,
    // 設定鎖屏與否
    setLock: function (isLock) {
        this.appStorage.setLock(isLock);
    },
    // 獲取是否鎖屏狀態
    getLock: function () {
        return this.appStorage.isLock
    },
    // 恢復頁面
    restore: function () {
        this.appStorage.restore();
    },
    pushState: function (page, option) {
        this.appStorage.pushState(page, option);
    },
    replaceState: function (page, option) {
        this.appStorage.replaceState(page, option);
    },
    _popHandler: function (ev) {
        var app = this.app, that = this;
        if (this.getLock()) return this.restore();
        if (this.skipPop) 
          if (typeof this.popBack === "function") return this.popBack();
        // 改變hash也會觸發popstate事件
        if (location.pathname == app.currentPage.url) return; 
        // 傳入三個方法,分別代表當前位置是否在首頁,如果是首頁執行第二個方法,不是首頁則執行第三個方法
        this.appStorage.popOperation(function (name, nameList) {
            return nameList.indexOf(name) === 0;
        }, function (component) {
            if (typeof app.outofHistory === "function") app.outofHistory();
            setTimeout(function () {
                that.pushState(component);
                app._renderPage(component, true); // 渲染頁面
            }, 2000);
        }, function (component, config, str) {
            that.renderBackComponent(app.component, config, str); 
        });
    }
};

History的實際操作轉向StorageHistory, 為了后期能更好的擴展,

function HistoryStorage() {
    this.history = []; // 存放url陣列,對應歷史記錄
    this.components = []; // 存放Page物件
    this.datas = []; // 存放資料,這里的資料和Page物件,還有包含其它資料
    this.index = null; // 指向當前的位置
    this.isLock = false;
}
HistoryStorage.prototype = {
    constructor: HistoryStorage,
    popOperation: function (filter, elseFn, operationFn) {
        var name = this._getCurrentHistoryName();
        if (filter(name, this.history)) {
            elseFn(this.components[this.index]);
        } else {
            var urlObj = this._getSurroundUrl(),
                str, config,
                component = this.components[this.index];

            component.save(); // 保存頁面
            if (urlObj.prev === name) {
                config = this.datas[--this.index];
                str = "out";
            } else {
                config = this.datas[++this.index];
                str = "in";
            }
            operationFn(this.components[this.index], config, str);
        }
    },
    // 指向pushState和replaceState的時候,把資料和page物件保存起來
    replaceState: function (component, option) {
        option = option || {};
        var url = option.url || component.url;
        // 銷毀頁面
        if (this.components[this.index]) this.components[this.index].destroy(true);
        if (location.protocol !== "file:") history.replaceState(option, "", url);
        this.datas[this.index] = option;
        this.components[this.index] = component;
        this.history[this.index] = url;
    },
    pushState: function (component, option) {
        option = option || {};
        var url = this.type == "app" ? 
            option.url || component.url : 
        	location.pathname + "?popup=" + component.name,
            components = this.components,
            datas = this.datas;

        if (components[this.index] && !components[this.index].isSave) 
            components[this.index].save();
        if (location.protocol !== "file:")  history.pushState(option, "", url);
        if (typeof this.index === "number") {
            for (var i = components.length - 1; i >= this.index + 1; i--) {
                // 移除頁面的時候銷毀頁面
                if (components[i]) components[i].destroy(true);
            }
            var nextIndex = ++this.index,
                len = components.length;
            components.splice(nextIndex, len - nextIndex, component);
            datas.splice(nextIndex, len - nextIndex, option);
            this.history.splice(nextIndex, len - nextIndex, url);
        } else {
            this.index = 0;
            this.components.push(component);
            this.history.push(url);
            this.datas.push(option);
        }
    },
    // 其它操作和之前的類似,這里省略
}
  

案例地址

結語

通過history和Page配合,讓頁面能夠快速的切換,極大的提升了頁面的切換速度,讓webapp更像原生app,

推廣

底層框架開源地址:https://gitee.com/string-for-100w/string
演示網站: https://www.renxuan.tech/

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/172610.html

標籤:JavaScript

上一篇:6.Page物件詳解

下一篇:8.html模板引擎以及頁面資料來源

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more