[深刻11] 前端路由

導航

[深刻01] 執行上下文
[深刻02] 原型鏈
[深刻03] 繼承
[深刻04] 事件循環
[深刻05] 柯里化 偏函數 函數記憶
[深刻06] 隱式轉換 和 運算符
[深刻07] 瀏覽器緩存機制(http緩存機制)
[深刻08] 前端安全
[深刻09] 深淺拷貝
[深刻10] Debounce Throttle
[深刻11] 前端路由
[深刻12] 前端模塊化
[深刻13] 觀察者模式 發佈訂閱模式 雙向數據綁定
[深刻14] canvas
[深刻15] webSocket
[深刻16] webpack
[深刻17] http 和 https
[深刻18] CSS-interview
[react] Hooksjavascript

[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CIcss

[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程html

前置知識

URL 和 URI

  • URI:統一資源標識符 ( I => Identifier:標識符Id) (Universal Resource Identifier
  • URL:統一資源定位符 ( L => Locator:定位器) Uniform Resource Locator
  • 區別:
    • URL:強調的是地址,即( 定位 )這個惟一的資源
    • URI:強調的是( 標識 )資源,資源具備的( 惟一性 )
    • 分別標識惟一資源和標識惟一地址很麻煩,因此用URL也充當RUI的角色,即標記惟一資源還標記該資源的惟一地址

URL的組成

http://www.baidu.com:80/stu/index.html?name=xxx&age=25#teacher前端

  • Protocol:協議 http://https://
  • Domain:域名 www.baicu.com
  • Port:端口 :80
    • http協議的默認地址是 :80
    • https自已的默認地址是:443
  • Path:文件路徑, => /開始 ?以前的部分, 本例中是:/stu/index.html
  • Query:查詢字符串 => ?開頭到結尾,或者?開頭到#以前,本例是:?name=xxx&age=25
  • Hash:哈希值 => #開頭到結尾,本例是:teacher
  • protocol,domain,port,path,query,hash

DOMContentLoaded 事件,load事件

  • window.onload window.addEventListener('load', ....)
  • DOMContentLoaded
  • 區別:
    • DOMContentLoaded:DOM加載完成時觸發
    • load:須要DOM,樣式,腳本,圖片,視頻等全部資源都加載完成時纔會觸發,即頁面加載完成才觸發
DOM完整解析過程:

1. 解析html
2. 解析css - 包括當html中的樣式和外部引入的樣式
3. 解析並運行腳本 - 報錯本html中的腳本和外部引入的腳本
4. DOM構建完成 ---------------------------------------------------- DOM加載完成,觸發 DOMContentLoaded
5. 加載圖片,視頻等其餘資源
6. 頁面加載完畢 --------------------------------------------------- 頁面加載完成,觸發 load
複製代碼

字符串 slice 方法特例

  • slice可用戶數組和字符串
  • 有返回值,不改變原字符串
  • String.prototype.slice(開始位置,結束位置) 截取字符串,不包括結束位置
String.prototype.slice()

特例:
''.slice(1) ----------- 返回 '' 空字符串

案例:
window.location.hash 
// 由於:當地址欄的url中的hash不存在時,window.location.hash返回的是空字符串,這種狀況以下
// 因此:window.location.hash.slice(1) => 返回空字符串
複製代碼

window.location 對象

window.location 對象

屬性:
pathname: 返回url的path部分,/開始 ?以前 或者 /開始到結果,若是沒有query和hash
origin:protocal + hostname + port 三者之和,至關於協議,域名,端口
protocal:協議 http:// https://
hostnme: 主機名
port:端口號
host:主機 (hostname + port)
search:查詢字符串 (?開頭到#以前,或者?開頭到結尾)
hash:片斷字符串 (哈希值,#開頭到結尾)
複製代碼

hash路由

  • url中的hash以#號開頭,本來用來做爲錨點,從而定位到頁面的特定區域
  • 當 hash 發生改變時,頁面不會刷新,瀏覽器也不會向服務器發送請求
  • 注意:hash改變時,能夠觸發 hashchange 事件,在監聽函數中能夠請求數據,實現頁面的更新操做

做爲錨點,定位頁面特色區域

<a href="#anchor1">錨點1</a>
<a href="#anchor2">錨點2</a>

<div id="anchor1">錨點1的位置</div>
<div id="anchor2">錨點2的位置</div>

說明:
- 點擊a2,頁面會跳轉到div2的位置
- 而且頁面的hash部分也會改變,即 url 中以 #開頭的字符串會改變
- anchor:是錨的意思

- 注意:a標籤的name屬性已經廢棄,用id代替 (由於有的教程使用name屬性實現的)
複製代碼

hashchange事件

  • 若是監聽了hashchange事件,hash改變,地址欄的url中的hash部分就會改變,同時hashchange也會觸發
  • 可是頁面不會刷新,即瀏覽器的刷新按鈕的圈圈不會轉動
  • 可是能夠利用hashchange的回調函數更新頁面的內容,注意不是頁面刷新
<body>
  <a href="#anchor1">錨點1</a>
  <a href="#anchor2">錨點2</a>
  <script>
    window.addEventListener('hashchange', function() {
      console.log('111111111')
    }, false)
  </script>
</body>

說明:
- 點擊a標籤,url中的hash改變,hash改變,hashchange事件觸發,則監聽函數就會執行,輸出111111
複製代碼

手動實現一個 hash-router

hash-router

原理:
(1) hash改變,地址欄url的hash字符串改變,觸發hashchange事件
(2) 在hashchange事件的回調函數中更新視圖


代碼:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
<a href="#/home">home</a>
<a href="#/other">other</a>
<div id="content">內容部分</div>

<script>
  const routes = [{
    path: '/home',
    component: '<h1>home頁面</h1>'
  }, {
    path: '/other',
    component: '<h1>other頁面</h1>'
  }]

  class Router {
    constructor(routes) { // 構造函數
      this.route = {} // 路由映射
      this.createRouteMap(routes) // 建立路由映射
      this.init() // 初始化
    }
    createRouteMap = (routes) => {
      routes.forEach(item => {
        this.route[item.path] = () => {
          document.getElementById('content').innerHTML = item.component
          // 函數體的做用:將id是content的div中的內容換成 componet
        }
        // 循環配置
        // key是path
        // value包裝成一個更新html內容的函數,在 load 和 hahschange 中調用
      })
    }
    init = () => {
      window.addEventListener('load', this.updateView, false) // 頁面加載完成時觸發,注意區分DOMContentLoaded
      window.addEventListener('hashchange', this.updateView, false)
    }
    updateView = () => {
      const hash = window.location.hash.slice(1) || '/home'; // 初次加載home頁面
      // load事件觸發時,window.location.hash => 返回 '' 空字符串
      // ''.slice(1) => 返回''
      if (this.route[hash]) this.route[hash]()
      // 存在,則執行函數
    }
  }

  new Router(routes)
</script>
</body>
</html>

注意:該html須要用 Live Server 啓動,vscode插件
複製代碼

history路由

window.history 對象

  • window.history對象的方法:back()forward()go()pushState()replaceState()
  • pushState()
  • replaceState()
  • pushState() 和 replaceState()
    • 不會觸發頁面刷新,只能致使History對象發生變化,地址欄的url會變化
    • 會改變url,不會觸發 popstate 事件,地址欄的url有所變化

window.history.pushState(state, title, url)

  • window.history.pushState(state, title, url)
  • state:一個與添加的記錄相關聯的對象
  • title:新頁面的標題,如今全部瀏覽器都忽略該參數,能夠傳入空字符串
  • url:新的url地址,必須與當前頁面同一個域,瀏覽器的地址欄顯示這個網址
  • window.history.pushState({}, null, url)
  • 注意:pushState不會刷新頁面,只會改變History對象,地址欄url會變化
    • 能夠經過 History.state 讀取狀態對象

popstate

  • popstate觸發的條件
    • 瀏覽器的前進後退按鈕
    • history.go(), history.back(), history.forward()
  • 注意:window.history.pushState() 和 window.history.replaceState()不會觸發 popstate 事件
  • 注意:pushState()和replaceState()能夠改變url,且實現不向服務器發送請求,不存在#號,比hash路由更美觀,可是 History 路由須要服務器的支持,而且需將全部的路由重定向到根頁面

手動實現一個 history-router

history-router

原理:
(1) 封裝一個方法,在pushState()和replaceState()改變url後調用,在該方法中獲取最新的window.location.path,更相信頁面
(2) 經過 go() back() forward() 瀏覽器前進後退等觸發 popstate 事件


代碼:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <a href="javascript:void(0)" data-href="/home">home</a> // html中的自定義屬性
  <a href="javascript:void(0)" data-href="/other">other</a>
  <div id="content">content的內容</div>

  <script>
    const routes = [{
      path: '/home',
      component: '<h1>home頁面</h1>'
    }, {
      path: '/other',
      component: '<h1>other頁面</h1>'
    }]

    class Router {
      constructor(routes) {
        this.route = {} // key-value鍵值對,key是path,value是更新視圖的函數
        this.createRouteMap(routes) // 建立路由映射
        this.bindEvent() // 綁定a標籤的點擊事件
        this.init() // 綁定load和popstate事件
      }
      createRouteMap = (routes) => {
        routes.forEach(item => {
          this.route[item.path] = () => {
            document.getElementById('content').innerHTML = item.component
          }
        })
      }
      bindEvent = () => {
        const a = document.getElementsByTagName('a')
        Array.prototype.forEach.call(a, item => { // 第二個參數,是forEach須要傳入的回調函數
          item.addEventListener('click', () => {
            const path = item.getAttribute('data-href') // 獲取data-herf屬性
            this.pushStateFn(path) 
            // 執行History.pushState()方法
            // 這裏因爲是箭頭函數,this指向父級所在的上下文環境,即 bindEvent 所在的上下文環境,即Router 
          }, false)
        })
      }
      pushStateFn = (url) => {
        window.history.pushState({}, null, url) // 改變url後,調用更新視圖的函數updateView
        this.updateView() // 更新視圖
      }
      init = () => {
        window.addEventListener('load', this.updateView, false) // 頁面加載完成時觸發
        window.addEventListener('popstate', this.updateView, false) // 瀏覽器前進後退,History.go() back() forward()時觸發
      }
      updateView = () => {
        const path = window.location.pathname || '/'; // 獲取url的path部分
        if(this.route[path]) this.route[path]() // path在route中存在,就執行對象的函數,key-value鍵值對
      }
    }

    new Router(routes)
  </script>
</body>
</html>
複製代碼

手動實現一個vue-router(hash版)

手動實現一個vue-router(hash版)

vue相關前置知識
- <router-link to="#/home">home</router-link> // 點擊會跳轉到 '#/home' 地址
- <router-view></router-view> // 路由將顯示的DOM位置

// 定義一個名爲 button-counter 的新組件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
- 由於組件是可複用的 Vue 實例,因此它們與 new Vue 接收相同的選項
- 例如 data、computed、watch、methods 以及生命週期鉤子等。


---------------
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <!-- 引入 Vue 經過CDN引入 -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <!-- 注意 router-link組件具備 to 屬性 -->
    <router-link to="#/home">home</router-link>
    <router-link to="#/other">other</router-link>
    <router-view></router-view>
  </div>

  <script>
    // 建立兩個vue組件
    const Home = {template: '<h1>home頁面</h1>'}
    const Other = {template: '<h1>other頁面</h1>'}

    // 建立vue路由數組
    const routes = [{
      path: '/home',
      component: Home
    }, {
      path: '/other',
      component: Other
    }]

    class VueRouter {
      constructor(Vue, option) {
        // 參數:
        // Vue:Vue構造函數,經過cdn引入的
        // option: 配置對象,包含routes路由數組屬性
        this.$options = option
        this.routeMap = {} // 路由映射,是這樣的結構 { path: component }
        this.createRouteMap(this.$options) // 建立路由映射

        this.app = new Vue({
          data: {
            currentHash: '#/'
          }
        })
        // this.app.currentHash => 能夠訪問到currentHash的值 '#/'
        // 舉例
        // var data = {name: 'woow_wu7'}
        // var vm = new Vue({
        //  data: data
        // })
        // vm.name === data.name => true

        this.init() // 初始化監聽函數
        this.initComponent(Vue) // 初始化Vue種的各類組件
      }
      createRouteMap = (option) => {
        // 注意:option 是傳入VueRoute的第二個參數,即 {routes: routes}
        // 因此:options是一個對象
        option.routes.forEach(item => {
          this.routeMap[item.path] = item.component
          // this.routeMap是這樣一個對象:{path: component}
        })
      }
      init = () => {
        window.addEventListener('load', this.onHashChange, false)
        // 頁面加載完成觸發,注意區別 DOMContentLoaded
        // load:頁面加載完成時觸發,包括 DOM加載完成,圖片,視頻等全部資源加載完成
        // DOMContentLoaded:DOM加載完成時觸發
        window.addEventListener('hashchange', this.onHashChange, false)
        // 監聽 hashchange 事件
        // 觸發hashchange的條件:hash改變時候
      }
      onHashChange = () => {
        this.app.currentHash = window.location.hash.slice(1) || '/'
        // (1)
        // 當 hahs沒有改變時,load事件觸發時
        // window.location.hash = '' =>  window.location.hash.slice(1) = ''
        // 因此:此種狀況:this.app.currentHash =  '/'
        // (2)
        // hash改變時,window.location.hash有值,是 '#/...' 這樣的字符串
      }
      initComponent = (Vue) => {
        // router-link組件
        // props to屬性
        // template 本質上會被處理成a標籤,href屬性是傳入的 to 屬性,內容是 slot 插入的內容
        Vue.component('router-link', {
          props: {
            to: {
              type: String,
              value: ''
            }
          },
          template: '<a :href="to"><slot/></a>'
        })
        Vue.component('router-view', {
          render: (h) => {
            const component = this.routeMap[this.app.currentHash] // 拿到最新hash對應的組件
            return h(component)
            // h(component) 至關於 createElement(component)
            // render: function(createElement) { return createElement(App); }
          }
        })
      }
    }
    new VueRouter(Vue, {
      routes
    })
    new Vue({
      el: '#app'
    })
  </script>
</body>
</html>
複製代碼

資料

URI和URL:www.luyuqiang.com/uri-url-urn…
URI和URL的區別舉例(很形象)juejin.im/post/5cd4e4…
URL的組成(優秀)www.jianshu.com/p/406d19dfa…
DOMContentLoaded和load的區別:www.jianshu.com/p/1a8a7e698…
window.location對象:wangdoc.com/javascript/…
vue-router模擬實現 juejin.im/post/5b35dc…
hash history 路由 模擬實現 juejin.im/post/5b3301…
vue-router源碼記錄 juejin.im/post/5cf9f7…
VueRouter源碼分析 juejin.im/post/5cb2c1…vue

相關文章
相關標籤/搜索