亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

原生 js 實現(xiàn)一個前端路由 router

gggggggbong / 3494人閱讀

摘要:實現(xiàn)原理現(xiàn)在前端的路由實現(xiàn)一般有兩種,一種是路由,另外一種是路由?,F(xiàn)在的前端主流框架的路由實現(xiàn)方式都會采用路由,本項目采用的也是。當值發(fā)生改變的時候,我們可以通過事件監(jiān)聽到,從而在回調(diào)函數(shù)里面觸發(fā)某些方法。

效果圖:

項目地址:https://github.com/biaochenxuying/route

效果體驗地址:

1. 滑動效果: https://biaochenxuying.github.io/route/index.html

2. 淡入淡出效果: https://biaochenxuying.github.io/route/index2.html

1. 需求

因為我司的 H 5 的項目是用原生 js 寫的,要用到路由,但是現(xiàn)在好用的路由都是和某些框架綁定在一起的,比如 vue-router ,framework7 的路由;但是又沒必要為了一個路由功能而加入一套框架,現(xiàn)在自己寫一個輕量級的路由。

2. 實現(xiàn)原理

現(xiàn)在前端的路由實現(xiàn)一般有兩種,一種是 Hash 路由,另外一種是 History 路由。

2.1 History 路由

History 接口允許操作瀏覽器的曾經(jīng)在標簽頁或者框架里訪問的會話歷史記錄。

屬性

History.length 是一個只讀屬性,返回當前 session 中的 history 個數(shù),包含當前頁面在內(nèi)。舉個例子,對于新開一個 tab 加載的頁面當前屬性返回值 1 。

History.state 返回一個表示歷史堆棧頂部的狀態(tài)的值。這是一種可以不必等待 popstate?事件而查看狀態(tài)而的方式。

方法

History.back()

前往上一頁, 用戶可點擊瀏覽器左上角的返回按鈕模擬此方法. 等價于 history.go(-1).

Note: 當瀏覽器會話歷史記錄處于第一頁時調(diào)用此方法沒有效果,而且也不會報錯。

History.forward()

在瀏覽器歷史記錄里前往下一頁,用戶可點擊瀏覽器左上角的前進按鈕模擬此方法. 等價于 history.go(1).

Note: 當瀏覽器歷史棧處于最頂端時( 當前頁面處于最后一頁時 )調(diào)用此方法沒有效果也不報錯。

History.go(n)

通過當前頁面的相對位置從瀏覽器歷史記錄( 會話記錄 )加載頁面。比如:參數(shù)為 -1的時候為上一頁,參數(shù)為 1 的時候為下一頁. 當整數(shù)參數(shù)超出界限時 ( 譯者注:原文為 When integerDelta is out of bounds ),例如: 如果當前頁為第一頁,前面已經(jīng)沒有頁面了,我傳參的值為 -1,那么這個方法沒有任何效果也不會報錯。調(diào)用沒有參數(shù)的 go() 方法或者不是整數(shù)的參數(shù)時也沒有效果。( 這點與支持字符串作為 url 參數(shù)的 IE 有點不同)。

history.pushState() 和 history.replaceState()

這兩個 API 都接收三個參數(shù),分別是

a. 狀態(tài)對象(state object) — 一個JavaScript對象,與用 pushState() 方法創(chuàng)建的新歷史記錄條目關(guān)聯(lián)。無論何時用戶導航到新創(chuàng)建的狀態(tài),popstate 事件都會被觸發(fā),并且事件對象的state 屬性都包含歷史記錄條目的狀態(tài)對象的拷貝。

b. 標題(title) — FireFox 瀏覽器目前會忽略該參數(shù),雖然以后可能會用上??紤]到未來可能會對該方法進行修改,傳一個空字符串會比較安全?;蛘?,你也可以傳入一個簡短的標題,標明將要進入的狀態(tài)。

c. 地址(URL) — 新的歷史記錄條目的地址。瀏覽器不會在調(diào)用 pushState() 方法后加載該地址,但之后,可能會試圖加載,例如用戶重啟瀏覽器。新的 URL 不一定是絕對路徑;如果是相對路徑,它將以當前 URL 為基準;傳入的 URL 與當前 URL 應該是同源的,否則,pushState() 會拋出異常。該參數(shù)是可選的;不指定的話則為文檔當前 URL。

