一.對於mvvm的理解 1.MVVM 是 Model-View-ViewModel 的縮寫 2.Model表明數據模型,也能夠在Model中定義數據修改和操做的業務邏輯 3.View表明UI 組件,它負責將數據模型轉化成UI 展示出來 4.ViewModel監聽模型數據的改變和控制視圖行爲、處理用戶交互,簡單理解就是一個同步View 和 Model的對象,鏈接Model和View。javascript
二.vue的生命週期(11個,重點8個) 1.beforeCreate(建立前) 在數據觀測和初始化事件還未開始css
2.created(建立後) 完成數據觀測,屬性和方法的運算,初始化事件,$el屬性尚未顯示出來html
3.beforeMount(載入前) 在掛載開始以前被調用,相關的render函數首次被調用。實例已完成如下的配置:編譯模板,把data裏面的數據和模板生成html。注意此時尚未掛載html到頁面上前端
4.mounted(載入後) 在el 被新建立的 vm.$el 替換,並掛載到實例上去以後調用。實例已完成如下的配置:用上面編譯好的html內容替換el屬性指向的DOM對象。完成模板中的html渲染到html頁面中。此過程當中進行ajax交互。vue
5.beforeUpdate(更新前) 在數據更新以前調用,發生在虛擬DOM從新渲染和打補丁以前。能夠在該鉤子中進一步地更改狀態,不會觸發附加的重渲染過程。html5
6.updated(更新後) 在因爲數據更改致使的虛擬DOM從新渲染和打補丁以後調用。調用時,組件DOM已經更新,因此能夠執行依賴於DOM的操做。然而在大多數狀況下,應該避免在此期間更改狀態,由於這可能會致使更新無限循環。該鉤子在服務器端渲染期間不被調用。java
7.beforeDestroy(銷燬前) 在實例銷燬以前調用。實例仍然徹底可用。node
8.destroyed(銷燬後) 在實例銷燬以後調用。調用後,全部的事件監聽器會被移除,全部的子實例也會被銷燬。該鉤子在服務器端渲染期間不被調用。python
三.Vue實現數據雙向綁定的原理:Object.defineProperty()react
Vue實現數據雙向綁定的三大對象Observer(Object.defineProperty中的getter,每當數據發生變化,就會觸發setter,這時候Observer就要通知訂閱者,訂閱者就是Watcher)、Watcher(Watcher訂閱者做爲Observer和Compile之間通訊的橋樑)、Compile
Watcher: 1.在自身實例化時往屬性訂閱器(dep)裏面添加本身 2.自身必須有一個update()方法 3.待屬性變更dep.notice()通知時,能調用自身的update()方法,並觸發Compile中綁定的回調
Compile主要作的事情是解析模板指令,將模板中的變量替換成數據,而後初始化渲染頁面視圖,並將每一個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變更,收到通知,更新視圖。
history模式和hash模式 頁面404的狀況,咱們前端須要本身處理 routes: [ { path: '*', component: NotFoundComponent } ]
何如訪問vue實例 beforeRouteEnter(to, from, next) { next(vm => { console.log('vm', vm) }) }
activated:keep-alive 組件激活時調用,該鉤子在服務器端渲染期間不被調用
deactivated:keep-alive 組件停用時調用,該鉤子在服務器端渲染期間不被調用
beforeCreate 實例建立前 created 實例建立完成 beforeMount 掛載前 mounted 掛載完成 beforeUpdate 更新前 updated 更新完成 beforeDestory 銷燬前 destoryed 銷燬完成
1.beforeCreate 這個鉤子是new Vue()以後觸發的第一個鉤子,在當前階段中data、methods、computed以及watch上的數據和方法均不能被訪問。
2.created 這個鉤子在實例建立完成後發生,當前階段已經完成了數據觀測,也就是能夠使用數據,更改數據,在這裏更改數據不會觸發updated函數。能夠作一些初始數據的獲取,注意請求數據不易過多,會形成白屏時間過長。在當前階段沒法與Dom進行交互,若是你非要想,能夠經過vm.$nextTick來訪問Dom。
3.beforeMounted 這個鉤子發生在掛載以前,在這以前template模板已導入渲染函數編譯。而當前階段虛擬Dom已經建立完成,即將開始渲染。在此時也能夠對數據進行更改,不會觸發updated。
4.mounted 這個鉤子在掛載完成後發生,在當前階段,真實的Dom掛載完畢,數據完成雙向綁定,能夠訪問到Dom節點,使用$ref屬性對Dom進行操做。也能夠向後臺發送請求,拿到返回數據。
5.beforeUpdate 這個鉤子發生在更新以前,也就是響應式數據發生更新,虛擬dom從新渲染以前被觸發,你能夠在當前階段進行更改數據,不會形成重渲染。
6.updated 這個鉤子發生在更新完成以後,當前階段組件Dom已完成更新。要注意的是避免在此期間更改數據,由於這可能會致使無限循環的更新。
7.beforeDestroy 這個鉤子發生在實例銷燬以前,在當前階段實例徹底能夠被使用,咱們能夠在這時進行善後收尾工做,好比清除計時器。
8.destroyed 這個鉤子發生在實例銷燬以後,這個時候只剩下了dom空殼。組件已被拆解,數據綁定被卸除,監聽被移出,子實例也通通被銷燬。
最近利用空閒時間又翻看了一遍Vue的源碼,只不過此次不一樣的是看了Flow版本的源碼。說來慚愧,最先看的第一遍時對Flow不瞭解,所以閱讀的是打包以後的vue文件,你們能夠想象這過程的痛苦,沒有類型的支持,看代碼時摸索了很長時間,因此咱們此次對Vue源碼的剖析是Flow版本的源碼,也就是從Github上下載下來的源碼中src目錄下的代碼。不過,在分析以前,我想先說說閱讀Vue源碼所須要的一些知識點,掌握這些知識點以後,相信再閱讀源碼會較爲輕鬆。
我我的認爲要想深刻理解Vue的源碼,至少須要如下知識點:
下面我們一一介紹
相信你們都知道,javascript是弱類型的語言,在寫代碼灰常爽的同時也十分容易犯錯誤,因此Facebook搞了這麼一個類型檢查工具,能夠加入類型的限制,提升代碼質量,舉個例子:
function sum(a, b) {
return a + b;
}
複製代碼
但是這樣,咱們若是這麼調用這個函數sum('a', 1) 甚至sum(1, [1,2,3])這麼調用,執行時會獲得一些你想不到的結果,這樣編程未免太不穩定了。那咱們看看用了Flow以後的結果:
function sum(a: number, b:number) {
return a + b;
}
複製代碼
咱們能夠看到多了一個number的限制,標明對a和b只能傳遞數字類型的,不然的話用Flow工具檢測會報錯。其實這裏你們可能有疑問,這麼寫仍是js嗎? 瀏覽器還能認識執行嗎?固然不認識了,因此須要翻譯或者說編譯。其實如今前端技術發展太快了,各類插件層出不窮--Babel、Typescript等等,其實都是將一種更好的寫法編譯成瀏覽器認識的javascript代碼(咱們之前都是寫瀏覽器認識的javascript代碼的)。咱們繼續說Flow的事情,在Vue源碼中其實出現的Flow語法都比較好懂,好比下面這個函數的定義:
export function renderList (
val: any,
render: (
val: any,
keyOrIndex: string | number,
index?: number
) => VNode
): ?Array<VNode>{
...
}
複製代碼
val是any表明能夠傳入的類型是任何類型, keyOrIndex是string|number類型,表明要不是string類型,要不是number,不能是別的;index?:number這個咱們想一想正則表達式中?的含義---0個或者1個,這裏其實意義也是一致的,可是要注意?的位置是在冒號以前仍是冒號以後--由於這兩種可能性都有,上面代碼中問號是跟在冒號前面,表明index能夠不傳,可是傳的話必定要傳入數字類型;若是問號是在冒號後面的話,則表明這個參數必需要傳遞,可是能夠是數字類型也能夠是空。這樣是否是頓時感受嚴謹多了?同時,代碼意義更明確了。爲啥這麼說呢? 以前看打包後的vue源碼,其中看到觀察者模式實現時因爲沒有類型十分難看懂,可是看了這個Flow版本的源碼,感受容易懂。 固然,若是想學習Flow更多的細節, 能夠看看下面這個學習文檔: Flow學習資料
Vue中的組件相信你們都使用過,而且組件之中能夠有子組件,那麼這裏就涉及到父子組件了。組件其實初始化過程都是同樣的,顯然有些方法是能夠繼承的。Vue代碼中是使用原型繼承的方式實現父子組件共享初始化代碼的。因此,要看懂這裏,須要瞭解js中原型的概念;這裏很少談,只是提供幾個學習資料供你們參考: 廖雪峯js教程 js原型理解 1.3 Object.defineProperty 這個方法在js中十分強大,Vue正是使用了它實現了響應式數據功能。咱們先瞄一眼Vue中定義響應式數據的代碼:
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
.....
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
複製代碼
其中咱們看到Object.defineProperty這個函數的運用,其中第一個參數表明要設置的對象,第二個參數表明要設置的對象的鍵值,第三個參數是一個配置對象,對象裏面能夠設置參數以下: value: 對應key的值,無需多言 configurable:是否能夠刪除該key或者從新配置該key enumerable:是否能夠遍歷該key writable:是否能夠修改該key get: 獲取該key值時調用的函數 set: 設置該key值時調用的函數 咱們經過一個例子來了解一下這些屬性:
let x = {}
x['name'] = 'vue'
console.log(Object.getOwnPropertyDescriptor(x,'name'))
Object.getOwnPropertyDescriptor能夠獲取對象某個key的描述對象,打印結果以下:
{
value: "vue",
writable: true,
enumerable: true,
configurable: true
}
複製代碼
從上可知,該key對應的屬性咱們能夠改寫(writable:true),能夠從新設置或者刪除(configurable: true),同時能夠遍歷(enumerable:true)。那麼讓咱們修改一下這些屬性,好比configurable,代碼以下:
Object.defineProperty(x, 'name', {
configurable: false
})
複製代碼
執行成功以後,若是你再想刪除該屬性,好比delete x['name'],你會發現返回爲false,即沒法刪除了。 那enumerable是什麼意思呢?來個例子就明白了,代碼以下:
let x = {}
x[1] = 2
x[2] = 4
Object.defineProperty(x, 2, {
enumerable: false
})
for(let key in x){
console.log("key:" + key + "|value:" + x[key])
}
複製代碼
結果以下: key:1|value:2 爲何呢? 由於咱們把2設置爲不可遍歷了,那麼咱們的for循環就取不到了,固然咱們仍是能夠用x[2]去取到2對應的值得,只是for循環中取不到而已。這個有什麼用呢?Vue源碼中Observer類中有下面一行代碼: def(value, 'ob', this);
這裏def是個工具函數,目的是想給value添加一個key爲__ob__,值爲this,可是爲何不直接 value.ob = this 反而要大費周章呢? 由於程序下面要遍歷value對其子內容進行遞歸設置,若是直接用value.__ob__這種方式,在遍歷時又會取到形成,這顯然不是本意,因此def函數是利用Object.defineProperty給value添加的屬性,同時enumerable設置爲false。 至於get和set嘛?這個就更強大了,相似於在獲取對象值和設置對象值時加了一個代理,在這個代理函數中能夠作的東西你就能夠想象了,好比設置值時再通知一下View視圖作更新。也來個例子體會一下吧: let x = {} Object.defineProperty(x, 1, { get: function(){ console.log("getter called!") }, set: function(newVal){ console.log("setter called! newVal is:" + newVal) } })
當咱們訪問x[1]時便會打印getter called,當咱們設置x[1] = 2時,打印setter called。Vue源碼正是經過這種方式實現了訪問屬性時收集依賴,設置屬性時源碼有一句dep.notify,裏面即是通知視圖更新的相關操做。
Vnode,顧名思義,Virtual node,虛擬節點,首先聲明,這不是Vue本身獨創的概念,其實Github上早就有一個相似的項目:Snabbdom。我我的認爲,Vue應該也參考過這個庫的實現,由於這個庫包含了完整的Vnode以及dom diff算法,甚至實現的具體代碼上感受Vue和這個庫也是有點相像的。爲啥要用Vnode呢?其實緣由主要是原生的dom節點對象太大了,咱們運行一下代碼:
let dom = document.createElement('div');
for(let key in dom){
console.log(key)
}
複製代碼
打印的結果灰常長!!!說明這個dom對象節點有點重量級,而咱們的html網頁常常數以百計個這種dom節點,若是採用以前的Jquery這種方式直接操做dom,性能上確實稍微low一點。因此snabbdom或者Vue中應用了Vnode,Vnode對象啥樣呢? 看看Vue源碼對Vnode的定義:
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope key: string | number | void; componentOptions: VNodeComponentOptions | void; componentInstance: Component | void; // component instance parent: VNode | void; // component placeholder node // strictly internal raw: boolean; // contains raw HTML? (server only) isStatic: boolean; // hoisted static node isRootInsert: boolean; // necessary for enter transition check isComment: boolean; // empty comment placeholder? isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? asyncFactory: Function | void; // async component factory function asyncMeta: Object | void; isAsyncPlaceholder: boolean; ssrContext: Object | void; fnContext: Component | void; // real context vm for functional nodes fnOptions: ?ComponentOptions; // for SSR caching fnScopeId: ?string; .... } 複製代碼
相比之下, Vnode對象的屬性確實少了不少;其實光屬性少也不見得性能就能高到哪兒去,另外一個方面即是針對新舊Vnode的diff算法了。這裏其實有一個現象:其實大多數場景下即使有不少修改,可是若是從宏觀角度觀看,其實修改的點很少。舉個例子: 好比有如下三個dom節點A B C 咱們的操做中依次會改爲 B C D 若是採用Jquery的改法,當碰到第一次A改成B時,修改了一次,再碰到B改成C,又修改了一次,再次碰到C改成D,又又修改了一次,顯然其實從宏觀上看,只須要刪除A,而後末尾加上D便可,修改次數獲得減小;可是這種優化是有前提的,也就是說可以從宏觀角度看才行。之前Jquery的修改方法在碰到第一次修改的時候,須要把A改成B,這時代碼尚未執行到後面,它是不可能知道後面的修改的,也就是沒法以全局視角看問題。因此從全局看問題的方式就是異步,先把修改放到隊列中,而後整成一批去修改,作diff,這個時候從統計學意義上來說確實能夠優化性能。這也是爲啥Vue源碼中出現下述代碼的緣由: queueWatcher(this);
函數柯里化是什麼鬼呢?其實就是將多參數的函數化做多個部分函數去調用。舉個例子:
function getSum(a,b){
return a+b;
}
複製代碼
這是個兩個參數的函數,能夠直接getSum(1,2)調用拿到結果;然而,有時候並不會兩個參數都能肯定,只想先傳一個值,另一個在其餘時間點再傳入,那咱們把函數改成:
function getSum(a){
return function(b){
return a+b;
}
}
複製代碼
那咱們如何調用這個柯里化以後的函數呢?
let f = getSum(2)
console.log(f(3))
console.log(getSum(2)(3)) //結果同上
複製代碼
可見,柯里化的效果即是以前必須同時傳入兩個參數才能調用成功而如今兩個參數能夠在不一樣時間點傳入。那爲毛要這麼作嘛?Vue源碼是這麼應用這個特性的,Vue源碼中有一個platform目錄,專門存放和平臺相關的源碼(Vue能夠在多平臺上運行 好比Weex)。那這些源碼中確定有些操做是和平臺相關的,好比會有些如下僞代碼所表示的邏輯: if(平臺A){ .... }else if(平臺B){ .... }
但是若是這麼寫會有個小不舒服的地方,那就是其實代碼運行時第一次走到這裏根據當前平臺就已經知道走哪個分支了,而如今這麼寫必當致使代碼再次運行到這裏的時候還會進行平臺判斷,這樣總感受會多一些無聊的多餘判斷,所以Vue解決此問題的方式就是應用了函數柯里化技巧,相似聲明瞭如下一個函數: function ...(平臺相關參數){ return function(平臺不相關參數){ 處理邏輯 } }
在Vue的patch以及編譯環節都應用了這種方式,講到那部分代碼時咱們再細緻的看,讀者提早先了解一下能夠幫助理解Vue的設計。
可能有的讀者第一次聽到這兩個詞,實際上這個和js的事件循環機制息息相關。在上面咱們也提到,Vue更新不是數據一改立刻同步更新視圖的,這樣確定會有性能問題,好比在一個事件處理函數裏先this.data = A 而後再this.data=B,若是要渲染兩次,想一想都感受很low。Vue源碼其實是將更改都放入到隊列中,同一個watcher不會重複(不理解這些概念沒關係,後面源碼會重點介紹),而後異步處理更新邏輯。在實現異步的方式時,js實際提供了兩種task--Macrotask與Microtask。兩種task有什麼區別呢?先從一個例子講起:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
Promise.resolve().then(function() {
console.log('promise3');
}).then(function() {
console.log('promise4');
});
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
複製代碼
以上代碼運行結果是什麼呢?讀者能夠思考一下,答案應該是:
script start
script end
promise1
promise2
setTimeout
promise3
promise4
複製代碼
簡單能夠這麼理解,js事件循環中有兩個隊列,一個叫MacroTask,一個MircroTask,看名字就知道Macro是大的,Micro是小的(想一想宏觀經濟學和微觀經濟學的翻譯)。那麼大任務隊列跑大任務--好比主流程程序了、事件處理函數了、setTimeout了等等,小任務隊列跑小任務,目前讀者記住一個就能夠--Promise。js老是先從大任務隊列拿一個執行,而後再把全部小任務隊列所有執行再循環往復。以上面示例程序,首先總體上個這個程序是一個大任務先執行,執行完畢後要執行全部小任務,Promise就是小任務,因此又打印出promise1和promise2,而setTimeout是大任務,因此執行完全部小任務以後,再取一個大任務執行,就是setTimeout,這裏面又往小任務隊列扔了一個Promise,因此等setTimeout執行完畢以後,又去執行全部小任務隊列,因此最後是promise3和promise4。說的有點繞,把上面示例程序拷貝到瀏覽器執行一下多思考一下就明白了,關鍵是要知道上面程序自己也是一個大任務。必定要理解了以後再去看Vue源碼,不然不會理解Vue中的nextTick函數。 推薦幾篇文章吧(我都認真讀完了,受益不淺) Macrotask Vs Microtask 理解js中Macrotask和Microtask 阮一峯 Eventloop理解
不少程序員比較懼怕遞歸,可是遞歸真的是一種灰常灰常強大的算法。Vue源碼中大量使用了遞歸算法--好比dom diff算法、ast的優化、目標代碼的生成等等....不少不少。並且這些遞歸不只僅是A->A這麼簡單,大多數源碼中的遞歸是A->B->C...->A等等這種複雜遞歸調用。好比Vue中經典的dom diff算法:
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx];
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); }
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
} else {
vnodeToMove = oldCh[idxInOld];
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue);
oldCh[idxInOld] = undefined;
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
}
}
newStartVnode = newCh[++newStartIdx];
}
複製代碼
上面代碼是比較新舊Vnode節點更新孩子節點的部分源碼,調用者是patchVnode函數,咱們發現這部分函數中又會調用會patchVnode,調用鏈條爲:patchVnode->updateChildren->patchVnode。同時,即使沒有直接應用遞歸,在將模板編譯成AST(抽象語法樹)的過程當中,其使用了棧去模擬了遞歸的思想,因而可知遞歸算法的重要性。這也難怪,畢竟無論是真實dom仍是vnode,其實本質都是樹狀結構,原本就是遞歸定義的東西。咱們也會單獨拿出一篇文章講講遞歸,好比用遞歸實現一下JSON串的解析。但願讀者注意查看。
這恐怕比遞歸更讓某些程序員蛋疼,可是我相信只要讀者認真把Vue這部分代碼看懂,絕對比看N遍編譯原理的課本更能管用。咱們看看Vue源碼這裏的實現:
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
複製代碼
上述代碼首先經過parse函數將template編譯爲抽象語法樹ast,而後對ast進行代碼優化,最後生成render函數。其實這個過程就是翻譯,好比gcc把c語言翻譯爲彙編、又好比Babel把ES6翻譯爲ES5等等,這裏面的流程十分都是十分地類似。Vue也玩了這麼一把,把模板html編譯爲render函數,什麼意思呢?
<li v-for="record in commits">
<span class="date">{{record.commit.author.date}}</span>
</li>
複製代碼
好比上面的html,你以爲瀏覽器會認識嘛?顯然v-for不是html原生的屬性,上述代碼若是直接在瀏覽器運行,你會發現{{record.commit.author.date}}就直接展現出來了,v-for也沒有起做用,固然仍是會出現html裏面(畢竟html容錯性很高的);可是通過Vue的編譯系統一編譯生成一些函數,這些函數一執行就是瀏覽器認識的html元素了,神奇吧? 其實僅僅是應用了編譯原理課本的部分知識罷了,這部分咱們後面會灰常灰常詳細的介紹源碼,只要跟着看下來,一定會對編譯過程有所理解。如今能夠這麼簡單理解一下AST(抽象語法樹),好比java能夠寫一個if判斷,C語言也能夠寫,js、python等等也能夠(以下所示): java: if(x > 5){ .... }
python: if x>5: ....
注意點 在使用生命週期時有幾點注意事項須要咱們牢記。 1.第一點就是上文曾提到的created階段的ajax請求與mounted請求的區別:前者頁面視圖未出現,若是請求信息過多,頁面會長時間處於白屏狀態。 2.除了beforeCreate和created鉤子以外,其餘鉤子均在服務器端渲染期間不被調用。 3.上文曾提到過,在updated的時候千萬不要去修改data裏面賦值的數據,不然會致使死循環。 4.Vue的全部生命週期函數都是自動綁定到this的上下文上。因此,你這裏使用箭頭函數的話,就會出現this指向的父級做用域,就會報錯。緣由下面源碼部分會講解。
一.webpack的基本功能的介紹 Webpack是一款用戶打包前端模塊的工具。主要是用來打包在瀏覽器端使用的javascript的。 同時也能轉換、捆綁、打包其餘的靜態資源,包括css、image、font file、template等。 我的認爲它的優勢就是易用,並且經常使用功能基本都有, 另外能夠經過本身開發loader和plugin來知足本身的需求。 這裏就儘可能詳細的來介紹下一些基本功能的使用。
二.運行webpack webpack須要編寫一個config文件,而後根據這個文件來執行須要的打包功能。 咱們如今來編寫一個最簡單的config。新建一個文件,命名爲webpack-config.js。 config文件實際上就是一個Commonjs的模塊。內容以下:
var webpack = require('webpack'); var path = require('path'); var buildPath = path.resolve(__dirname,"build"); var nodemodulesPath = path.resolve(__dirname,'node_modules');
var config = { //入口文件配置 entry:path.resolve(__dirname,'src/main.js'), resolve:{ extentions:["","js"]//當requrie的模塊找不到時,添加這些後綴 }, //文件導出的配置 output:{ path:buildPath, filename:"app.js" } }
module.exports = config;
1.entry:配置要打包的文件的入口;能夠配置多個入口文件
2.output:output參數是個對象,用於定義構建後的文件的輸出。其中包含path和filename
3.module(loaders): 配置要使用的loader。對文件進行一些相應的處理。好比babel-loader能夠把es6的文件轉換成es5。 大部分的對文件的處理的功能都是經過loader實現的。loader就至關於gulp裏的task。 loader能夠用來處理在入口文件中require的和其餘方式引用進來的文件。loader通常是一個獨立的node模塊,要單獨安裝。 loader項的配置: test: /.(js|jsx)$/,//注意是正則表達式,不要加引號,匹配要處理的文件 loader: 'eslint-loader',//要使用的loader,"-loader"能夠省略 include: [path.resolve(__dirname, "src/app")],//把要處理的目錄包括進來 exclude: [nodeModulesPath]//排除不處理的目錄
module的配置: module: { preLoaders: [ { test: /.(js|jsx)/, //正則表達式匹配 .js 和 .jsx 文件 loader: 'babel-loader?optional=runtime&stage=0',//對匹配的文件進行處理的loader exclude: [nodeModulesPath]//排除node module中的文件 } ] }
process.env,獲取基本的全局環境
經過選擇 development 或 production 之中的一個,來設置 mode 參數, 你能夠啓用相應模式下的 webpack 內置的優化
核心概念
entry output loader plugins 模式
webpack.config.js const config = { entry: './file.js' } module.exports = config
常見場景: 分離 應用程序和第三方庫入口 但頁面應用和多頁面應用
高級進階(使用CDN和資源的hash)
loader的定義 loader 用於對模塊的源代碼進行轉換。
loader 能夠使你在 import 或"加載"模塊時預處理文件。
所以,loader 相似於其餘構建工具中「任務(task)」,並提供了處理前端構建步驟的強大方法。
loader 能夠將文件從不一樣的語言(如 TypeScript)轉換爲 JavaScript,或將內聯圖像轉換爲 data URL。
loader 甚至容許你直接在 JavaScript 模塊中 import CSS文件!
plugins
插件目的在於解決 loader 沒法實現的其餘事
Vuex 是一個專爲 Vue.js 應用程序開發的** 狀態管理模式** 。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化
首先,咱們先看一下下面這個例子
new Vue({
// state
data () {
return {
count: 0
}
},
// view
template: `
<div>{{ count }}</div>
`,
// actions
methods: {
increment () {
this.count++
}
}
})
複製代碼
這個狀態自管理應用包含如下幾個部分:
如下是一個表示「單向數據流」理念的簡單示意:
可是,當咱們的應用遇到多個組件共享狀態時,單向數據流的簡潔性很容易被破壞:
Vuex 是專門爲 Vue.js 設計的狀態管理庫,以利用 Vue.js 的細粒度數據響應機制來進行高效的狀態更新。
Vuex 能夠幫助咱們管理共享狀態,並附帶了更多的概念和框架。這須要對短時間和長期效益進行權衡。 若是您不打算開發大型單頁應用,使用 Vuex 多是繁瑣冗餘的。確實是如此——若是您的應用夠簡單,您最好不要使用 Vuex。一個簡單的 store 模式就足夠您所需了。可是,若是您須要構建一箇中大型單頁應用,您極可能會考慮如何更好地在組件外部管理狀態,Vuex 將會成爲天然而然的選擇
具體的代碼實現
const store = new Vuex.Store({
state: {
token: 'xxx12345'
},
mutations: {
changeToken(state, token) {
state.token = token
}
}
})
// 經過mutation去改變token的狀態值
store.commit('changeToken', 'xxxx12345555')
複製代碼
再次強調,咱們經過提交 mutation 的方式,而非直接改變 store.state.count,是由於咱們想要更明確地追蹤到狀態的變化。這個簡單的約定可以讓你的意圖更加明顯,這樣你在閱讀代碼的時候能更容易地解讀應用內部的狀態改變。此外,這樣也讓咱們有機會去實現一些能記錄每次狀態改變,保存狀態快照的調試工具。有了它,咱們甚至能夠實現如時間穿梭般的調試體驗。
因爲 store 中的狀態是響應式的,在組件中調用 store 中的狀態簡單到僅須要在計算屬性中返回便可。觸發變化也僅僅是在組件的 methods 中提交 mutation。
** 單一狀態樹 ** Vuex 使用單一狀態樹——是的,用一個對象就包含了所有的應用層級狀態。至此它便做爲一個「惟一數據源 (SSOT)」而存在。這也意味着,每一個應用將僅僅包含一個 store 實例。單一狀態樹讓咱們可以直接地定位任一特定的狀態片斷,在調試的過程當中也能輕易地取得整個當前應用狀態的快照。
單狀態樹和模塊化並不衝突——在後面的章節裏咱們會討論如何將狀態和狀態變動事件分佈到各個子模塊中
** 在 Vue 組件中得到 Vuex 狀態 **
那麼咱們如何在 Vue 組件中展現狀態呢?因爲 Vuex 的狀態存儲是響應式的,從 store 實例中讀取狀態最簡單的方法就是在計算屬性中返回某個狀態:
// 建立一個 tree 組件
const trees = {
template: `<div>{{ tree }}</div>`,
computed: {
tree () {
return store.state.tree
}
}
}
複製代碼
每當 store.state.tree 變化的時候, 都會從新求取計算屬性,而且觸發更新相關聯的 DOM。
然而,這種模式致使組件依賴全局狀態單例。在模塊化的構建系統中,在每一個須要使用 state 的組件中須要頻繁地導入,而且在測試組件時須要模擬狀態。
Vuex 經過 store 選項,提供了一種機制將狀態從根組件「注入」到每個子組件中(需調用 Vue.use(Vuex)):
const app = new Vue({
el: '#app',
// 把 store 對象提供給 「store」 選項,這能夠把 store 的實例注入全部的子組件
store,
components: { trees },
template: `
<div class="app">
<trees></trees>
</div>
`
})
複製代碼
經過在根實例中註冊 store 選項,該 store 實例會注入到根組件下的全部子組件中,且子組件能經過 this.$store 訪問到。讓咱們更新下 trees 的實現:
const trees = {
template: `<div>{{ tree }}</div>`,
computed: {
count () {
return this.$store.state.tree
}
}
}
複製代碼
有時候咱們須要從 store 中的 state 中派生出一些狀態,例如對列表進行過濾並計數:
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
複製代碼
若是有多個組件須要用到此屬性,咱們要麼複製這個函數,或者抽取到一個共享函數而後在多處導入它——不管哪一種方式都不是很理想。
Vuex 容許咱們在 store 中定義「getter」(能夠認爲是 store 的計算屬性)。就像計算屬性同樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被從新計算。
Getter 接受 state 做爲其第一個參數:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
複製代碼
** 經過屬性訪問 ** Getter 會暴露爲 store.getters 對象,你能夠以屬性的形式訪問這些值:
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
Getter 也能夠接受其餘 getter 做爲第二個參數:
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1
咱們能夠很容易地在任何組件中使用它:
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
複製代碼
注意,getter 在經過屬性訪問時是做爲 Vue 的響應式系統的一部分緩存其中的。
** 經過方法訪問 ** 你也能夠經過讓 getter 返回一個函數,來實現給 getter 傳參。在你對 store 裏的數組進行查詢時很是有用。
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
複製代碼
注意,getter 在經過方法訪問時,每次都會去進行調用,而不會緩存結果。
更改 Vuex 的 store 中的狀態的惟一方法是提交 mutation。Vuex 中的 mutation 很是相似於事件:每一個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是咱們實際進行狀態更改的地方,而且它會接受 state 做爲第一個參數:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
state.count++
}
}
})
複製代碼
你不能直接調用一個 mutation handler。這個選項更像是事件註冊:「當觸發一個類型爲 increment 的 mutation 時,調用此函數。」要喚醒一個 mutation handler,你須要以相應的 type 調用 store.commit 方法:
store.commit('increment')
複製代碼
** 提交載荷(Payload)**
你能夠向 store.commit 傳入額外的參數,即 mutation 的 載荷(payload):
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
複製代碼
在大多數狀況下,載荷應該是一個對象,這樣能夠包含多個字段而且記錄的 mutation 會更易讀:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
複製代碼
** 對象風格的提交方式 **
store.commit({
type: 'increment',
amount: 10
})
複製代碼
當使用對象風格的提交方式,整個對象都做爲載荷傳給 mutation 函數,所以 handler 保持不變:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
複製代碼
** Mutation 需遵照 Vue 的響應規則 ** 既然 Vuex 的 store 中的狀態是響應式的,那麼當咱們變動狀態時,監視狀態的 Vue 組件也會自動更新。這也意味着 Vuex 中的 mutation 也須要與使用 Vue 同樣遵照一些注意事項:
最好提早在你的 store 中初始化好全部所需屬性。
當須要在對象上添加新屬性時,你應該
使用 Vue.set(obj, 'newProp', 123), 或者
以新對象替換老對象。例如,利用 stage-3 的對象展開運算符咱們能夠這樣寫:
state.obj = { ...state.obj, newProp: 123 } ** 使用常量替代 Mutation 事件類型 ** 使用常量替代 mutation 事件類型在各類 Flux 實現中是很常見的模式。這樣能夠使 linter 之類的工具發揮做用,同時把這些常量放在單獨的文件中可讓你的代碼合做者對整個 app 包含的 mutation 一目瞭然:
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// 咱們能夠使用 ES2015 風格的計算屬性命名功能來使用一個常量做爲函數名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
複製代碼
用不用常量取決於你——在須要多人協做的大型項目中,這會頗有幫助。但若是你不喜歡,你徹底能夠不這樣作。
** Mutation 必須是同步函數 ** 一條重要的原則就是要記住 mutation 必須是同步函數。爲何?請參考下面的例子:
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
複製代碼
如今想象,咱們正在 debug 一個 app 而且觀察 devtool 中的 mutation 日誌。每一條 mutation 被記錄,devtools 都須要捕捉到前一狀態和後一狀態的快照。然而,在上面的例子中 mutation 中的異步函數中的回調讓這不可能完成:由於當 mutation 觸發的時候,回調函數尚未被調用,devtools 不知道何時回調函數實際上被調用——實質上任何在回調函數中進行的狀態的改變都是不可追蹤的。
** 在組件中提交 Mutation ** 你能夠在組件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 輔助函數將組件中的 methods 映射爲 store.commit 調用(須要在根節點注入 store)。
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 將 `this.increment()` 映射爲 `this.$store.commit('increment')`
// `mapMutations` 也支持載荷:
'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 將 `this.add()` 映射爲 `this.$store.commit('increment')`
})
}
}
複製代碼
在 scrimba 上嘗試這節課 Action 相似於 mutation,不一樣在於:
Action 提交的是 mutation,而不是直接變動狀態。 Action 能夠包含任意異步操做。 讓咱們來註冊一個簡單的 action:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
複製代碼
Action 函數接受一個與 store 實例具備相同方法和屬性的 context 對象,所以你能夠調用 context.commit 提交一個 mutation,或者經過 context.state 和 context.getters 來獲取 state 和 getters。當咱們在以後介紹到 Modules 時,你就知道 context 對象爲何不是 store 實例自己了。
實踐中,咱們會常常用到 ES2015 的 參數解構 來簡化代碼(特別是咱們須要調用 commit 不少次的時候):
actions: {
increment ({ commit }) {
commit('increment')
}
}
複製代碼
** 分發 Action ** Action 經過 store.dispatch 方法觸發:
store.dispatch('increment') 乍一眼看上去感受畫蛇添足,咱們直接分發 mutation 豈不更方便?實際上並不是如此,還記得 mutation 必須同步執行這個限制麼?Action 就不受約束!咱們能夠在 action 內部執行異步操做:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
複製代碼
Actions 支持一樣的載荷方式和對象方式進行分發:
// 以載荷形式分發
store.dispatch('incrementAsync', {
amount: 10
})
// 以對象形式分發
store.dispatch({
type: 'incrementAsync',
amount: 10
})
複製代碼
來看一個更加實際的購物車示例,涉及到調用異步 API 和分發多重 mutation:
actions: {
checkout ({ commit, state }, products) {
// 把當前購物車的物品備份起來
const savedCartItems = [...state.cart.added]
// 發出結帳請求,而後樂觀地清空購物車
commit(types.CHECKOUT_REQUEST)
// 購物 API 接受一個成功回調和一個失敗回調
shop.buyProducts(
products,
// 成功操做
() => commit(types.CHECKOUT_SUCCESS),
// 失敗操做
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
複製代碼
注意咱們正在進行一系列的異步操做,而且經過提交 mutation 來記錄 action 產生的反作用(即狀態變動)。
** 在組件中分發 Action ** 你在組件中使用 this.$store.dispatch('xxx') 分發 action,或者使用 mapActions 輔助函數將組件的 methods 映射爲 store.dispatch 調用(須要先在根節點注入 store):
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 將 `this.increment()` 映射爲 `this.$store.dispatch('increment')`
// `mapActions` 也支持載荷:
'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 將 `this.add()` 映射爲 `this.$store.dispatch('increment')`
})
}
}
複製代碼
** 組合 Action ** Action 一般是異步的,那麼如何知道 action 何時結束呢?更重要的是,咱們如何才能組合多個 action,以處理更加複雜的異步流程?
首先,你須要明白 store.dispatch 能夠處理被觸發的 action 的處理函數返回的 Promise,而且 store.dispatch 仍舊返回 Promise:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
複製代碼
如今你能夠:
store.dispatch('actionA').then(() => {
// ...
})
在另一個 action 中也能夠:
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
複製代碼
最後,若是咱們利用 async / await,咱們能夠以下組合 action:
// 假設 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
複製代碼
一個 store.dispatch 在不一樣模塊中能夠觸發多個 action 函數。在這種狀況下,只有當全部觸發函數完成後,返回的 Promise 纔會執行。
因爲使用單一狀態樹,應用的全部狀態會集中到一個比較大的對象。當應用變得很是複雜時,store 對象就有可能變得至關臃腫。
爲了解決以上問題,Vuex 容許咱們將 store 分割成模塊(module)。每一個模塊擁有本身的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行一樣方式的分割:
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態
複製代碼
** 模塊的局部狀態 **
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
// 這裏的 `state` 對象是模塊的局部狀態
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
複製代碼
一樣,對於模塊內部的 action,局部狀態經過 context.state 暴露出來,根節點狀態則爲 context.rootState:
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
複製代碼
對於模塊內部的 getter,根節點狀態會做爲第三個參數暴露出來:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
複製代碼
** 命名空間 ** 默認狀況下,模塊內部的 action、mutation 和 getter 是註冊在全局命名空間的——這樣使得多個模塊可以對同一 mutation 或 action 做出響應。
若是但願你的模塊具備更高的封裝度和複用性,你能夠經過添加 namespaced: true 的方式使其成爲帶命名空間的模塊。當模塊被註冊後,它的全部 getter、action 及 mutation 都會自動根據模塊註冊的路徑調整命名。例如:
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模塊內容(module assets)
state: { ... }, // 模塊內的狀態已是嵌套的了,使用 `namespaced` 屬性不會對其產生影響
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模塊
modules: {
// 繼承父模塊的命名空間
myPage: {
state: { ... },
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 進一步嵌套命名空間
posts: {
namespaced: true,
state: { ... },
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
複製代碼
啓用了命名空間的 getter 和 action 會收到局部化的 getter,dispatch 和 commit。換言之,你在使用模塊內容(module assets)時不須要在同一模塊內額外添加空間名前綴。更改 namespaced 屬性後不須要修改模塊內的代碼。
** 在帶命名空間的模塊內訪問全局內容(Global Assets)** 若是你但願使用全局 state 和 getter,rootState 和 rootGetter 會做爲第三和第四參數傳入 getter,也會經過 context 對象的屬性傳入 action。
若須要在全局命名空間內分發 action 或提交 mutation,將 { root: true } 做爲第三參數傳給 dispatch 或 commit 便可。
modules: {
foo: {
namespaced: true,
getters: {
// 在這個模塊的 getter 中,`getters` 被局部化了
// 你能夠使用 getter 的第四個參數來調用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// 在這個模塊中, dispatch 和 commit 也被局部化了
// 他們能夠接受 `root` 屬性以訪問根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
複製代碼
** 在帶命名空間的模塊註冊全局 action ** 若須要在帶命名空間的模塊註冊全局 action,你可添加 root: true,並將這個 action 的定義放在函數 handler 中。例如:
{
actions: {
someOtherAction ({dispatch}) {
dispatch('someAction')
}
},
modules: {
foo: {
namespaced: true,
actions: {
someAction: {
root: true,
handler (namespacedContext, payload) { ... } // -> 'someAction'
}
}
}
}
}
複製代碼
** 帶命名空間的綁定函數 ** 當使用 mapState, mapGetters, mapActions 和 mapMutations 這些函數來綁定帶命名空間的模塊時,寫起來可能比較繁瑣:
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
}
複製代碼
對於這種狀況,你能夠將模塊的空間名稱字符串做爲第一個參數傳遞給上述函數,這樣全部綁定都會自動將該模塊做爲上下文。因而上面的例子能夠簡化爲:
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
複製代碼
並且,你能夠經過使用 createNamespacedHelpers 建立基於某個命名空間輔助函數。它返回一個對象,對象裏有新的綁定在給定命名空間值上的組件綁定輔助函數:
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
複製代碼
** 給插件開發者的注意事項 ** 若是你開發的插件(Plugin)提供了模塊並容許用戶將其添加到 Vuex store,可能須要考慮模塊的空間名稱問題。對於這種狀況,你能夠經過插件的參數對象來容許用戶指定空間名稱:
// 經過插件的參數對象獲得空間名稱 // 而後返回 Vuex 插件函數
export function createPlugin (options = {}) {
return function (store) {
// 把空間名字添加到插件模塊的類型(type)中去
const namespace = options.namespace || ''
store.dispatch(namespace + 'pluginAction')
}
}
複製代碼
** 模塊動態註冊 ** 在 store 建立以後,你能夠使用 store.registerModule 方法註冊模塊:
// 註冊模塊 myModule
store.registerModule('myModule', { // ... }) // 註冊嵌套模塊 nested/myModule
store.registerModule(['nested', 'myModule'], { // ... }) 以後就能夠經過 store.state.myModule 和 store.state.nested.myModule 訪問模塊的狀態。
模塊動態註冊功能使得其餘 Vue 插件能夠經過在 store 中附加新模塊的方式來使用 Vuex 管理狀態。例如,vuex-router-sync 插件就是經過動態註冊模塊將 vue-router 和 vuex 結合在一塊兒,實現應用的路由狀態管理。
你也能夠使用 store.unregisterModule(moduleName) 來動態卸載模塊。注意,你不能使用此方法卸載靜態模塊(即建立 store 時聲明的模塊)。
** 保留 state ** 在註冊一個新 module 時,你頗有可能想保留過去的 state,例如從一個服務端渲染的應用保留 state。你能夠經過 preserveState 選項將其歸檔:store.registerModule('a', module, { preserveState: true })。
當你設置 preserveState: true 時,該模塊會被註冊,action、mutation 和 getter 會被添加到 store 中,可是 state 不會。這裏假設 store 的 state 已經包含了這個 module 的 state 而且你不但願將其覆寫。
** 模塊重用 ** 有時咱們可能須要建立一個模塊的多個實例,例如:
建立多個 store,他們公用同一個模塊 (例如當 runInNewContext 選項是 false 或 'once' 時,爲了在服務端渲染中避免有狀態的單例) 在一個 store 中屢次註冊同一個模塊 若是咱們使用一個純對象來聲明模塊的狀態,那麼這個狀態對象會經過引用被共享,致使狀態對象被修改時 store 或模塊間數據互相污染的問題。
實際上這和 Vue 組件內的 data 是一樣的問題。所以解決辦法也是相同的——使用一個函數來聲明模塊狀態(僅 2.3.0+ 支持):
const MyReusableModule = {
state () {
return {
foo: 'bar'
}
},
// mutation, action 和 getter 等等...
}
複製代碼
1、前言 要學習vue-router就要先知道這裏的路由是什麼?爲何咱們不能像原來同樣直接用標籤編寫連接哪?vue-router如何使用?常見路由操做有哪些?等等這些問題,就是本篇要探討的主要問題。
想閱讀更多優質文章請猛戳GitHub博客
2、vue-router是什麼 這裏的路由並非指咱們平時所說的硬件路由器,這裏的路由就是SPA(單頁應用)的路徑管理器。再通俗的說,vue-router就是WebApp的連接路徑管理系統。 vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,適合用於構建單頁面應用。vue的單頁面應用是基於路由和組件的,路由用於設定訪問路徑,並將路徑和組件映射起來。傳統的頁面應用,是用一些超連接來實現頁面切換和跳轉的。在vue-router單頁面應用中,則是路徑之間的切換,也就是組件的切換。路由模塊的本質 就是創建起url和頁面之間的映射關係。
至於咱們爲啥不能用a標籤,這是由於用Vue作的都是單頁應用(當你的項目準備打包時,運行npm run build時,就會生成dist文件夾,這裏面只有靜態資源和一個index.html頁面),因此你寫的標籤是不起做用的,你必須使用vue-router來進行管理。
3、vue-router實現原理 SPA(single page application):單一頁面應用程序,只有一個完整的頁面;它在加載頁面時,不會加載整個頁面,而是隻更新某個指定的容器中內容。單頁面應用(SPA)的核心之一是: 更新視圖而不從新請求頁面;vue-router在實現單頁面前端路由時,提供了兩種方式:Hash模式和History模式;根據mode參數來決定採用哪種方式。
一、Hash模式: vue-router 默認 hash 模式 —— 使用 URL 的 hash 來模擬一個完整的 URL,因而當 URL 改變時,頁面不會從新加載。 hash(#)是URL 的錨點,表明的是網頁中的一個位置,單單改變#後的部分,瀏覽器只會滾動到相應位置,不會從新加載網頁,也就是說hash 出如今 URL 中,但不會被包含在 http 請求中,對後端徹底沒有影響,所以改變 hash 不會從新加載頁面;同時每一次改變#後的部分,都會在瀏覽器的訪問歷史中增長一個記錄,使用」後退」按鈕,就能夠回到上一個位置;因此說Hash模式經過錨點值的改變,根據不一樣的值,渲染指定DOM位置的不一樣數據。hash 模式的原理是 onhashchange 事件(監測hash值變化),能夠在 window 對象上監聽這個事件。
二、History模式: 因爲hash模式會在url中自帶#,若是不想要很醜的 hash,咱們能夠用路由的 history 模式,只須要在配置路由規則時,加入"mode: 'history'",這種模式充分利用了html5 history interface 中新增的 pushState() 和 replaceState() 方法。這兩個方法應用於瀏覽器記錄棧,在當前已有的 back、forward、go 基礎之上,它們提供了對歷史記錄修改的功能。只是當它們執行修改時,雖然改變了當前的 URL ,但瀏覽器不會當即向後端發送請求。
//main.js文件中 const router = new VueRouter({ mode: 'history', routes: [...] }) 當你使用 history 模式時,URL 就像正常的 url,例如 yoursite.com/user/id,比較好… 不過這種模式要玩好,還須要後臺配置支持。由於咱們的應用是個單頁客戶端應用,若是後臺沒有正確的配置,當用戶在瀏覽器直接訪問 oursite.com/user/id 就會返回 404,這就很差看了。 因此呢,你要在服務端增長一個覆蓋全部狀況的候選資源:若是 URL 匹配不到任何靜態資源,則應該返回同一個 index.html 頁面,這個頁面就是你 app 依賴的頁面。
export const routes = [ {path: "/", name: "homeLink", component:Home} {path: "/register", name: "registerLink", component: Register}, {path: "/login", name: "loginLink", component: Login}, {path: "*", redirect: "/"}] 此處就設置若是URL輸入錯誤或者是URL 匹配不到任何靜態資源,就自動跳到到Home頁面
三、使用路由模塊來實現頁面跳轉的方式 方式1:直接修改地址欄
方式2:this.$router.push(‘路由地址’)
方式3:
4、vue-router使用方式 1:下載 npm i vue-router -S 2:在main.js中引入 import VueRouter from 'vue-router'; 3:安裝插件Vue.use(VueRouter); 4:建立路由對象並配置路由規則 let router = new VueRouter({routes:[{path:'/home',component:Home}]}); 5:將其路由對象傳遞給Vue的實例,options中加入 router:router 6:在app.vue中留坑
具體實現請看以下代碼:
//main.js文件中引入 import Vue from 'vue'; import VueRouter from 'vue-router'; //主體 import App from './components/app.vue'; import Home from './components/home.vue' //安裝插件 Vue.use(VueRouter); //掛載屬性 //建立路由對象並配置路由規則 let router = new VueRouter({ routes: [ //一個個對象 { path: '/home', component: Home } ] }); //new Vue 啓動 new Vue({ el: '#app', //讓vue知道咱們的路由規則 router: router, //能夠簡寫router render: c => c(App), }) 最後記得在在app.vue中「留坑」
5、 vue-router參數傳遞 聲明式的導航和編程式的導航router.push(...)均可以傳參,本文主要介紹前者的傳參方法,一樣的規則也適用於編程式的導航。
1.用name傳遞參數 在路由文件src/router/index.js裏配置name屬性
routes: [ { path: '/', name: 'Hello', component: Hello } ] 模板裏(src/App.vue)用route.name}}
2 經過 標籤中的to傳參 這種傳參方法的基本語法:
valueString 好比先在src/App.vue文件中
Hi頁面1 而後把src/router/index.js文件裏給hi1配置的路由起個name,就叫hi1.
{path:'/hi1',name:'hi1',component:Hi1} 最後在模板裏(src/components/Hi1.vue)用$route.params.username進行接收.
{{route.params.id}} 3 利用url傳遞參數----在配置文件裏以冒號的形式設置參數。 咱們在/src/router/index.js文件裏配置路由
{ path:'/params/:newsId/:newsTitle', component:Params } 咱們須要傳遞參數是新聞ID(newsId)和新聞標題(newsTitle).因此咱們在路由配置文件裏制定了這兩個值。
在src/components目錄下創建咱們params.vue組件,也能夠說是頁面。咱們在頁面裏輸出了url傳遞的的新聞ID和新聞標題。
<template>
<div>
<h2>{{ msg }}</h2>
<p>新聞ID:{{ $route.params.newsId}}</p>
<p>新聞標題:{{ $route.params.newsTitle}}</p>
</div>
</template>
<script>
export default {
name: 'params',
data () {
return {
msg: 'params page'
}
}
}
</script>
複製代碼
在App.vue文件里加入咱們的標籤。這時候咱們能夠直接利用url傳值了
params
對應路由配置:
{
path: '/query',
name: 'Query',
component: Query
}
複製代碼
因而咱們能夠獲取參數:
this.$route.query.queryId 6、vue-router配置子路由(二級路由) 實際生活中的應用界面,一般由多層嵌套的組件組合而成。一樣地,URL中各段動態路徑也按某種結構對應嵌套的各層組件,例如:
image 如何實現下圖效果(H1頁面和H2頁面嵌套在主頁中)? image 1.首先用標籤增長了兩個新的導航連接
主頁 H1頁面 H2頁面 2.在HelloWorld.vue加入標籤,給子模板提供插入位置
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<router-view></router-view>
</div>
</template>
複製代碼
3.在components目錄下新建兩個組件模板 H1.vue 和 H2.vue 二者內容相似,如下是H1.vue頁面內容:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
msg: 'I am H1 page,Welcome to H1'
}
}
}
</script>
修改router/index.js代碼,子路由的寫法是在原有的路由配置下加入children字段。
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld,
children: [{path: '/h1', name: 'H1', component: H1},//子路由的<router-view>必須在HelloWorld.vue中出現
{path: '/h2', name: 'H2', component: H2}
]
}
]
複製代碼
7、單頁面多路由區域操做 在一個頁面裏咱們有兩個以上區域,咱們經過配置路由的js文件,來操做這些區域的內容
1.App.vue文件,在下面新寫了兩行標籤,並加入了些CSS樣式
<template>
<div id="app">
<img src="./assets/logo.png">
<router-link :to="{name:'HelloWorld'}"><h1>H1</h1></router-link>
<router-link :to="{name:'H1'}"><h1>H2</h1></router-link>
<router-view></router-view>
<router-view name="left" style="float:left;width:50%;background-color:#ccc;height:300px;"/>
<router-view name="right" style="float:right;width:50%;background-color:yellowgreen;height:300px;"/>
</div>
</template>
複製代碼
2.須要在路由裏配置這三個區域,配置主要是在components字段裏進行
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
components: {default: HelloWorld,
left:H1,//顯示H1組件內容'I am H1 page,Welcome to H1'
right:H2//顯示H2組件內容'I am H2 page,Welcome to H2'
}
},
{
path: '/h1',
name: 'H1',
components: {default: HelloWorld,
left:H2,//顯示H2組件內容
right:H1//顯示H1組件內容
}
}
]
})
複製代碼
上邊的代碼咱們編寫了兩個路徑,一個是默認的‘/’,另外一個是‘/Hi’.在兩個路徑下的components裏面,咱們對三個區域都定義了顯示內容。最後頁面展現以下圖:
image 八.router 的區別 咱們先將這二者console.log打印出來:
image image $route 是「路由信息對象」,包括 path,params,hash,query,fullPath,matched,name 等路由信息參數。
① $route.path 字符串,對應當前路由的路徑,老是解析爲絕對路徑,如 "/order"。
② $route.params 一個 key/value 對象,包含了 動態片斷 和 全匹配片斷, 若是沒有路由參數,就是一個空對象。
③ route.query.user爲1, 若是沒有查詢參數,則是個空對象。
④ $route.hash 當前路由的 hash 值 (不帶 #) ,若是沒有 hash 值,則爲空字符串。
⑤ $route.fullPath 完成解析後的 URL,包含查詢參數和 hash 的完整路徑。
⑥ $route.matched 數組,包含當前匹配的路徑中所包含的全部片斷所對應的配置參數對象。
⑦ $route.name 當前路徑名字
$router 是「路由實例」對象,即便用 new VueRouter建立的實例,包括了路由的跳轉方法,鉤子函數等。
$router常見跳轉方法:
<button @click="goToMenu" class="btn btn-success">Let's order! .....
<script>
export default{
methods:{
goToMenu(){
this.$router.go(-1)//跳轉到上一次瀏覽的頁面
this.$router.replace('/menu')//指定跳轉的地址
this.$router.replace({name:'menuLink'})//指定跳轉路由的名字下
this.$router.push('/menu')//經過push進行跳轉
this.$router.push({name:'menuLink'})//經過push進行跳轉路由的名字下
}
}
}
</script>
複製代碼
router.replace的區別:
使用push方法的跳轉會向 history 棧添加一個新的記錄,當咱們點擊瀏覽器的返回按鈕時能夠看到以前的頁面。 使用replace方法不會向 history 添加新記錄,而是替換掉當前的 history 記錄,即當replace跳轉到的網頁後,‘後退’按鈕不能查看以前的頁面。 9、 如何設置404頁面 用戶會常常輸錯頁面,當用戶輸錯頁面時,咱們但願給他一個友好的提示頁面,這個頁面就是咱們常說的404頁面。vue-router也爲咱們提供了這樣的機制。
設置咱們的路由配置文件(/src/router/index.js) { path:'', component:Error } 這裏的path:''就是輸入地址不匹配時,自動顯示出Error.vue的文件內容
在/src/components/文件夾下新建一個Error.vue的文件。簡單輸入一些有關錯誤頁面的內容。
<template>
<div>
<h2>{{ msg }}</h2>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'Error:404'
}
}
}
</script>
複製代碼
此時咱們隨意輸入一個錯誤的地址時,便會自動跳轉到404頁面
Axios 是一個基於 promise 的 HTTP 庫,能夠用在瀏覽器和 node.js 中。
1.從瀏覽器中建立 XMLHttpRequests
2.從 node.js 建立 http 請求
3.支持 Promise API
4.攔截請求和響應
5.轉換請求數據和響應數據
6.取消請求
7.自動轉換 JSON 數據
8.客戶端支持防護 XSRF
執行 GET 請求
axios.get('/user?id=12345')
.then(function (response) {
console.log(response);
})
複製代碼
執行 POST 請求
axios.post('/user', {
name: 'zxm'
})
.then(function (response) {
console.log(response);
})
複製代碼
'use strict';
var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');
// 重點 createInstance 方法
// 先眼熟一個代碼 下面講完工具函數會再具體來說解 createInstance
function createInstance(defaultConfig) {
// 實例化 Axios
var context = new Axios(defaultConfig);
// 自定義 bind 方法 返回一個函數()=> {Axios.prototype.request.apply(context,args)}
var instance = bind(Axios.prototype.request, context);
// Axios 源碼的工具類
utils.extend(instance, Axios.prototype, context);
utils.extend(instance, context);
return instance;
}
// 傳入一個默認配置 defaults 配置先無論,後面會有具體的細節
var axios = createInstance(defaults);
// 下面都是爲 axios 實例化的對象增長不一樣的方法。
axios.Axios = Axios;
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require('./helpers/spread');
module.exports = axios;
module.exports.default = axios;
複製代碼
module.exports = {
isArray: isArray,
isArrayBuffer: isArrayBuffer,
isBuffer: isBuffer,
isFormData: isFormData,
isArrayBufferView: isArrayBufferView,
isString: isString,
isNumber: isNumber,
isObject: isObject,
isUndefined: isUndefined,
isDate: isDate,
isFile: isFile,
isBlob: isBlob,
isFunction: isFunction,
isStream: isStream,
isURLSearchParams: isURLSearchParams,
isStandardBrowserEnv: isStandardBrowserEnv,
forEach: forEach,
merge: merge,
deepMerge: deepMerge,
extend: extend,
trim: trim
};
複製代碼
// a, b,thisArg 參數都爲一個對象
function extend(a, b, thisArg) {
forEach(b, function assignValue(val, key) {
// 若是指定了 thisArg 那麼綁定執行上下文到 thisArg
if (thisArg && typeof val === 'function') {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
});
return a;
}
複製代碼
'use strict';
var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
// 核心方法 request
Axios.prototype.request = function request(config) {
// ... 單獨講
};
// 合併配置將用戶的配置 和默認的配置合併
Axios.prototype.getUri = function getUri(config) {
config = mergeConfig(this.defaults, config);
return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};
// 這個就是給 Axios.prototype 上面增長 delete,get,head,options 方法
// 這樣咱們就能夠使用 axios.get(), axios.post() 等等方法
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
// 都是調用了 this.request 方法
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
module.exports = Axios;
複製代碼
上面的全部的方法都是經過調用了 this.request 方法
那麼咱們就來看這個 request 方法,我的認爲是源碼內的精華也是比較難理解的部分,使用到了 Promise 的鏈式調用,也使用到了中間件的思想。
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
Axios.prototype.request = function request(config) {
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
// 合併配置
config = mergeConfig(this.defaults, config);
// 請求方式,沒有默認爲 get
config.method = config.method ? config.method.toLowerCase() : 'get';
// 重點 這個就是攔截器的中間件
var chain = [dispatchRequest, undefined];
// 生成一個 promise 對象
var promise = Promise.resolve(config);
// 將請求前方法置入 chain 數組的前面 一次置入兩個 成功的,失敗的
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// 將請求後的方法置入 chain 數組的後面 一次置入兩個 成功的,失敗的
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
// 經過 shift 方法把第一個元素從其中刪除,並返回第一個元素。
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
複製代碼
'use strict';
var utils = require('./../utils');
function InterceptorManager() {
// 存放方法的數組
this.handlers = [];
}
// 經過 use 方法來添加攔截方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
// 經過 eject 方法來刪除攔截方法
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
// 添加一個 forEach 方法,這就是上述說的 forEach
InterceptorManager.prototype.forEach = function forEach(fn) {
// 裏面仍是依舊使用了 utils 的 forEach, 不要糾結這些 forEach 的具體代碼
// 明白他們幹了什麼就能夠
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
module.exports = InterceptorManager;
複製代碼
'use strict';
var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');
// 請求取消時候的方法,暫不看
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
}
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
// 請求沒有取消 執行下面的請求
if (config.baseURL && !isAbsoluteURL(config.url)) {
config.url = combineURLs(config.baseURL, config.url);
}
config.headers = config.headers || {};
// 轉換數據
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// 合併配置
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {}
);
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
// 這裏是重點, 獲取請求的方式,下面會將到
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// 難道了請求的數據, 轉換 data
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
// 失敗處理
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
};
複製代碼
看了這麼多,咱們還沒看到是經過什麼來發送請求的,如今咱們看看在最開始實例化 createInstance 方法中咱們傳入的 defaults 是什麼
var axios = createInstance(defaults);
'use strict';
var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');
var DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded'
};
function setContentTypeIfUnset(headers, value) {
if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
headers['Content-Type'] = value;
}
}
// getDefaultAdapter 方法是來獲取請求的方式
function getDefaultAdapter() {
var adapter;
// process 是 node 環境的全局變量
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// 若是是 node 環境那麼久經過 node http 的請求方法
adapter = require('./adapters/http');
} else if (typeof XMLHttpRequest !== 'undefined') {
// 若是是瀏覽器啥的 有 XMLHttpRequest 的就用 XMLHttpRequest
adapter = require('./adapters/xhr');
}
return adapter;
}
var defaults = {
// adapter 就是請求的方法
adapter: getDefaultAdapter(),
// 下面一些請求頭,轉換數據,請求,詳情的數據
// 這也就是爲何咱們能夠直接拿到請求的數據時一個對象,若是用 ajax 咱們拿到的都是 jSON 格式的字符串
// 而後每次都經過 JSON.stringify(data)來處理結果。
transformRequest: [function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
return data;
}],
transformResponse: [function transformResponse(data) {
/*eslint no-param-reassign:0*/
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}],
/**
* A timeout in milliseconds to abort a request. If set to 0 (default) a
* timeout is not created.
*/
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
}
};
defaults.headers = {
common: {
'Accept': 'application/json, text/plain, */*'
}
};
utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
defaults.headers[method] = {};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
module.exports = defaults;
複製代碼
做爲vue的全家桶技術棧,vue、vuex、vue-Router、axios是必須須要掌握的,掌握的越深,你對前端架構的掌控能力越強,但願你們能夠在前端技術有所做爲!