Vue八個生命周期
beforeCreate【創建前】????created【創建后】
beforeMount【載入前】? ? mounted【載入后】
beforeUpdate【更新前】? ? updated【更新后】
beforeDestroy【銷毀前】? ? destroyed【銷毀后】
vue生命周期的作用:
它的生命周期有多個事件鉤子,讓我們控制Vue實例的過程時形成更好的邏輯
Vue、React、Angular之間的區別【題目根據此文章內容整理】
MVX框架模式:MVC+MVP+MVVM
1、MVC:Model【模型】+View【視圖】+Controller【控制器】,只要基于分層的目的,讓彼此的指責分開
View通過Controller和Model聯系,Controller是View和Model的協調者,View和Model不直接聯系,基本聯系都是單向的;
用戶通過控制器Controller來操作模版Model從而達到視圖View的變化。
2、MVP是從MVC模式演變出來的,都是通過Controller/Presenter負責邏輯的處理+Model提供數據+View負責顯示
在MVP中,Presenter完全把View和Model進行了分離,主要的程序邏輯在Presenter中實現。
3、MVVM
MVVM是把MVC里面的Controller和MVP里的Presenter改成了ViewModel。Model+View+ViewModel
View變化會自動更新到ViewModel,ViewModel的變化也會自動同步到View顯示。
這種自動同步是因為ViewModel的屬性實現了Observer,當屬性變更時都能出發對應的操作。
Vue實現數據雙向綁定的原理
采用數據劫持結合發布者+訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter、getter,在數據變動時發布消息給訂閱者,觸發相應監聽回調。當把一個普通JS對象傳給Vue實例,來作為它的data選項時,Vue將遍歷它的屬性,用Object.defineProperty()將他們轉化為getter/setter。用戶看不到getter/setter,但是在內部他們讓Vue追蹤依賴,在屬性被訪問和修改時通知變化。
vue的數據雙向綁定,將MVVM作為數據綁定的入口,整合Observer、Compile和Watcher三者,通過Observer來監聽自己的model數據變化,通過Compile來解析編譯模版指令(Vue是用來解析{{}}),最終利用Watcher搭起Observer和Compile之間的通信橋梁,達到數據變化=>視圖更新;視圖監護變化(input)=>數據model變更雙向綁定效果。
Vue組件間的參數傳遞
1、父組件與子組件傳值
父組件=>子組件:子組件通過props方法接受數據;
子組件=>父組件:$emit方法傳遞參數;
2、兄弟組件傳值
eventBus,就是創建一個事件中心,相當于中轉站,可以用它來傳遞事件和接受事件。【項目比較小時用這個比較合適,Vuex適用于中大型項目】
Vue虛擬DOM傳送門
瀏覽器渲染引擎工作流程:
創建DOM樹=>創建StyleRules=>創建Render樹=>布局Layout=>繪制Painting
1、用html分析器,分析html元素,構建一顆DOM樹;
2、用CSS分析器,分析css文件和元素上的inline樣式,生成頁面的樣式表;
3、將DOM樹和樣式表關聯起來,構建一顆Render樹。每個DOM節點都有attach方法,接受樣式信息,返回一個render對象,這些render對象最終會被構建成一顆Render樹;
4、有了Render樹,瀏覽器開始布局,為每個Render的節點確定一個在顯示屏上出現的精確坐標;
5、Render樹和顯示坐標節點都有了,就調用每個節點paint方法。把他們繪制出來。
JS操作真實DOM的代價:
用我們傳統模式開發,原生js或者jq操作dom時,瀏覽器會從構建dom樹開始從頭到尾執行一次,在第一次操作中,我們需要更新10個DOM節點,瀏覽器收到第一個DOM請求后會立即執行,并不知道之后還有九次,最終執行10次,因此js直接操作真實DOM節點會付出很大的代價。
為什么需要虛擬DOM:
web界面由DOM樹來構建,當其中一部分發生變化時,其實就是對應某個DOM節點發生了變化。
虛擬DOM就是為了解決瀏覽器性能問題而被設計出來的;
創建DOM樹=>創建StyleRules=>創建Render樹【10次DOM操作=>虛擬DOM=>將十次更新的diff內容存儲到本地的js對象上=>將這個js對象一次性attch到DOM樹上】=>布局Layout=>繪制Painting
Vue異步更新隊列
運行后會拋出錯誤:Cannot read property 'innnerHTML of null,意思就是獲取不到div元素。這里就涉及到一個vue重要的概念:異步更新隊列。
Vue會根據當前瀏覽器環境優先使用原生的Promise.then和MutationObserver,如果都不支持,就會采用setTimeout代替。【Vue異步更新DOM】
Vue在觀察數據變化時,并不是直接更新DOM,而是開啟一個隊列,并緩沖同一個事件循環中發生的所有數據變化。在緩沖過程中會去除所有重復數據,從而避免不必要的計算和DOM操作。
vue-router鏈接
vue-router是vue官方的路由管理器。他和vue的核心深度集成讓構建單頁面應用變得易如反掌。
使用vue,我們通過組合組件來組成應用程序,當把vue-router添加進來,需要將組件映射到路由,然后告訴vue-router哪里渲染他們。
通過注入路由器,我們可以在任何組件內通過this.$router訪問路由器。
this.$router和router使用起來完全不一樣,我們使用this.$router的原因是我們并不想在每個獨立需要封裝路由的組件中都導入路由。
router:
//使用router-link組件來導航,通過to屬性指定鏈接
<router-link to="/foo"></router-link>? ? //默認渲染為a標簽
//路由出口? ????路由匹配到的組件將在這里渲染
<router-view></router-view>??
1、定義組件
var Foo = {template: '<div>foo</div>'};
2、定義路由
每個路由應該映射一個組件
通過vue.extend()創建組件構造器
const routes = [{"foo", component: Foo}]
3、創建router實例,然后傳routes配置
const router = new VueRouter({
? ? routes
})
4、創建和掛載根實例
通過router配置參數注入路由,從而讓整個應用都有路由功能
const app = new Vue({
? ? router
}).$mount("#app")
動態路徑參數
const Foo = {template: '<div></div>'};
const router = new VueRouter({
? ? routes: [
? ? ? ? {path: '/user/:id', component: Foo}
????]
})
一個路徑參數使用冒號:標記。當匹配到一個路由時,參數值會被設置到this.$router.params
響應路由參數的變化
當使用路由參數時,原來的組件實例會被復用,不過這也意味著組件的生命周期鉤子不會再被調用。
復用組件時,相對路由參數的變化作出響應的話,可以watch $route對象:
const Foo = {
? ? template: '...',
? ? watch:{
? ? ? ? '$route'(to, from){
? ? ? ? ? ? 對路由變化作出相應
????????}
????}
}
或者使用2.2中引入的beforeRouteUpdate導航守衛:
const Foo = {
? ? template: '...',
? ? beforeRouteUpdate(to,from,next){
????}
}
捕獲所有路由以及404路由
常規參數只會匹配被/分隔url片段中的字符。如果想匹配任意字符,我們可以使用通配符*。
當使用通配符路由時,請確保路由的順序是正確的,也就是含有通配符的路由應該放到最后。路由{path:'*'}通常用于客戶端404錯誤,。如果你使用了history模式,請確保正確配置你的服務器。
當使用通配符時,$route.params會自動添加一個名為pathMatch的參數。它包含了通配符匹配的部分。
高級匹配模式
匹配優先級【誰先定義的,誰的優先級就最高】
嵌套路由
<div id="app"><router-link></router-link></div>
const User = {
? ??template: '<div>{{$router.params.id}}<router-view></vouter-view/></div>'
}
const router = new VueRouter({
? ? routes:[
? ? ? ? {path: '/user/:id', component: User,
? ? ? ? children:[{
? ? ? ? ? ? 當路由匹配到‘/user/:id/post’時
? ? ? ? ? ? path:''post",
? ? ? ? ? ? component:post
????????}]
? ? ? ? }
????]
})
要注意,以/開頭的嵌套路由會被當做根路徑。這讓你充分的使用全套組件而無需設置嵌套的路徑。
編程式導航
在vue實例內部,你可以通過$router訪問到路由實例。因此你可以調用this.$router.push。
想要導航到不同的url,則可以使用router.push方法。這個方法會向history棧添加一個新的記錄,所以當用戶點擊瀏覽器后退按鈕時,則回到之前的url。
點擊<router-link to=""></router-link>相當于調用router.psuh(...)
同樣的規則也適用于router-link組件的to屬性。
在2.2.0+,可選的在router.push或router.replace中提供onComplete和onAbort回調作為第二個和第三個參數。這些回調將會在導航完成(所有的異步鉤子被解析之后)或終止(導航到相同的路由、或在當前導航完成以前導航到另一個不同的路由)的時候進行響應的調用。
router.replace(location,onComplete,onAbout)
跟router.push很像,唯一的不同就是他不會向history添加新記錄,跟他的方法命一樣,替換掉當前的history記錄。
router.go()
操作history
router.push()/router.replace()/router.go()與window.history.pushState()/window.history.replaceState()/window.history.go相似,實際上他們確實消防了window.historyAPI.
命名路由
通過一個名稱來標示路由,連接一個路由或執行跳轉的時候。可以在創建Router實例的時候,在routes配置中給某個路由設置名稱。
const router = new VueRouter({
? ? routes:[{
? ? ? ? path: '/user/:id',
? ? ? ? name: 'user',
? ? ? ? component: User
? ? }]
})
要鏈接一個命名路由,可以給router-link的to屬性傳一個對象:
<router-link :to="{name:'user',params:{userId123}}"></router-link>
這跟代碼調用router.push()一樣
router.push({name:"user",params:{userId:123}})? ? //? ? ?'/user/123'
命名視圖
同時【同級】展示多個視圖,而不是嵌套展示。例如創建一個布局,有sidebar(側導航)和main(主內容)兩個視圖,這個時候命名視圖就派上用場了。可以在界面中擁有多個單獨命名的視圖,而不是只有一個單獨的出口。如果router-view沒有設置名字,那么默認default。
<router-view name="a"></router-view>
<router-view name="b"></router-view>
<router-view></router-view>
一個視圖使用一個組件渲染,因此對于同個路由多個視圖就需要多個路由。
const router = new VueRouter({
? ? routes: [{
? ? ? ? path: '/user',
? ? ? ? components: {
? ? ? ? ? ? default: Foo,
? ? ? ? ? ? a:Bar,
? ? ? ? ? ? b: Baz
????????}
????}]
})
嵌套命名路由
const router? = new VueRouter({
? ? routes:[{
? ? ? ? path: '/',
? ? ? ? component: Foo,
? ? ? ? children: [{
? ? ? ? ? ? path: '/',
? ? ? ? ? ? component: User
????????},{
? ? ? ? ? ? path: '/',
? ? ? ? ? ? components:{
? ? ? ? ? ? ? ? a: A,
? ? ? ? ? ? ? ? b: B,
? ? ? ? ? ? ? ? default: C
????????????}
????????}]
????}]
})
重定向
重定向也是通過routes配置完成的:
const router = new VueRouter({
? ? routes: [
? ? ? ? {path: '/a',redirect: '/b'}
????]
})
重定向的目標也可以是一個命名的路由:
const router = new VueRouter({
? ? routes: [
? ? ? ? {path: '/a',redirect: {name: 'foo'}}
????]
})
或者一個動態方法,動態返回重定向目標:
const router = new VueRouter({
? ? routes: [
? ? ? ? {path: '/a', redirect: to=> {
? ? ? ? ? ? 方法接收目標路由作為參數
? ? ? ? ? ? return 重定向的 字符串路徑路徑對象
????????}}
????]
})
路由組件傳參
const User = {
? ? props: ['id'],
? ? template: '<div>{id}</div>'
}
const router = new VueRouter({
? ? routes: [
? ? ? ? {path: '/user/:id', component: User, props: true}
????]
})
hash模式與history模式區別:【vue-router核心:改變視圖的同時不會想后端發送請求】【單頁面應用】
聲明式導航=>hash模式、編程式導航=>history模式
1、hash-即地址欄url中的#符號
特點:hash雖然出現在url中,但不會被包括在http請求中對后端完全沒有影響,因此改變hash不會重新加載頁面。
2、history-利用了h5 history interface 中新增的window.history.pushState()以及window.history.replaceState()方法。
因此hash模式以及history模式都是屬于瀏覽器的特性,vue-router只是利用了這兩種特性來實現前端路由。
優缺點:
1??history模式設置的新的url可以是與當前url同源的任意url,而hash模式只能修改#后面的部分;
2??history模式可以設置與當前url相同,也可以把記錄添加到棧,而hish模式設置的url必需與原來url不一樣,才能把記錄添加到棧;
3??history模式通過stateObject參數可以添加任意類型的數據到記錄中,而hash模式只可以添加短字符;
4??history模式可以額外設置title屬性提供后續使用;
5??hash模式,只有hash之前的url被包含在請求中,所以對后端來說,即使沒有做到對路由的全覆蓋,也不會出現404的情況;history模式下,前端的url必需和后端的url一致。如:'http:www.baidu.com/book/id'如果后端缺少對'/book/id'路由的處理,則會出現404情況。
導航守衛
全局前置守衛:router.beforeEach
const router = new VueRouter({})
router.beforeEach((to, form, next)=>{
? ? //to即將進入的路由目標對象
? ? //from當前導航正要離開的路由
? ? //next【function】一定要調用該方法來resolve這個鉤子。執行效果依賴next方法的調用參數。
})
全局后置守衛:router.afterEach((to, from) => {})
路由獨享的守衛:beforeEnter
組件內的守衛:beforeRouterEnter/beforeRouterUpdate/beforeRouterLeave
完整的導航解析流程:
1、導航被觸發;
2、在失活的組件里調用離開守衛;
3、調用全局的beforeEach守衛;
4、在重用的組件里調用beforeRouterUpdate守衛;
5、在路由配置里靠用befroeEnter;
6、解析異步路由組件;
7、再被激活的組件里調用beforeRouterEnter;
8、調用全局的beforResolve守衛;
9、導航被確認;
10、觸發dom刷新;
11、用創建好的實例調用beforeRouterEnter傳給next回調函數;
路由的懶加載
當打包構建應用時,js包會變得非常大,影響頁面加載。如果我們能把不同路由對應的組件分割成不同的代碼塊,然后當路由被訪問的時候才加載對應組件,這樣就更加高效了。
結合vue的異步組件和webpack的代碼分割功能, 輕松實現路由組件的懶加載。
1、將異步組件定義為返回一個Promise的工廠函數
const Foo = () => Promise.resolve({組件定義對象})
2、在webpack2中,我們可以使用動態import語法來定義代碼分塊點
import('./Foo.vue')