相同之處: 是兩個 API 都會操作瀏覽器的歷史記錄,而不會引起頁面的刷新。

不同之處在于: pushState 會增加一條新的歷史記錄,而 replaceState 則會替換當前的歷史記錄。

例子:

本來的路由

 http://biaochenxuying.cn/

執(zhí)行:

window.history.pushState(null, null, "http://biaochenxuying.cn/home");

路由變成了:

 http://biaochenxuying.cn/home

詳情介紹請看:MDN

2.2 Hash 路由

我們經(jīng)常在 url 中看到 #,這個 # 有兩種情況,一個是我們所謂的錨點,比如典型的回到頂部按鈕原理、Github 上各個標題之間的跳轉(zhuǎn)等,但是路由里的 # 不叫錨點,我們稱之為 hash。

現(xiàn)在的前端主流框架的路由實現(xiàn)方式都會采用 Hash 路由,本項目采用的也是。

當 hash 值發(fā)生改變的時候,我們可以通過 hashchange 事件監(jiān)聽到,從而在回調(diào)函數(shù)里面觸發(fā)某些方法。

3. 代碼實現(xiàn) 3.1 簡單版 - 單頁面路由

先看個簡單版的 原生 js 模擬 Vue 路由切換。

原理

監(jiān)聽 hashchange ,hash 改變的時候,根據(jù)當前的 hash 匹配相應的 html 內(nèi)容,然后用 innerHTML 把 html 內(nèi)容放進 router-view 里面。

這個代碼是網(wǎng)上的:





    
    
    
    原生模擬 Vue 路由切換
    



    
    
3.2 復雜版 - 內(nèi)聯(lián)頁面版,帶緩存功能

首先前端用 js 實現(xiàn)路由的緩存功能是很難的,但像 vue-router 那種還好,因為有 vue 框架和虛擬 dom 的技術(shù),可以保存當前頁面的數(shù)據(jù)。

要做緩存功能,首先要知道瀏覽器的 前進、刷新、回退 這三個操作。

但是瀏覽器中主要有這幾個限制:

沒有提供監(jiān)聽前進后退的事件

不允許開發(fā)者讀取瀏覽記錄

用戶可以手動輸入地址,或使用瀏覽器提供的前進后退來改變 url

所以要自定義路由,解決方案是自己維護一份路由歷史的記錄,存在一個數(shù)組里面,從而區(qū)分 前進、刷新、回退。

url 存在于瀏覽記錄中即為后退,后退時,把當前路由后面的瀏覽記錄刪除。

url 不存在于瀏覽記錄中即為前進,前進時,往數(shù)組里面 push 當前的路由。

url 在瀏覽記錄的末端即為刷新,刷新時,不對路由數(shù)組做任何操作。

另外,應用的路由路徑中可能允許相同的路由出現(xiàn)多次(例如 A -> B -> A),所以給每個路由添加一個 key 值來區(qū)分相同路由的不同實例。

這個瀏覽記錄需要存儲在 sessionStorage 中,這樣用戶刷新后瀏覽記錄也可以恢復。

3.2.1 route.js
3.2.1.1 跳轉(zhuǎn)方法 linkTo

像 vue-router 那樣,提供了一個 router-link 組件來導航,而我這個框架也提供了一個 linkTo 的方法。

        // 生成不同的 key 
        function genKey() {
            var t = "xxxxxxxx"
            return t.replace(/[xy]/g, function(c) {
                var r = Math.random() * 16 | 0
                var v = c === "x" ? r : (r & 0x3 | 0x8)
                return v.toString(16)
            })
        }

        // 初始化跳轉(zhuǎn)方法
        window.linkTo = function(path) {
                if (path.indexOf("?") !== -1) {
                    window.location.hash = path + "&key=" + genKey()
                } else {
                    window.location.hash = path + "?key=" + genKey()
                }
        }

用法:

//1. 直接用 a 標簽
列表1

