国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频

淺析 vue-router 源碼和動態(tài)路由權(quán)限分配

共 23200字,需瀏覽 47分鐘

 ·

2020-10-25 17:18



背景

上月立過一個 flag,看完 vue-router 的源碼,可到后面逐漸發(fā)現(xiàn) vue-router 的源碼并不是像很多總結(jié)的文章那么容易理解,閱讀過你就會發(fā)現(xiàn)里面的很多地方都會有多層的函數(shù)調(diào)用關(guān)系,還有大量的 this 指向問題,而且會有很多輔助函數(shù)需要去理解。但還是堅持啃下來了(當然還沒看完,內(nèi)容是真的多),下面是我在政采云(實習)工作閑暇時間閱讀源碼的一些感悟和總結(jié),并帶分析了大三時期使用的 vue-element-admin?這個 vuer 無所不知的后臺框架的動態(tài)路由權(quán)限控制原理。順便附帶本文實踐 demo 地址: 基于后臺框架開發(fā)的 學生管理系統(tǒng) (https://github.com/251205668/student-admin-template)。

vue-router 源碼分析

首先閱讀源碼之前最好是將 Vuevue-router 的源碼克隆下來,然后第一遍閱讀建議先跟著 官方文檔 (https://router.vuejs.org/zh/) 先走一遍基礎(chǔ)用法,然后第二遍開始閱讀源碼,先理清楚各層級目錄的作用和抽出一些核心的文件出來,過一遍代碼的同時寫個小的 demo 邊看邊打斷點調(diào)試,看不懂沒關(guān)系,可以邊看邊參考一些總結(jié)的比較好的文章,最后將比較重要的原理過程根據(jù)自己的理解整理出來,然后畫一畫相關(guān)的知識腦圖加深印象。

前置知識: flow 語法

JS 在編譯過程中可能看不出一些隱蔽的錯誤,但在運行過程中會報各種各樣的 bug。flow (https://flow.org/en/docs/getting-started/) 的作用就是編譯期間進行靜態(tài)類型檢查,盡早發(fā)現(xiàn)錯誤,拋出異常。

Vue、Vue-router 等大型項目往往需要這種工具去做靜態(tài)類型檢查以保證代碼的可維護性和可靠性。本文所分析的 vue-router 源碼中就大量的采用了 flow 去編寫函數(shù),所以學習 flow 的語法是有必要的。

首先安裝 flow 環(huán)境,初始化環(huán)境

npm?install?flow-bin?-g
flow?init

index.js 中輸入這一段報錯的代碼

/*@flow*/
function add(x: string, y: number): number {
return x + y
}
add(2, 11)

在控制臺輸入 flow,這個時候不出意外就會拋出異常提示,這就是簡單的 flow 使用方法。

具體用法還需要參考 flow官網(wǎng) (https://flow.org/en/docs/types/primitives/),另外這種語法是類似于 TypeScript (https://www.typescriptlang.org/) 的。

注冊

我們平時在使用 vue-router 的時候通常需要在 main.js 中初始化 Vue 實例時將 vue-router 實例對象當做參數(shù)傳入

例如:

import?Router?from?'vue-router'
Vue.use(Router)
const?routes?=?[
???{
?????path:?'/student',
????name:?'student',
????component:?Layout,
????meta:?{?title:?'學生信息查詢',?icon:?'documentation',?roles:?['student']?},
????children:?[
??????{
????????path:?'info',
????????component:?()?=>?import('@/views/student/info'),
????????name:?'studentInfo',
????????meta:?{?title:?'信息查詢',?icon:?'form'?}
??????},
??????{
????????path:?'score',
????????component:?()?=>?import('@/views/student/score'),
????????name:?'studentScore',
????????meta:?{?title:?'成績查詢',?icon:?'score'?}
??????}
????]
??}
??...
];
const?router?=?new?Router({
??mode:?"history",
??linkActiveClass:?"active",
??base:?process.env.BASE_URL,
??routes
});
new?Vue({
????router,
????store,
????render:?h?=>?h(App)
}).$mount("#app");

Vue.use

那么 Vue.use(Router) 又在做什么事情呢

問題定位到 Vue 源碼中的 src/core/global-api/use.js 源碼地址 (https://github.com/vuejs/vue/blob/dev/src/core/global-api/use.js)

export?function?initUse?(Vue:?GlobalAPI)?{
??Vue.use?=?function?(plugin:?Function?|?Object)?{
????//?拿到?installPlugins?
????const?installedPlugins?=?(this._installedPlugins?||?(this._installedPlugins?=?[]))
????//?保證不會重復(fù)注冊
????if?(installedPlugins.indexOf(plugin)?>?-1)?{
??????return?this
????}
????//?獲取第一個參數(shù)?plugins?以外的參數(shù)
????const?args?=?toArray(arguments,?1)
????//?將?Vue?實例添加到參數(shù)
????args.unshift(this)
????//?執(zhí)行?plugin?的?install?方法?每個?insatll?方法的第一個參數(shù)都會變成?Vue,不需要額外引入
????if?(typeof?plugin.install?===?'function')?{
??????plugin.install.apply(plugin,?args)
????}?else?if?(typeof?plugin?===?'function')?{
??????plugin.apply(null,?args)
????}
????//?最后用?installPlugins?保存?
????installedPlugins.push(plugin)
????return?this
??}
}

可以看到 Vueuse 方法會接受一個 plugin 參數(shù),然后使用 installPlugins 數(shù)組 保存已經(jīng)注冊過的 plugin。首先保證 plugin 不被重復(fù)注冊,然后將 Vue 從函數(shù)參數(shù)中取出,將整個 Vue 作為 plugininstall 方法的第一個參數(shù),這樣做的好處就是不需要麻煩的另外引入 Vue,便于操作。接著就去判斷 plugin 上是否存在 install 方法。存在則將賦值后的參數(shù)傳入執(zhí)行 ,最后將所有的存在 install 方法的 plugin 交給 installPlugins維護。

install

了解清楚 Vue.use 的結(jié)構(gòu)之后,可以得出 Vue 注冊插件其實就是在執(zhí)行插件的 install 方法,參數(shù)的第一項就是 Vue,所以我們將代碼定位到 vue-router 源碼中的 src/install.js 源碼地址 (https://github.com/vuejs/vue-router/blob/dev/src/install.js)

//?保存?Vue?的局部變量
export?let?_Vue
export?function?install?(Vue)?{
??//?如果已安裝
??if?(install.installed?&&?_Vue?===?Vue)?return
??install.installed?=?true
?//?局部變量保留傳入的?Vue
??_Vue?=?Vue
??const?isDef?=?v?=>?v?!==?undefined
??const?registerInstance?=?(vm,?callVal)?=>?{
????let?i?=?vm.$options._parentVnode
????if?(isDef(i)?&&?isDef(i?=?i.data)?&&?isDef(i?=?i.registerRouteInstance))?{
??????i(vm,?callVal)
????}
??}
??//?全局混入鉤子函數(shù)?每個組件都會有這些鉤子函數(shù),執(zhí)行就會走這里的邏輯
??Vue.mixin({
????beforeCreate?()?{
??????if?(isDef(this.$options.router))?{
????????//?new?Vue?時傳入的根組件?router?router對象傳入時就可以拿到?this.$options.router
????????//?根?router
????????this._routerRoot?=?this
????????this._router?=?this.$options.router
????????this._router.init(this)
????????//?變成響應(yīng)式
????????Vue.util.defineReactive(this,?'_route',?this._router.history.current)
??????}?else?{
????????//?非根組件訪問根組件通過$parent
????????this._routerRoot?=?(this.$parent?&&?this.$parent._routerRoot)?||?this
??????}
??????registerInstance(this,?this)
????},
????destroyed?()?{
??????registerInstance(this)
????}
??})
??//?原型加入?$router?和?$route
??Object.defineProperty(Vue.prototype,?'$router',?{
????get?()?{?return?this._routerRoot._router?}
??})
??Object.defineProperty(Vue.prototype,?'$route',?{
????get?()?{?return?this._routerRoot._route?}
??})
//?全局注冊
??Vue.component('RouterView',?View)
??Vue.component('RouterLink',?Link)
//?獲取合并策略
??const?strats?=?Vue.config.optionMergeStrategies
??//?use?the?same?hook?merging?strategy?for?route?hooks
??strats.beforeRouteEnter?=?strats.beforeRouteLeave?=?strats.beforeRouteUpdate?=?strats.created
}

可以看到這段代碼核心部分就是在執(zhí)行 install 方法時使用 mixin 的方式將每個組件都混入 beforeCreate,destroyed 這兩個生命周期鉤子。在 beforeCreate 函數(shù)中會去判斷當前傳入的 router 實例是否是根組件,如果是,則將 _routerRoot 賦值為當前組件實例、_router 賦值為傳入的VueRouter 實例對象,接著執(zhí)行 init 方法初始化 router,然后將 this_route 響應(yīng)式化。非根組件的話 _routerRoot 指向 $parent 父實例。然后執(zhí)行 registerInstance(this,this) 方法,該方法后會,接著原型加入 $router$route,最后注冊 RouterViewRouterLink,這就是整個 install 的過程。

小結(jié)

Vue.use(plugin) 實際上在執(zhí)行 plugin上的 install 方法,insatll 方法有個重要的步驟:

  • 使用 mixin 在組件中混入 beforeCreate , destory 這倆個生命周期鉤子
  • beforeCreate 這個鉤子進行初始化。
  • 全局注冊 router-view,router-link組件

VueRouter

接著就是這個最重要的 class : VueRouter。這一部分代碼比較多,所以不一一列舉,挑重點分析。vueRouter源碼地址 (https://github.com/vuejs/vue-router/blob/v3.1.2/src/index.js)。

構(gòu)造函數(shù)

??constructor?(options:?RouterOptions?=?{})?{
????this.app??=?null
????this.apps?=?[]
????//?傳入的配置項
????this.options?=?options
????this.beforeHooks?=?[]
????this.resolveHooks?=?[]
????this.afterHooks?=?[]
????this.matcher?=?createMatcher(options.routes?||?[],?this)
????//?一般分兩種模式?hash?和?history?路由?第三種是抽象模式
????let?mode?=?options.mode?||?'hash'
????//?判斷當前傳入的配置是否能使用?history?模式
????this.fallback?=?mode?===?'history'?&&?!supportsPushState?&&?options.fallback?!==?false
????//?降級處理
????if?(this.fallback)?{
??????mode?=?'hash'
????}
????if?(!inBrowser)?{
??????mode?=?'abstract'
????}
????this.mode?=?mode
????//?根據(jù)模式實例化不同的?history,history?對象會對路由進行管理?繼承于history?class
????switch?(mode)?{
??????case?'history':
????????this.history?=?new?HTML5History(this,?options.base)
????????break
??????case?'hash':
????????this.history?=?new?HashHistory(this,?options.base,?this.fallback)
????????break
??????case?'abstract':
????????this.history?=?new?AbstractHistory(this,?options.base)
????????break
??????default:
????????if?(process.env.NODE_ENV?!==?'production')?{
??????????assert(false,?`invalid?mode:?${mode}`)
????????}
????}
??}

首先在初始化 vueRouter 整個對象時定義了許多變量,app 代表 Vue 實例,options 代表傳入的配置參數(shù),然后就是路由攔截有用的 hooks 和重要的 matcher (后文會寫到)。構(gòu)造函數(shù)其實在做兩件事情: 1. 確定當前路由使用的 mode;2. 實例化對應(yīng)的 history 對象。

init

接著完成實例化 vueRouter 之后,如果這個實例傳入后,也就是剛開始說的將 vueRouter 實例在初始化 Vue 時傳入,它會在執(zhí)行 beforeCreate 時執(zhí)行 init 方法

init?(app:?any)?{
??...
??this.apps.push(app)
??//?確保后面的邏輯只走一次
??if?(this.app)?{
????return
??}
??//?保存?Vue?實例
??this.app?=?app
??const?history?=?this.history
??//?拿到?history?實例之后,調(diào)用?transitionTo?進行路由過渡
??if?(history?instanceof?HTML5History)?{
????history.transitionTo(history.getCurrentLocation())
??}?else?if?(history?instanceof?HashHistory)?{
????const?setupHashListener?=?()?=>?{
??????history.setupListeners()
????}
????history.transitionTo(
??????history.getCurrentLocation(),
??????setupHashListener,
??????setupHashListener
????)
??}
}

init 方法傳入 Vue 實例,保存到 this.apps 當中。Vue實例 會取出當前的 this.history,如果是哈希路由,先走 setupHashListener 函數(shù),然后調(diào)一個關(guān)鍵的函數(shù) transitionTo 路由過渡,這個函數(shù)其實調(diào)用了 this.matcher.match 去匹配。

小結(jié)

首先在 vueRouter 構(gòu)造函數(shù)執(zhí)行完會完成路由模式的選擇,生成 matcher ,然后初始化路由需要傳入 vueRouter 實例對象,在組件初始化階段執(zhí)行 beforeCreate 鉤子,調(diào)用 init 方法,接著拿到 this.history 去調(diào)用 transitionTo 進行路由過渡。

Matcher

之前在 vueRouter 的構(gòu)造函數(shù)中初始化了 macther,本節(jié)將詳細分析下面這句代碼到底在做什么事情,以及 match 方法在做什么 源碼地址 (https://github.com/vuejs/vue-router/blob/dev/src/create-matcher.js)。

?this.matcher?=?createMatcher(options.routes?||?[],?this)

首先將代碼定位到create-matcher.js

export?function?createMatcher?(
??routes:?Array,
??router:?VueRouter
):?Matcher?
{
??//?創(chuàng)建映射表
??const?{?pathList,?pathMap,?nameMap?}?=?createRouteMap(routes)
??//?添加動態(tài)路由
??function?addRoutes(routes){...}
??//?計算新路徑
??function?match?(
????raw:?RawLocation,
????currentRoute?:?Route,
????redirectedFrom?:?Location
??
):?Route?
{...}
??//?...?后面的一些方法暫不展開
??
???return?{
????match,
????addRoutes
??}
}

createMatcher 接受倆參數(shù),分別是 routes,這個就是我們平時在 router.js 定義的路由表配置,然后還有一個參數(shù)是 router 他是 new vueRouter 返回的實例。

createRouteMap

下面這句代碼是在創(chuàng)建一張 path-record,name-record 的映射表,我們將代碼定位到 create-route-map.js 源碼地址 (https://github.com/vuejs/vue-router/blob/dev/src/create-route-map.js)

export?function?createRouteMap?(
??routes:?Array,
??oldPathList?:?Array,
??oldPathMap?:?Dictionary,
??oldNameMap?:?Dictionary
):?
{
??pathList:?Array,
??pathMap:?Dictionary,
??nameMap:?Dictionary
}?{
??//?記錄所有的?path
??const?pathList:?Array?=?oldPathList?||?[]
??//?記錄?path-RouteRecord?的?Map
??const?pathMap:?Dictionary?=?oldPathMap?||?Object.create(null)
???//?記錄?name-RouteRecord?的?Map
??const?nameMap:?Dictionary?=?oldNameMap?||?Object.create(null)
??//?遍歷所有的?route?生成對應(yīng)映射表
??routes.forEach(route?=>?{
????addRouteRecord(pathList,?pathMap,?nameMap,?route)
??})
??//?調(diào)整優(yōu)先級
??for?(let?i?=?0,?l?=?pathList.length;?i?????if?(pathList[i]?===?'*')?{
??????pathList.push(pathList.splice(i,?1)[0])
??????l--
??????i--
????}
??}
??return?{
????pathList,
????pathMap,
????nameMap
??}
}

createRouteMap 需要傳入路由配置,支持傳入舊路徑數(shù)組和舊的 Map 這一步是為后面遞歸和 addRoutes 做好準備。首先用三個變量記錄 path,pathMap,nameMap,接著我們來看 addRouteRecord 這個核心方法。這一塊代碼太多了,列舉幾個重要的步驟

//?解析路徑
const?pathToRegexpOptions:?PathToRegexpOptions?=
????route.pathToRegexpOptions?||?{}
//?拼接路徑
const?normalizedPath?=?normalizePath(path,?parent,?pathToRegexpOptions.strict)
//?記錄路由信息的關(guān)鍵對象,后續(xù)會依此建立映射表
const?record:?RouteRecord?=?{
??path:?normalizedPath,
??regex:?compileRouteRegex(normalizedPath,?pathToRegexpOptions),
??//?route?對應(yīng)的組件
??components:?route.components?||?{?default:?route.component?},
??//?組件實例
??instances:?{},
??name,
??parent,
??matchAs,
??redirect:?route.redirect,
??beforeEnter:?route.beforeEnter,
??meta:?route.meta?||?{},
??props:?route.props?==?null
??????{}
????:?route.components
????????route.props
??????:?{?default:?route.props?}
}

使用 recod 對象 記錄路由配置有利于后續(xù)路徑切換時計算出新路徑,這里的 path 其實是通過傳入父級 record 對象的path和當前 path 拼接出來的 ?。然后 regex 使用一個庫將 path 解析為正則表達式。如果 route 有子節(jié)點就遞歸調(diào)用 addRouteRecord

?//?如果有?children?遞歸調(diào)用?addRouteRecord
????route.children.forEach(child?=>?{
??????const?childMatchAs?=?matchAs
??????????cleanPath(`${matchAs}/${child.path}`)
????????:?undefined
??????addRouteRecord(pathList,?pathMap,?nameMap,?child,?record,?childMatchAs)
????})

最后映射兩張表,并將 record·path 保存進 pathList,nameMap 邏輯相似就不列舉了

??if?(!pathMap[record.path])?{
????pathList.push(record.path)
????pathMap[record.path]?=?record
??}

廢了這么大勁將 pathListpathMapnameMap 抽出來是為啥呢? 首先 pathList 是記錄路由配置所有的 path,然后 pathMapnameMap 方便我們傳入 path 或者 name 快速定位到一個 record,然后輔助后續(xù)路徑切換計算路由的。

addRoutes

這是在 vue2.2.0 之后新添加的 api ,或許很多情況路由并不是寫死的,需要動態(tài)添加路由。有了前面的 createRouteMap 的基礎(chǔ)上我們只需要傳入 routes 即可,他就能在原基礎(chǔ)上修改

function?addRoutes?(routes)?{
??createRouteMap(routes,?pathList,?pathMap,?nameMap)
}

并且看到在 createMathcer 最后返回了這個方法,所以我們就可以使用這個方法

return?{
????match,
????addRoutes
??}

match

function?match?(
??raw:?RawLocation,
??currentRoute?:?Route,
??redirectedFrom?:?Location
):?Route?
{
??...
}

接下來就是 match 方法,它接收 3 個參數(shù),其中 rawRawLocation 類型,它可以是一個 url 字符串,也可以是一個 Location 對象;currentRouteRoute 類型,它表示當前的路徑;redirectedFrom 和重定向相關(guān)。match 方法返回的是一個路徑,它的作用是根據(jù)傳入的 raw 和當前的路徑 currentRoute 計算出一個新的路徑并返回。至于他是如何計算出這條路徑的,可以詳細看一下如何計算出locationnormalizeLocation 方法和 _createRoute 方法。

小結(jié)

  • createMatcher: 根據(jù)路由的配置描述建立映射表,包括路徑、名稱到路由 record 的映射關(guān)系, 最重要的就是 createRouteMap 這個方法,這里也是動態(tài)路由匹配和嵌套路由的原理。
  • addRoutes: 動態(tài)添加路由配置
  • match: 根據(jù)傳入的 raw 和當前的路徑 currentRoute 計算出一個新的路徑并返回。

路由模式

vue-router 支持三種路由模式(mode):hash、history、abstract,其中 abstract 是在非瀏覽器環(huán)境下使用的路由模式 源碼地址 (https://github.com/vuejs/vue-router/blob/dev/src/index.js)。

這一部分在前面初始化 vueRouter 對象時提到過,首先拿到配置項的模式,然后根據(jù)當前傳入的配置判斷當前瀏覽器是否支持這種模式,默認 IE9 以下會降級為 hash。然后根據(jù)不同的模式去初始化不同的 history 實例。

????//?一般分兩種模式?hash?和?history?路由?第三種是抽象模式不常用
????let?mode?=?options.mode?||?'hash'
????//?判斷當前傳入的配置是否能使用?history?模式
????this.fallback?=?mode?===?'history'?&&?!supportsPushState?&&?options.fallback?!==?false
????//?降級處理
????if?(this.fallback)?{
??????mode?=?'hash'
????}
????if?(!inBrowser)?{
??????mode?=?'abstract'
????}
????this.mode?=?mode
????//?根據(jù)模式實例化不同的?history?history?對象會對路由進行管理?繼承于?history?class
????switch?(mode)?{
??????case?'history':
????????this.history?=?new?HTML5History(this,?options.base)
????????break
??????case?'hash':
????????this.history?=?new?HashHistory(this,?options.base,?this.fallback)
????????break
??????case?'abstract':
????????this.history?=?new?AbstractHistory(this,?options.base)
????????break
??????default:
????????if?(process.env.NODE_ENV?!==?'production')?{
??????????assert(false,?`invalid?mode:?${mode}`)
????????}
????}

小結(jié)

vue-router 支持三種路由模式,hash、history和?abstract。默認為 hash,如果當前瀏覽器不支持?history則會做降級處理,然后完成 history 的初始化。

路由切換

切換 url 主要是調(diào)用了 push 方法,下面以哈希模式為例,分析push方法實現(xiàn)的原理 。push 方法切換路由的實現(xiàn)原理 源碼地址 (https://github.com/vuejs/vue-router/blob/dev/src/history/hash.js)

首先在 src/index.js 下找到 vueRouter 定義的 push 方法

??push?(location:?RawLocation,?onComplete?:?Function,?onAbort?:?Function)?{
????//?$flow-disable-line
????if?(!onComplete?&&?!onAbort?&&?typeof?Promise?!==?'undefined')?{
??????return?new?Promise((resolve,?reject)?=>?{
????????this.history.push(location,?resolve,?reject)
??????})
????}?else?{
??????this.history.push(location,?onComplete,?onAbort)
????}
??}

接著我們需要定位到 history/hash.js。這里首先獲取到當前路徑然后調(diào)用了 transitionTo 做路徑切換,在回調(diào)函數(shù)當中執(zhí)行 pushHash 這個核心方法。

push?(location:?RawLocation,?onComplete?:?Function,?onAbort?:?Function)?{
????const?{?current:?fromRoute?}?=?this
????//?路徑切換的回調(diào)函數(shù)中調(diào)用?pushHash
????this.transitionTo(
??????location,
??????route?=>?{
????????pushHash(route.fullPath)
????????handleScroll(this.router,?route,?fromRoute,?false)
????????onComplete?&&?onComplete(route)
??????},
??????onAbort
????)
??}

pushHash 方法在做完瀏覽器兼容判斷后調(diào)用的 pushState 方法,將 url 傳入

export?function?pushState?(url?:?string,?replace?:?boolean)?{
??const?history?=?window.history
??try?{
???//?調(diào)用瀏覽器原生的?history?的?pushState?接口或者?replaceState?接口,pushState?方法會將?url?入棧
????if?(replace)?{
??????history.replaceState({?key:?_key?},?'',?url)
????}?else?{
??????_key?=?genKey()
??????history.pushState({?key:?_key?},?'',?url)
????}
??}?catch?(e)?{
????window.location[replace???'replace'?:?'assign'](url)
??}
}

可以發(fā)現(xiàn),push 底層調(diào)用了瀏覽器原生的 historypushStatereplaceState 方法,不是 replace 模式 會將 url 推歷史棧當中。

另外提一嘴拼接哈希的原理

源碼位置 (https://github.com/vuejs/vue-router/blob/dev/src/history/hash.js)

初始化 HashHistory 時,構(gòu)造函數(shù)會執(zhí)行 ensureSlash 這個方法

export?class?HashHistory?extends?History?{
??constructor?(router:?Router,?base:??string,?fallback:?boolean)?{
????...
????ensureSlash()
??}
??...
??}

這個方法首先調(diào)用 getHash,然后執(zhí)行 replaceHash()

function?ensureSlash?():?boolean?{
??const?path?=?getHash()
??if?(path.charAt(0)?===?'/')?{
????return?true
??}
??replaceHash('/'?+?path)
??return?false
}

下面是這幾個方法

export?function?getHash?():?string?{
??const?href?=?window.location.href
??const?index?=?href.indexOf('#')
??return?index?===?-1???''?:?href.slice(index?+?1)
}
//?真正拼接哈希的方法?
function?getUrl?(path)?{
??const?href?=?window.location.href
??const?i?=?href.indexOf('#')
??const?base?=?i?>=?0???href.slice(0,?i)?:?href
??return?`${base}#${path}`
}
function?replaceHash?(path)?{
??if?(supportsPushState)?{
????replaceState(getUrl(path))
??}?else?{
????window.location.replace(getUrl(path))
??}
}
export?function?replaceState?(url?:?string)?{
??pushState(url,?true)
}

舉個例子來說: 假設(shè)當前URL是 http://localhost:8080,path 為空,執(zhí)行 replcaeHash('/' + path),然后內(nèi)部執(zhí)行 getUrl 計算出 urlhttp://localhost:8080/#/,最后執(zhí)行 pushState(url,true),就大功告成了!

小結(jié)

hash 模式的 push 方法會調(diào)用路徑切換方法 transitionTo,接著在回調(diào)函數(shù)中調(diào)用pushHash方法,這個方法調(diào)用的 pushState 方法底層是調(diào)用了瀏覽器原生 history 的方法。pushreplace 的區(qū)別就在于一個將 url 推入了歷史棧,一個沒有,最直觀的體現(xiàn)就是 replace 模式下瀏覽器點擊后退不會回到上一個路由去 ,另一個則可以。

router-view & router-link

vue-routerinstall 時全局注冊了兩個組件一個是 router-view 一個是 router-link,這兩個組件都是典型的函數(shù)式組件。源碼地址 (https://github.com/vuejs/vue-router/tree/dev/src/components)

router-view

首先在 router 組件執(zhí)行 beforeCreate 這個鉤子時,把 this._route 轉(zhuǎn)為了響應(yīng)式的一個對象

?Vue.util.defineReactive(this,?'_route',?this._router.history.current)

所以說每次路由切換都會觸發(fā) router-view 重新 render 從而渲染出新的視圖。

核心的 render 函數(shù)作用請看代碼注釋

??render?(_,?{?props,?children,?parent,?data?})?{
????...
????//?通過?depth?由?router-view?組件向上遍歷直到根組件,遇到其他的?router-view?組件則路由深度+1?這里的?depth?最直接的作用就是幫助找到對應(yīng)的?record
????let?depth?=?0
????let?inactive?=?false
????while?(parent?&&?parent._routerRoot?!==?parent)?{
??????//?parent.$vnode.data.routerView?為?true?則代表向上尋找的組件也存在嵌套的?router-view?
??????if?(parent.$vnode?&&?parent.$vnode.data.routerView)?{
????????depth++
??????}
??????if?(parent._inactive)?{
????????inactive?=?true
??????}
??????parent?=?parent.$parent
????}
????data.routerViewDepth?=?depth
????if?(inactive)?{
??????return?h(cache[name],?data,?children)
????}
???//?通過?matched?記錄尋找出對應(yīng)的?RouteRecord?
????const?matched?=?route.matched[depth]
????if?(!matched)?{
??????cache[name]?=?null
??????return?h()
????}
?//?通過?RouteRecord?找到?component
????const?component?=?cache[name]?=?matched.components[name]
???//?往父組件注冊?registerRouteInstance?方法
????data.registerRouteInstance?=?(vm,?val)?=>?{?????
??????const?current?=?matched.instances[name]
??????if?(
????????(val?&&?current?!==?vm)?||
????????(!val?&&?current?===?vm)
??????)?{
????????matched.instances[name]?=?val
??????}
????}
??//?渲染組件
????return?h(component,?data,?children)
??}

觸發(fā)更新也就是 setter 的調(diào)用,位于 src/index.js,當修改 _route 就會觸發(fā)更新。

history.listen(route?=>?{
??this.apps.forEach((app)?=>?{
????//?觸發(fā)?setter
????app._route?=?route
??})
})

router-link

分析幾個重要的部分:

  • 設(shè)置 active 路由樣式

router-link 之所以可以添加 router-link-activerouter-link-exact-active 這兩個 class 去修改樣式,是因為在執(zhí)行 render 函數(shù)時,會根據(jù)當前的路由狀態(tài),給渲染出來的 active 元素添加 class

render?(h:?Function)?{
??...
??const?globalActiveClass?=?router.options.linkActiveClass
??const?globalExactActiveClass?=?router.options.linkExactActiveClass
??//?Support?global?empty?active?class
??const?activeClassFallback?=?globalActiveClass?==?null
??????'router-link-active'
????:?globalActiveClass
??const?exactActiveClassFallback?=?globalExactActiveClass?==?null
??????'router-link-exact-active'
????:?globalExactActiveClass
????...
}
  • router-link 默認渲染為 a 標簽,如果不是會去向上查找出第一個 a 標簽
?if?(this.tag?===?'a')?{
??????data.on?=?on
??????data.attrs?=?{?href?}
????}?else?{
??????//?find?the?first??child?and?apply?listener?and?href
??????const?a?=?findAnchor(this.$slots.default)
??????if?(a)?{
????????//?in?case?the?
?is?a?static?node
????????a.isStatic?=?false
????????const?aData?=?(a.data?=?extend({},?a.data))
????????aData.on?=?on
????????const?aAttrs?=?(a.data.attrs?=?extend({},?a.data.attrs))
????????aAttrs.href?=?href
??????}?else?{
????????//?不存在則渲染本身元素
????????data.on?=?on
??????}
????}
const?handler?=?e?=>?{
??if?(guardEvent(e))?{
????if?(this.replace)?{
??????//?replace路由
??????router.replace(location)
????}?else?{
??????//?push?路由
??????router.push(location)
????}
??}
}

權(quán)限控制動態(tài)路由原理分析

我相信,開發(fā)過后臺項目的同學經(jīng)常會碰到以下的場景: 一個系統(tǒng)分為不同的角色,然后不同的角色對應(yīng)不同的操作菜單和操作權(quán)限。例如: 教師可以查詢教師自己的個人信息查詢?nèi)缓筮€可以查詢操作學生的信息和學生的成績系統(tǒng)、學生用戶只允許查詢個人成績和信息,不允許更改。在 vue2.2.0 之前還沒有加入 addRoutes 這個 API 是十分困難的的。

目前主流的路由權(quán)限控制的方式是:

  1. 登錄時獲取 token 保存到本地,接著前端會攜帶 token 再調(diào)用獲取用戶信息的接口獲取當前用戶的角色信息。
  2. 前端再根據(jù)當前的角色計算出相應(yīng)的路由表拼接到常規(guī)路由表后面。

登錄生成動態(tài)路由全過程

了解 如何控制動態(tài)路由之后,下面是一張全過程流程圖

前端在 beforeEach 中判斷:

結(jié)合框架源碼分析

下面結(jié)合 vue-element-admin 的源碼分析該框架中如何處理路由邏輯的。

路由訪問邏輯分析

首先可以定位到和入口文件 main.js 同級的 permission.js, 全局路由守衛(wèi)處理就在此。源碼地址 (https://github.com/251205668/student-admin-template/blob/master/src/permission.js)

const?whiteList?=?['/login',?'/register']?//?路由白名單,不會重定向
//?全局路由守衛(wèi)
router.beforeEach(async(to,?from,?next)?=>?{
??NProgress.start()?//路由加載進度條
??//?設(shè)置?meta?標題
??document.title?=?getPageTitle(to.meta.title)
??//?判斷?token?是否存在
??const?hasToken?=?getToken()
??if?(hasToken)?{
????if?(to.path?===?'/login')?{
??????//?有?token?跳轉(zhuǎn)首頁
??????next({?path:?'/'?})
??????NProgress.done()
????}?else?{
??????const?hasRoles?=?store.getters.roles?&&?store.getters.roles.length?>?0
??????if?(hasRoles)?{
????????next()
??????}?else?{
????????try?{
??????????//?獲取動態(tài)路由,添加到路由表中
??????????const?{?roles?}?=?await?store.dispatch('user/getInfo')
??????????const?accessRoutes?=?await?store.dispatch('permission/generateRoutes',?roles)
??????????router.addRoutes(accessRoutes)
??????????//??使用?replace?訪問路由,不會在?history?中留下記錄,登錄到?dashbord?時回退空白頁面
??????????next({?...to,?replace:?true?})
????????}?catch?(error)?{
??????????next('/login')
??????????NProgress.done()
????????}
??????}
????}
??}?else?{
????//?無?token
????//?白名單不用重定向?直接訪問
????if?(whiteList.indexOf(to.path)?!==?-1)?{
??????next()
????}?else?{
??????//?攜帶參數(shù)為重定向到前往的路徑
??????next(`/login?redirect=${to.path}`)
??????NProgress.done()
????}
??}
})

這里的代碼我都添加了注釋方便大家好去理解,總結(jié)為一句話就是訪問路由 /xxx,首先需要校驗 token 是否存在,如果有就判斷是否訪問的是登錄路由,走的不是登錄路由則需要判斷該用戶是否是第一訪問首頁,然后生成動態(tài)路由,如果走的是登錄路由則直接定位到首頁,如果沒有 token 就去檢查路由是否在白名單(任何情況都能訪問的路由),在的話就訪問,否則重定向回登錄頁面。

下面是經(jīng)過全局守衛(wèi)后路由變化的截圖

結(jié)合Vuex生成動態(tài)路由

下面就是分析這一步 const accessRoutes = await store.dispatch('permission/generateRoutes', roles) 是怎么把路由生成出來的。源碼地址 (https://github.com/251205668/student-admin-template/blob/master/src/store/modules/permission.js)

首先 vue-element-admin 中路由是分為兩種的:

//?無需校驗身份路由
export?const?constantRoutes?=?[
??{
????path:?'/login',
????component:?()?=>?import('@/views/login/index'),
????hidden:?true
??}
??...
??],
?//?需要校驗身份路由?
export?const?asyncRoutes?=?[
??//?學生角色路由
??{
????path:?'/student',
????name:?'student',
????component:?Layout,
????meta:?{?title:?'學生信息查詢',?icon:?'documentation',?roles:?['student']?},
????children:?[
??????{
????????path:?'info',
????????component:?()?=>?import('@/views/student/info'),
????????name:?'studentInfo',
????????meta:?{?title:?'信息查詢',?icon:?'form'?}
??????},
??????{
????????path:?'score',
????????component:?()?=>?import('@/views/student/score'),
????????name:?'studentScore',
????????meta:?{?title:?'成績查詢',?icon:?'score'?}
??????}
????]
??}]
??...

生成動態(tài)路由的源碼位于 src/store/modules/permission.js 中的 generateRoutes 方法,源碼如下:

?generateRoutes({?commit?},?roles)?{
????return?new?Promise(resolve?=>?{
??????let?accessedRoutes
??????if?(roles.includes('admin'))?{
????????accessedRoutes?=?asyncRoutes?||?[]
??????}?else?{
??????//?不是?admin?去遍歷生成對應(yīng)的權(quán)限路由表
????????accessedRoutes?=?filterAsyncRoutes(asyncRoutes,?roles)
??????}
??????//?vuex?中保存異步路由和常規(guī)路由
??????commit('SET_ROUTES',?accessedRoutes)
??????resolve(accessedRoutes)
????})
??}

route.js 讀取 asyncRoutesconstantRoutes 之后首先判斷當前角色是否是 admin,是的話默認超級管理員能夠訪問所有的路由,當然這里也可以自定義,否則去過濾出路由權(quán)限路由表,然后保存到 Vuex 中。最后將過濾之后的 asyncRoutesconstantRoutes 進行合并。過濾權(quán)限路由的源碼如下:

export?function?filterAsyncRoutes(routes,?roles)?{
??const?res?=?[]
??routes.forEach(route?=>?{
????//?淺拷貝
????const?tmp?=?{?...route?}
????//?過濾出權(quán)限路由
????if?(hasPermission(roles,?tmp))?{
??????if?(tmp.children)?{
????????tmp.children?=?filterAsyncRoutes(tmp.children,?roles)
??????}
??????res.push(tmp)
????}
??})
??return?res
}

首先定義一個空數(shù)組,對傳入 asyncRoutes 進行遍歷,判斷每個路由是否具有權(quán)限,未命中的權(quán)限路由直接舍棄 判斷權(quán)限方法如下:

function?hasPermission(roles,?route)?{
??if?(route.meta?&&?route.meta.roles)?{
????//?roles?有對應(yīng)路由元定義的?role?就返回?true
????return?roles.some(role?=>?route.meta.roles.includes(role))
??}?else?{
????return?true
??}
}

接著需要判斷二級路由、三級路由等等的情況,再做一層迭代處理,最后將過濾出來的路由推進數(shù)組返回。然后追加到 constantRoutes 后面

?SET_ROUTES:?(state,?routes)?=>?{
????state.addRoutes?=?routes
????state.routes?=?constantRoutes.concat(routes)
??}

動態(tài)路由生成全過程

總結(jié)

讀后感想

或許閱讀源碼的作用不能像一篇開發(fā)文檔一樣直接立馬對日常開發(fā)有所幫助,但是它的影響是長遠的,在讀源碼的過程中都可以學到眾多知識,類似閉包、設(shè)計模式、時間循環(huán)、回調(diào)等等 JS 進階技能,并穩(wěn)固并提升了你的 JS 基礎(chǔ)。當然這篇文章是有缺陷的,有幾個地方都沒有分析到,比如導(dǎo)航守衛(wèi)實現(xiàn)原理和路由懶加載實現(xiàn)原理,這一部分,我還在摸索當中。

如果一味的死記硬背一些所謂的面經(jīng),或者直接死記硬背相關(guān)的框架行為或者 API ,你很難在遇到比較復(fù)雜的問題下面去快速定位問題,了解怎么去解決問題,而且我發(fā)現(xiàn)很多人在使用一個新框架之后遇到點問題都會立馬去提對應(yīng)的 Issues,以至于很多流行框架 Issues 超過幾百個或者幾千個,但是許多問題都是因為我們并未按照設(shè)計者開發(fā)初設(shè)定的方向才導(dǎo)致錯誤的,更多都是些粗心大意造成的問題。

參考文章

帶你全面分析 vue-router 源碼 (萬字長文) (https://juejin.im/post/6844904064367460366)

vuejs 源碼解析 (https://github.com/answershuto/learnVue)

掃碼關(guān)注公眾號,訂閱更多精彩內(nèi)容。



你點的每個贊,我都認真當成了喜歡
瀏覽 40
點贊
評論
收藏
分享

手機掃一掃分享

分享
舉報
評論
圖片
表情
推薦
點贊
評論
收藏
分享

手機掃一掃分享

分享
舉報

感谢您访问我们的网站,您可能还对以下资源感兴趣:

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 五月色丁香| 免费看一级无码成人片| 国产伊人在线| 欧美成人电影在线观看| 久久三级片电影| 欧美成人视频18| 日韩超碰在线| 欧美操BB| 亚洲欧洲精品视频| 日日碰狠狠躁久久躁婷婷| 亚洲精品成人无码| 久久久久久免费视频| 亚洲精品A| 亚洲熟妇AV日韩熟妇在线| 91一区二区在线观看| 在线不卡视频| 欧美插穴| 天天艹| 黄色国产网站| 人人操人人妻人人| 国产无码一| 一区二区高清无码| 一级a一级a免费观看视频Al明星 | 在线观看中文字幕av| 色悠久久久| 日韩爆乳在线| 亚洲AV无码成人片在线| 一区二区中文字幕| 一本到在线观看午夜剧场| 动漫3d啪啪成人h动漫| avwww| 潮喷在线观看| 51妺嘿嘿在线电影免费观看| 先锋成人电影| 啊哈嗯| 国精产品一区一区三区四区| 无码三级片在线观看| www.国产在线| 最新av| 成人在线免费电影| 97人人干人人| 亚洲天堂视频网站| 黄色视频在线观看大全| 西西西444www无码视频| 91羞羞网站| 日韩视频一区| 无码人妻一区二区| 奇米av| 三级在线观看视频| 激情五月天黄色| 亚洲午夜精品成人毛片| 国产精品v欧美精品v日韩| 成人国产三级| 色图插插插| 亚州加勒比无码| 无码高清在线| 欧美三级片在线| 国产电影一区二区三区| 俺来也俺就去www色情网| 色热热| 国产精品自拍偷拍| 亚洲在线第一页| 一插菊花网| 亚洲高清无码视频大全| 另类激情网| 精国产品一区二区三区A片| 黄色片大香蕉| www.大吊视频| 日韩成人无码一区二区视频| 最近中文字幕免费mv第一季歌词強上 | 欧美狠狠| 中文字幕在线日韩| 波多野结衣无码AV在线| 99成人国产精品视频| 日逼网站免费观看| 自拍三级片| 久久av片| 在线观看污网站| 狠狠插狠狠操| 国产99自拍| 国产精品久久久久久久久久久久久久久 | 久久午夜成人电影| 在线观看一区二区视频| 白丝自慰网站| 日日免费视频| 亚洲国产97| 无码中文字幕高清| 青草社区在线观看| 风情万种AV| 91大神久久| 亚洲色无码人妻激情| 色天堂在线观看| 欧美三级片视频| 人人色人人爱| 一区二区三区视频免费| 一区二区三区不卡视频| www色色| 蜜桃免费视频| 免费a网站| 欧美黄色一级视频| 69毛片| 亚洲成人一区二区三区| 国产色情性黄片Av网站| 狼友视频在线观看18| 最新中文字幕777私人在线| 无码国产精品一区二区免费96| 成人做爰A片一区二区app| 亚洲香蕉在线| 口爆在线观看| 综合天堂AV久久久久久久| 日韩乱伦视频| 国产成人在线播放| 免费无码高清| 爱爱帝国综合社区| 国产一级a毛一级a毛片视频黑人| 国产一级a毛一级a毛片视频黑人| 黄网在线观看视频| 无套内射免费视频| 中文字幕综合在线| 香蕉中文在线| 人妻无码人妻| 51妺嘿嘿在线电影免费观看| 手机AV在线播放| 黄色激情五月| 四虎A片| 影音先锋AV在线资源| av四虎| 亚洲视频在线免费播放| 刘玥91精一区二区三区| 国模在线| 免费国产黄色| 性爱免费视频| 欧美日韩有码视频网址大全| 国产毛片久久久久久国产毛片| 日本成人激情视频| 人与鲁牲交| 无码观看视频| 一级黄色电影免费在线观看| 综合精品7799| 精品久久一区二区三区四区| www.婷婷六月天| 久久婷婷亚洲| 日韩国产成人| 青娱乐成人| 国产在线一区二区| 国产精品色在线回看| 五月天亭亭.com| 人人澡人人爽欧一区| 天天日天天干天天爽| 麻豆www| 男人操女人免费网站| 18精品爽国产冫绿帽社| 日韩人妻码一区二区三区| 日批视频在线观看| 亚洲免费成人网站| 91蝌蚪丨人妻丨丝袜| 国产无码专区| 亚洲色逼图片| 伊人久久大香蕉视频| 免费av播放| 欧美h在线观看| 色欲91| 日本高清无码在线观看| 国产成人大片| 日韩人妻精品无码久久| 青春草视频| 999久久久久| 91视频在线免费观看app| 韩国gogogo高清在线完整版| 日本a在线免费观看| 国产成人无码精品久在线观看| 强开小嫩苞一区二区电影| 亚洲免费播放| 无码一二区| 阿拉伯三级片| 在线中文AV| 一级黄色视频片| 色色播| 欧美不卡一区二区三区| 国产1区2区| 一级片在线播放| 欧美精品无码久久久精品酒店 | 久久国产免费视频| 国产迷奸视频| av日韩无码| 欧美手机在线| 亚洲国产另类精品| 三须三级久久三级久久18| 麻豆亚洲| 国产精品久久久久久久久久久久久 | 闷骚艳岳的婬乱生活视频| 嫩草av在线| 92自拍视频| 韩国无码一区二区| 狠狠色色| 国产porn| 久久一区二区三区四区| 狠狠干,狠狠操| 亚洲天堂一区在线观看| 成人免费黄色视频| 超碰九九| 日韩免费av| 久久人妻无码中文字幕系列| 国产在线接入| 无码水蜜桃一区二区| 九九热在线精品| 足浴小少妇-88AX| 逼逼AV网站-日韩电影| 精品国产免费无码久久噜噜噜AV | 免费黄片网站在线观看| 成人黄色免费| 3d动漫一区二区| 超碰1999| 丁香五月五月婷婷| 九九精品热| 国产麻豆AⅤMDMD0071| 一区二区视频在线| 四川少妇BBB| AV性爱社区| 精精国产| 成人A片免费观看| 99自拍| 大香蕉75| 91插插网| 亚州在线中文字幕经典a| 亚洲免费毛片| 日韩A电影| 国产欧美一区在线看| 91最新视频| 十八女人高潮A片免费| 久久国产劲爆∧v内射| 欧美日韩北条麻妃视频在线观看 | 精品77777| 精品一区二区三区av| 日本精品视频在线| 操逼地址| 久久无码人妻精品一区二区三区| 色婷婷欧美在线播放内射| 亚洲国产精品尤物yw在线观看| 欧美大屌网站| 亚欧精品久久久| 91热爆在线| 97精品欧美91久久久久久久| 成人视频18+在线观看| 日韩中文字幕网| 国产三级在线免费观看| 欧美V| 国产成人免费观看| 国产操比视频| 青青草在线播放| 看免费黄色视频| 俺也色俺也干| 视频一区二区免费| 51午夜福利| 亚洲永久天堂| 91综合视频在线播放| 色吟AV| 少妇搡BBBB搡BBB搡视频一级 | 亚洲免费在线婷婷| 在线免费观看成人网站| 国产三级AV在线| 久久综合九九| 国产欧美日韩综合精品| 黄色视频网站在线看| 日本中文字幕在线视频| 亚洲无码久久飞鱼网站| 免费无码婬片aaaa| 九一久久| 日本一级特黄电影| 日本AⅤ| 在线免费观看中文字幕| 欧美一级在线观看| 狼友免费视频| 京东一热本色道久久爱| 69AV在线| 简单av网| 日本三级韩三级99久久| 国产天堂| av免费观看网址| 色秘乱码一区二区三区唱戏| 操老女人视频| 日本99视频| 色综合天天综合网国产成人网 | 欧美伊人在线| h无码| 亚洲激情综合视频| 91无码影院| 中文字幕2018第一页| 久操视频一区二区三区| 日韩不卡一区二区三区| 七十路の高齡熟妇无码| 婷婷激情丁香五月天| 九九九欧美| 五月天激情电影| 在线天堂9| 亚洲精品一二三| 欧美色图第一页| 色婷婷在线视频播放| 午夜褔利| 亚洲无码人妻| 一级A片黄色| 亚洲性爱无码| 丝袜东京热AV高清| 亚洲免费观看高清完整版| 韩日一级片| 大荫蒂精品另类| 婷婷色色网| 亚洲国产熟妇综合色专区| 欧美插菊花综合网| 无码123| 色哟哟视频在线观看| 午夜爽爽爽| 久久国产V一级毛多内射| 高清无码一级片| 国产在线观看免费| 国产熟妇码AV| 麻豆传媒免费观看| 日本不卡一区二区三区| 九九伊人大香蕉| 韩国精品一区二区三区| 成人片天天看片欧美一级| 99热日本| 亚洲中文无码视频| 99精品在线播放| 中文字幕在线有码| 欧洲精品在线视频| 亚洲春色一区二区三区| 日国无码| 日韩欧美国产精品综合嫩V| 91视频亚洲| 欧美亚洲视频在线观看| 欧美v在线| 久久久久久免费一级A片| 小泬BBBBBB免费看| 老司机精品在线观看| 精品人妻午夜| 久久V| 国产成人精品一区二区三区视频| 中文字幕第五页| 东北老女人性爱视频| 不卡无码在线观看| 手机在线一区| 日韩a级片| 99在线免费视频| 国语操逼| 91精品人妻一区二区三区蜜桃| 国产又爽又黄网站免费观看| 思思99热| 唐嫣一级婬片A片AAA| 日韩欧美毛片| 在线中出| 91九色91蝌蚪91窝成人| 欧美黄色a片| 黄片免费视频观看| 欧美精品18videosex性欧美| 狠狠干五月天| 成人视频91| 久久久久久久大香蕉| 韩国日本美国免费毛片| 一本色道久久综合无码人妻软件 | aaa久久| 五月天婷婷AV| 能看的AV网站| 国产剧情自拍| 99福利视频| 婷婷激情五月天丁香| 日韩精品免费观看| 欧美成人一区二区三区片| 日韩亚洲视频| 欧美性猛交XXXX乱大交| 日韩小视频在线| 少妇在线视频| 免费无码又爽又黄又刺激网站| 色色天堂成人电影| 黄色一级视频网站| 美国无码黄片| 青青草手机在线观看| 国产性爱电影网| 天天操夜夜操人人操| 水多多成人网站A片| 国产91在线观看| 1024手机在线观看| 操bbbb| 久久艹骚逼| 国产一二三| 欧美爱爱网站| 成人免费毛片AAAAAA片| 另类视频在线| 无码天堂| 午夜69成人做爱视频网站| 久草视频99| 一区二区三区免费在线| 男女性爱视频免费| 日韩午夜AV| 亚洲天堂女人| 日韩爱爱视频| 天天草视频| 亚洲日韩精品中文字幕| 日韩操屄视频| 亚洲色逼图片| 视频二区中文字幕| 国产wwwww| 青青草免费公开视频| 亚洲欧美另类色图| 国产色av| 久草视频这里只有精品| 伊人黄色| 操逼操123| 奇米影视av| 国产成人亚洲综合A∨婷婷| 台湾无码精品| 中文字幕无码在线播放| 亚洲无码AV在线观看| 久草黄色电影在线观看| 日韩精品一区二区三区中文在线| 国产成人三级在线| 国产毛片基地| 国产激情无码免费| 色男人的天堂| 久久久久久久97| 激情中文网| 九色国产| 黄色电影av| 麻豆一区| AV天天看| 亚洲成人A| 在线免费看av| 蜜桃成人无码区免费视频网站 | 波多野结衣视频在线播放| 成人高清无码在线观看| 亚洲日韩一区二区三区四区| 国产AV一区二区三区精品| 四虎影成人精品A片| 黄色激情网站| 亚洲a∨| 亚洲无码在线资源| 一本道在线无码| 草草影院第一页YYCCC| 无码人妻一区二区三区免费九色 | 性爱视频亚洲| 九九九成人| 91三级片网站| 女人卖婬视频播放| aaa免费视频| 黄色AV电影| 啪啪啪啪网站| 亚欧三级| 日韩欧美大香蕉| 国产XXXX| 国产精品久久久久久久久久久久久| 欧美性爱日韩| 国产又爽又黄免费网站校园里| 色老板在线观看视频| 亚洲三级网站在线观看| 日韩高清毛片| 人妻在线免费视频| 亚洲va在线∨a天堂va欧美va | 久久久夜夜夜| 中文字幕无码一区二区三区一本久| 久久久精品人妻| 国产视频一区二区在线观看| 国产a毛一级,a毛一级| 欧美一级日韩一级| 一区二区三区久久久久〖网:.〗| 国内精品人妻无码久久久影院蜜桃 | 又a又黄高清无码视频| 亚洲欧美日韩激情| 亚洲最新在线视频| 一区二区三区亚洲| 人妻黄色视频| 日韩熟妇人妻中文字幕| 国产天堂| 俺去久久| 色香蕉网| 久久久www| 国产AV一区二区三区四区| 7799精品视频天天看| 日本一级黄色电影网| 亚洲黄片免费观看| 国产最新地址| 日本A∨在线| 91性爱视频| 日本性爱无码| 蜜臀久久99精品久久久| 狠狠干,狠狠操| 天天插天天插| 一区二区无码免费| 东北老女人性爱视频| 褒姒AV无玛| 北条麻妃无码| 免费久草视频| 国产资源网| 超碰精品| caopor在线| 日韩逼逼| 国产精品AV网站| 九九成人网站| 九九视频免费观看| 91在线无码精品秘入口电车 | 3344在线观看免费下载视频 | 日本久热| 国产性受XXXXXYX性爽| 欧美视频一区二区三区| 国产成人无码在线| 激情爱爱网站| 中文原创麻豆传媒md0052| 韩国无码精品| 2025av天堂| 99久久伊人| 夜色福利在线看| 99免费视频在线观看| 九色国产视频| 欧美噜噜| 中文字幕亚洲观看| 日无码| 色婷婷AV一区二区三区软件| 亚州无码免费| 国产精品色情A级毛片| 91视频在线| 精品少妇人妻| 欧美V∧| 亚洲一级a片| 欧美亚洲系列| 77777免费观看电视剧推荐爱的教育| 激情五月在线| 日韩啊啊啊| 成人无码99| 日韩一级黄片| 在线免费观看黄| 澳门四虎影院| 大香蕉青娱乐| 久久久久久99| 成人网站在线观看免费| 99综合在线| 自拍偷拍av| 国产亚洲99久久精品熟女| 国产久久视频在线观看| 波多野结衣无码视频| 日本翔田千里奶水| 亚洲AV无码成人精品区在线欢看 | 久操婷婷| 大香蕉伊人视频在线观看| 亚洲精品一区中文字幕乱码| 黄色片在线播放| 精品视频日韩| 国产福利美女网站| 青青草资源站| 欧美亚洲视频| 毛片视频免费观看| 日韩aaaaaa| 婷婷激情视频| 国产秘精品区二区三区日本| 成年人久久| 日韩精品一区二区三区四区| 国产成人在线播放| 91绿帽人妻-ThePorn| 日韩亚洲欧美在线观看| 狠狠色五月亚洲91| 日爽夜爽| 国产精品色哟哟| 人妻无码HEYZO少妇精品| 黄色日逼视频| 手机看片久久| 免费在线看黄网站| 激情五月在线| 一本色道久久综合无码人妻软件| 2025无码视频| 亚洲成人在线视频免费观看| 免费欧美三级片| 久久任你操| 人妻无码一区二区| 黄色动漫在线免费观看| 国产精品嫩草久久久久yw193| 色av影音先锋无吗一区| 操逼视频网| 熟女一区二区| 日韩中文在线视频| 女生操逼网站| 97精品人妻一区二区三区香蕉| 狠狠干2022| 成人久久AV| 成人播放视频| 色猫AV| 国产成人精品无码免费| 加勒比精品在线| 五月婷婷六月丁香综合| 国产无遮挡又黄又爽又色视频| 99久久亚洲精品日本无码| 中文无码字幕视频| 三级片青青草| 麻豆三级电影| 久久丝袜| 国产精品永久| 亚洲专区在线| 亚洲黄色视频网站在线观看| 国产成人欧美| 噜噜色小说| 韩国无码成人电影啊荒| 亚洲国产视频在线观看| 亚色网址| 成人无码视频| 婷婷五月中文字幕| 猫咪AV成人永久网站| 特级西西人体444.444人体聚色| 国产无遮挡又黄又爽| 国产乱子伦-区二区三区熟睡91| 黄色福利视频在线观看| 无码秘蜜桃一区二区三区| 中文字幕无吗| 免费一级欧美片在线观看| 亚洲成人一区二区在线观看| 日韩AAA| 婷婷久久五月| 国产suv精品一区二区| 亚洲国产成人综合| 免费操逼视频在线观看| 91探花秘在线播放偷拍| 日本精品黄色| 精品日逼| 国内精品久久久久久久久久变脸| 日韩欧美高清无码| 欧美成综合| 麻豆传媒一区| 97精品人妻麻豆一区二区| 囯产精品久久久久久久久久久久久久| 中国熟女网站| 精品第一页| 国产精品电影| 69av在线播放| 少妇特黄A一区二区三区| 国产网站在线| 亚洲男人天堂av| 日逼网站免费观看| 亚洲综合色色| 91色在线| 久久免费视频播放| 91九色首页| 精品无码久久久| 欧美一卡| 97超级碰| 肏网站| 少妇bbb搡bbbb搡bbbb| 欧美亚洲日韩一区二区| 国产午夜精品一区二区三区牛牛 | 黄色视频| 日韩中文字幕在线视频| 欧美性爱在线观看| 91人妻成人精品一区二区| 久久久久久网| 久久黄色视频网站| 91成人电影在线观看| 米奇色色| 大肉大捧一进一出两腿| 麻豆精品久久久久久久99蜜桃| 国产精品TV| 精品无码秘人妻一区二区三区| 亚洲精品中文字幕无码| 中文字幕日韩人妻在线| 国产1页| 欧美国产激情| 中文av在线播放| 亚洲免费网| 日韩人妻精品无码制服| 亚洲天堂2017| 一级A片一毛片大全| 91综合视频| 婷婷色婷婷| 婷婷综合视频| 韩国av在线| 爱爱网址| av一区二区三区| 亚洲日韩中文字幕| 欧美亚洲综合手机在线| www.zaixianshipin| 日韩AAA在线| 999久久精品| 成人精品网| TokyoKot大交乱无码| 69av视频在线观看| 四色五月婷婷| 四川性BBB搡BBB爽爽爽小说| 欧美一区二区精品| 久艹av| 日韩免费高清无码视频| 操逼视频一级| 永久久久久久久| 亚洲欧美视频一区| 亚洲欧美在线免费观看| 日韩大片在线观看| 欧美XXX黑人XYX性爽| 色综合久久天天综合网| 精品无码电影| 日本精品二区| 91站街农村熟女露脸| 中文字幕无码AV| 黄片网站免费观看| 底流量AV电影在线| 搞搞网日本9| 久久综合成人| 西西人体BBBBBB| 日日摸日日添日日躁AV| 青青草网站在线观看| 日本午夜无码| 日日躁夜夜躁| 九九九精品在线| 亚洲欧洲免费| 免费成人黄色网址| 青娱乐99| 日韩高清在线| 国产视频一区二区三区四区| www.操操网| 亚洲午夜精品久久久久久APP| 俺也去色色| 9一区二区三区| 天堂资源站| 人人超碰人人| 嫰BBB槡BBBB槡BBBB| 欧美精品人妻| 无码高清免费| 婷婷色在线| 无码高清在线| 日本久久高清| 婷婷五月免费视频| 青草久久久久| 国产精品XXX视频| 人妻制服丝袜| 内射视频免费观看| 中文字幕在线观看免费高清电影 | 亚洲骚逼| 亚洲AV免费在线观看| 99热| 无码人妻精品一区二区50| 伊人免费视频在线观看| 久久大香蕉91| AV在线免费网站| 91热热| 久久无码高清视频| 免费A片在线观看| 91福利网| 香蕉一级视频| 欧亚一区二区| 91操b| 亚洲精品乱码久久久久久| 国产人妻精品| 欧美日韩小视频| 日韩一级视频| 三级片网站国产| 天天拍天天日| AV无码一区二区三区| 精品少妇一区| 天天干干| www.骚逼| 免费成人AV| 中文字幕成人网站中文字幕| 屁屁影院CCYYCOM国产| 欧美日韩不卡视频| 无码免费视频观看| 蜜桃传媒一区二区亚洲AV| 日韩激情视频| 国产g蝌蚪| 中文字幕丰满的翔田千里| 老太婆擦BBBB撩BBBB| 久久五月丁香| 婷婷视频在线| 亚洲无码自拍| 欧美熟妇性爱视频| 国精品无码人妻一区二区三区 | 五月婷婷六月天| 中文字幕伊人| 中文字幕日本欧美| 亚洲无码色婷婷| 国产白丝视频| 亚洲精品999| 日韩人妻无码一区二区三区99 | 2024av在线| 午夜久久视频| 黄色av免费观看| 亚洲女人被黑人巨大进入| 男女日皮视频| 伊人婷婷久久| 成人精品一区二区三区无码视频 | 一级片三级片| 亚欧视频在线观看| 波多野结衣无码一区二区| 日本无码专区| 黄片网址| 中文字幕成人网站中文字幕| 最近日本中文字幕中文翻译歌词 | 中国老少配BBwBBwBBW| 99草在线视频| 黑巨茎大战欧美白妞小说| www.高清无码| 黃色A片一級二級三級免費久久久 亚洲AV无码第一区二区三区蜜桃 亚洲综合免费观看高清完整版在线 | 亚洲成人69| 亚洲Aⅴ| 亚洲精品一二三| 影音AV| 久久午夜无码鲁丝片午夜精品| 亚洲精品系列| 91成人小视频| 亚洲欧美v| 刘玥91精品一区二区三区| AV无码国产| 五月天婷婷综合网| 青青草原无码| 久久亚洲成人| www.天天射视频| 人妻熟女在线视频| 熟女AV888| 亚洲夜夜撸| 国产香蕉视屏| 色噜噜狠狠一区二区三区300部| 抽插免费视频| 天天日天天射天天干| 99视频在线| 国产精品毛片VA一区二区三区 | 精品中文字幕在线播放| 久久另类TS人妖一区二区免费| 亚洲日韩欧美一区二区天天天| www.偷拍| 亚洲AV第一页| 国产精品无码无套在线照片| 久久久一| www.麻豆网91成人久久久| 免费的一级A片| 中文字幕巨肉乱码中文乱码| 精品视频免费| 伊人毛片| 影音先锋麻豆传媒| 内射无码视频| a在线免费观看| 国产91探花系列在线观看| 好男人一区二区三区在线观看 | 五月婷网| 成人午夜在线观看| 亚洲精品大片| 加勒比在线视频| 自拍欧美亚洲| 壁特壁视频在线观看| 日韩A| 蜜芽成人在线视频| 午夜69成人做爱视频网站| 国产精品一区二区三| 在线草| 羽月希奶水饱胀在线播放| 中文字幕黄色电影| 一见钟情的韩国电影| 自慰一区二区| 黄色一级免费看| 狼友视频在线| 伊人黄色网| 日韩黄色在线视频| 影音先锋成人资源站| 色射影院| 3D动漫精品一区二区在线播放免费 | 日本乱轮视频| 激情五月天在线观看| 亚洲电影在线观看| 国产亚洲综合无码| anwuye官方网站| 日皮免费视频| 茄子av| 草草影院国产第一页| 午夜激情视频| 国产一区免费观看| 熟妇槡BBBB槡BBBB图| 国产无码片| 日本a片免费| 欧美AAA在线观看| av中文在线观看| 免费成人国产| 人人妻人人超| 泄火熟妇2-ThePorn| 成人无码自拍| 嫩苞又嫩又紧AV无码| 欧美亚洲视频| av在线小说| 老湿机福利视频| 日本精品视频| 国产精品小电影| 亚洲夜夜操| 成人小视频十八禁免费观看| 国产精品久久久久久久9999| 欧美97| 在线观看AV无码| 日本牲交| 911亚洲精品| 日韩无码一卡| 亚洲视频高清无码| 大香蕉97| 狠狠操婷婷| 色九| 三级无码av| 久久婷婷成人综合色怡春院| 一级免费黄片| 伊人影院在线免费观看| 国产无遮挡又黄又爽| 色墦五月丁香| 91理论片| 小黄片免费|