//2. 標簽加 js 調(diào)用方法
首頁
// 3. js 調(diào)用觸發(fā) linkTo("#/list")
3.2.1.2 構(gòu)造函數(shù) Router

定義好要用到的變量

function Router() {
        this.routes = {}; //保存注冊的所有路由
        this.beforeFun = null; //切換前
        this.afterFun = null; // 切換后
        this.routerViewId = "#routerView"; // 路由掛載點 
        this.redirectRoute = null; // 路由重定向的 hash
        this.stackPages = true; // 多級頁面緩存
        this.routerMap = []; // 路由遍歷
        this.historyFlag = "" // 路由狀態(tài),前進,回退,刷新
        this.history = []; // 路由歷史
        this.animationName = "slide" // 頁面切換時的動畫
    }
3.2.1.3 實現(xiàn)路由功能

包括:初始化、注冊路由、歷史記錄、切換頁面、切換頁面的動畫、切換之前的鉤子、切換之后的鉤子、滾動位置的處理,緩存。

Router.prototype = {
        init: function(config) {
            var self = this;
            this.routerMap = config ? config.routes : this.routerMap
            this.routerViewId = config ? config.routerViewId : this.routerViewId
            this.stackPages = config ? config.stackPages : this.stackPages
            var name = document.querySelector("#routerView").getAttribute("data-animationName")
            if (name) {
                this.animationName = name
            }
            this.animationName = config ? config.animationName : this.animationName

            if (!this.routerMap.length) {
                var selector = this.routerViewId + " .page"
                var pages = document.querySelectorAll(selector)
                for (var i = 0; i < pages.length; i++) {
                    var page = pages[i];
                    var hash = page.getAttribute("data-hash")
                    var name = hash.substr(1)
                    var item = {
                        path: hash,
                        name: name,
                        callback: util.closure(name)
                    }
                    this.routerMap.push(item)
                }
            }

            this.map()

            // 初始化跳轉(zhuǎn)方法
            window.linkTo = function(path) {
                console.log("path :", path)
                if (path.indexOf("?") !== -1) {
                    window.location.hash = path + "&key=" + util.genKey()
                } else {
                    window.location.hash = path + "?key=" + util.genKey()
                }
            }

            //頁面首次加載 匹配路由
            window.addEventListener("load", function(event) {
                // console.log("load", event);
                self.historyChange(event)
            }, false)

            //路由切換
            window.addEventListener("hashchange", function(event) {
                // console.log("hashchange", event);
                self.historyChange(event)
            }, false)

        },
        // 路由歷史紀錄變化
        historyChange: function(event) {
            var currentHash = util.getParamsUrl();
            var nameStr = "router-" + (this.routerViewId) + "-history"
            this.history = window.sessionStorage[nameStr] ? JSON.parse(window.sessionStorage[nameStr]) : []

            var back = false,
                refresh = false,
                forward = false,
                index = 0,
                len = this.history.length;

            for (var i = 0; i < len; i++) {
                var h = this.history[i];
                if (h.hash === currentHash.path && h.key === currentHash.query.key) {
                    index = i
                    if (i === len - 1) {
                        refresh = true
                    } else {
                        back = true
                    }
                    break;
                } else {
                    forward = true
                }
            }
            if (back) {
                this.historyFlag = "back"
                this.history.length = index + 1
            } else if (refresh) {
                this.historyFlag = "refresh"
            } else {
                this.historyFlag = "forward"
                var item = {
                    key: currentHash.query.key,
                    hash: currentHash.path,
                    query: currentHash.query
                }
                this.history.push(item)
            }
            console.log("historyFlag :", this.historyFlag)
                // console.log("history :", this.history)
            if (!this.stackPages) {
                this.historyFlag = "forward"
            }
            window.sessionStorage[nameStr] = JSON.stringify(this.history)
            this.urlChange()
        },
        // 切換頁面
        changeView: function(currentHash) {
            var pages = document.getElementsByClassName("page")
            var previousPage = document.getElementsByClassName("current")[0]
            var currentPage = null
            var currHash = null
            for (var i = 0; i < pages.length; i++) {
                var page = pages[i];
                var hash = page.getAttribute("data-hash")
                page.setAttribute("class", "page")
                if (hash === currentHash.path) {
                    currHash = hash
                    currentPage = page
                }
            }
            var enterName = "enter-" + this.animationName
            var leaveName = "leave-" + this.animationName
            if (this.historyFlag === "back") {
                util.addClass(currentPage, "current")
                if (previousPage) {
                    util.addClass(previousPage, leaveName)
                }
                setTimeout(function() {
                    if (previousPage) {
                        util.removeClass(previousPage, leaveName)
                    }
                }, 250);
            } else if (this.historyFlag === "forward" || this.historyFlag === "refresh") {
                if (previousPage) {
                    util.addClass(previousPage, "current")
                }
                util.addClass(currentPage, enterName)
                setTimeout(function() {
                    if (previousPage) {
                        util.removeClass(previousPage, "current")
                    }
                    util.removeClass(currentPage, enterName)
                    util.addClass(currentPage, "current")
                }, 350);
                // 前進和刷新都執(zhí)行回調(diào) 與 初始滾動位置為 0
                currentPage.scrollTop = 0
                this.routes[currHash].callback ? this.routes[currHash].callback(currentHash) : null
            }
            this.afterFun ? this.afterFun(currentHash) : null
        },
        //路由處理
        urlChange: function() {
            var currentHash = util.getParamsUrl();
            if (this.routes[currentHash.path]) {
                var self = this;
                if (this.beforeFun) {
                    this.beforeFun({
                        to: {
                            path: currentHash.path,
                            query: currentHash.query
                        },
                        next: function() {
                            self.changeView(currentHash)
                        }
                    })
                } else {
                    this.changeView(currentHash)
                }
            } else {
                //不存在的地址,重定向到默認頁面
                location.hash = this.redirectRoute
            }
        },
        //路由注冊
        map: function() {
            for (var i = 0; i < this.routerMap.length; i++) {
                var route = this.routerMap[i]
                if (route.name === "redirect") {
                    this.redirectRoute = route.path
                } else {
                    this.redirectRoute = this.routerMap[0].path
                }
                var newPath = route.path
                var path = newPath.replace(/s*/g, ""); //過濾空格
                this.routes[path] = {
                    callback: route.callback, //回調(diào)
                }
            }
        },
        //切換之前的鉤子
        beforeEach: function(callback) {
            if (Object.prototype.toString.call(callback) === "[object Function]") {
                this.beforeFun = callback;
            } else {
                console.trace("路由切換前鉤子函數(shù)不正確")
            }
        },
        //切換成功之后的鉤子
        afterEach: function(callback) {
            if (Object.prototype.toString.call(callback) === "[object Function]") {
                this.afterFun = callback;
            } else {
                console.trace("路由切換后回調(diào)函數(shù)不正確")
            }
        }
    }
3.2.1.4 注冊到 Router 到 window 全局
    window.Router = Router;
    window.router = new Router();

完整代碼:https://github.com/biaochenxu...

3.2.2 使用方法
3.2.2.1 js 定義法

callback 是切換頁面后,執(zhí)行的回調(diào)

3.2.2.2 html 加
內(nèi)容占位
內(nèi)容占位

參考項目:https://github.com/kliuj/spa-...

5. 最后

項目地址:https://github.com/biaochenxuying/route

博客常更地址1 :https://github.com/biaochenxuying/blog

博客常更地址2 :http://biaochenxuying.cn/main.html

足足一個多月沒有更新文章了,因為項目太緊,加班加班啊,趁著在家有空,趕緊寫下這篇干貨,免得忘記了,希望對大家有所幫助。

如果您覺得這篇文章不錯或者對你有所幫助,請點個贊,謝謝。

全棧修煉 有興趣的朋友可以掃下方二維碼關(guān)注我的公眾號

我會不定期更新有價值的內(nèi)容,長期運營。

關(guān)注公眾號并回復 福利 可領(lǐng)取免費學習資料,福利詳情請猛戳: Python、Java、Linux、Go、node、vue、react、javaScript

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/108955.html

相關(guān)文章

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<