一、Vue構造函數
import { initMinxin } from './init'
import { stateMinxin } from './state'
import { renderMinxin } from './render'
import { eventsMinxin } from './events'
import { lifecycleMinxin } from './lifecycle'
import { warn } from '../util/index'
function Vue(options){
if(process.env.NODE_ENV !=='production' && !(this instanceof Vue)){
warn('Vue is a constructor and should be called with the 'new' keyword')
}
this._init(options)
}
initMinxin(Vue)
stateMinxin(Vue)
eventsMinxin(Vue)
lifecycleMinxin(Vue)
renderMinxin(Vue)
export default Vue
(1)其中定義了Vue構造函數,然後分別調用了initMinxin、stateMinxin、eventsMinxin、lifecycleMinxin、renderMinxin這5個函數,並將Vue構造函數當作參數傳給了這5個函數。
(2)這5個函數的作用就是向Vue的原型中掛載方法。
export function initMinxin(Vue){
Vue.prototype._init = function(options){
//做些什麼
}
}
1、當函數initMinxin被調用時,會向Vue構造函數的prototype屬性添加_init方法。
2、執行new Vue()時,會調用 _init 方法,該方法實現了一系列初始化操作,包括整個生命週期的流程以及響應式系統流程的啓動等。
3、其他4個函數也是如此,只是它們會在Vue構造函數的prototype屬性上掛載不同的方法而已。
二、數據相關的實例方法
(1)與數據相關的實例方法有3個,分別是vm.$watch、vm.$set 和 vm.$delete,它們是在stateMinxin中掛載到Vue的原型上的。
import{ set,del} from '../observer.index'
export function stateMinxin(Vue){
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
Vue.prototype.$watch = function(expOrFn,cb,options){}
}
1、當stateMinxin被調用時,會向Vue構造函數的prototype屬性掛載上面說的3個與數據相關的實例方法。
三、事件相關的實例方法
(1)與事件相關的實例方法有4個,分別是:vm.$on、vm.$once、vm.$off 和 vm.$emit。這4個方法是在eventsMinxin中掛載到Vue構造函數的prototype屬性中的。
export function eventsMinxin(Vue){
Vue.prototype.$on = function(event,fn){
//做點什麼
}
Vue.prototype.$once = function(event,fn){
//做點什麼
}
Vue.prototype.$off = function(event,fn){
//做點什麼
}
Vue.prototype.$emit = function(event,fn){
//做點什麼
}
}
1、當eventsMinxin被調用時,會向Vue構造函數的prototype屬性添加4個實例方法。
四、vm.$on
vm.$on(event,callback)
(1)參數
- { string | Array<string> } event
- { Function } callback
(2)用法
監聽當前實例上的自定義事件,事件可以由vm.$emit觸發。回調函數會接收所有傳入事件所觸發的函數的額外參數。
(3)示例
vm.$on('test',function(msg){
console.log(msg);
})
vm.$emit('test','hi');
// => "hi"
(4)內部原理:
1、只需要在註冊事件時將回調函數收集起來,在觸發事件時將收集起來的回調函數依次調用即可。
Vue.prototype.$on = function(event,fn){
const vm = this;
if(Array.isArray(event)){
for(let i = 0,l = event.length;i<l;i++){
this.$on(event[i],fn)
}
}else{
(vm._events[event] || (vm._events[event] = [])).push(fn);
}
return vm;
}
2、當event參數爲數組時,需要遍歷數組,將其中的每一項遞歸調用vm.$on,使回調可以被註冊到數組中每項事件名所指定的事件列表中。
3、當event參數不爲數組時,就向事件列表中添加回調。通俗地講,就是將回調註冊到事件列表中。
4、vm._events是一個對象,用來存儲事件。
5、使用事件名(event)從vm._events中取出事件列表,如果列表不存在,則使用空數組初始化,然後再將回調函數添加到事件列表中。
6、再執行 new Vue()時,Vue會執行 this._init方法進行一系列初始化操作,其中就會再Vue.js的實例上創建一個_events屬性,用來存儲事件。
vm._events = Object.create(null)
五、vm.$off
vm.$off([event,callback])
(1)參數
- { string | Array<string> } event
- { Function } callback
(2)用法:移除自定義事件監聽器
1、如果沒有提供參數,則移除所有的事件監聽器。
2、如果只提供了事件,則移除該事件所有的監聽器。
3、如果同時提供了事件與回調,則只移除這個回調的監聽器。
(3)沒有提供參數,則移除所有的事件監聽器
Vue.prototype.$off = function(event,fn){
const vm = this;
<!-- 移除所有事件的監聽器 -->
if(!arguments.length){
vm._events = Object.create(null);
return vm;
}
return vm;
}
(4)event參數爲數組
只需要將數組遍歷一遍,然後數組中每一項依次調用vm.$off即可
Vue.prototype.$off = function(event,fn){
const vm = this;
<!-- 移除所有事件的監聽器 -->
if(!arguments.length){
vm._events = Object.create(null);
return vm;
}
<!-- event支持數組 -->
if(Array.isArray(event)){
for(let i = 0,l = event.length;i<l;i++){
this.$off(event[i],fn);
}
return vm;
}
return vm;
}
(5)如果只提供了事件,則移除該事件所有的監聽器
Vue.prototype.$off = function(event,fn){
const vm = this;
<!-- 移除所有事件的監聽器 -->
if(!arguments.length){
vm._events = Object.create(null);
return vm;
}
<!-- event支持數組 -->
if(Array.isArray(event)){
for(let i = 0,l = event.length;i<l;i++){
this.$off(event[i],fn);
}
return vm;
}
<!-- 安全監測 -->
const cbs = vm._events[event];
if(!cbs){
return vm;
}
<!-- 移除該事件的所有監聽器 -->
if(arguments.length ===1 ){
vm._events[event] = null;
return vm;
}
return vm;
}
1、需要進行一個安全監測。如果這個事件沒有被監聽,也就是說vm._events中找不到任何監聽器,那麼什麼都不需要做,直接退出程序即可。
2、然後判斷是否只有一個參數,如果是,將事件名在vm._events中的所有事件都移除。
(6)如果同時提供了事件與回調,則只移除這個回調的監聽器。
只需要使用參數中提供的事件名從vm._events上取出事件列表,然後從列表中找到與參數中提供的回調函數相同的那個函數,並將它從列表中移除即可。
Vue.prototype.$off = function(event,fn){
const vm = this;
<!-- 移除所有事件的監聽器 -->
if(!arguments.length){
vm._events = Object.create(null);
return vm;
}
<!-- event支持數組 -->
if(Array.isArray(event)){
for(let i = 0,l = event.length;i<l;i++){
this.$off(event[i],fn);
}
return vm;
}
<!-- 安全監測 -->
const cbs = vm._events[event];
if(!cbs){
return vm;
}
<!-- 移除該事件的所有監聽器 -->
if(arguments.length ===1 ){
vm._events[event] = null;
return vm;
}
if(fn){
const cbs = vm._events[event];
let cb;
let i = cbs.length;
while(i--){
cb = cbs[i];
if(cb === fn || cb.fn === fn ){
cbs.splice(i,1);
break;
}
}
}
return vm;
}
1、首先判斷是否有fn函數,有則說明用戶同時提供了event和fn這兩個參數。
2、從vm._events中取出事件監聽器列表並遍歷它,如果列表中的某一項與fn相同,或者某一項的fn屬性與fn相同,或者某一項的fn屬性與fn相同,說明已經找到了需要刪除的監聽器(也就是回調函數),這時使用splice方法將它從列表中移除即可。
3、當循環結束後,列表中所有與用戶在參數中提供的fn相同的監聽器都會被移除。
4、注意:在代碼中遍歷列表是從後向前循環,這樣在列表中移除當前位置的監聽器時,不會影響列表中未遍歷到的監聽器的位置。如果是從前向後遍歷,那麼從列表中移除一個監聽器時,後面的監聽器會自動向前移動一個位置,這會導致下一輪循環時跳過一個元素。
六、vm.$once
vm.$once(event,callback)
(1)參數
- { string | Array<string> } event
- { Function } callback
(2)用法
監聽一個自定義事件,但是隻觸發一次,在第一次觸發之後移除監聽器。
(3)區別
vm.$on和vm.$once的區別是vm.$once只能觸發一次,所以實現這個功能的一個思路是:在vm.$once中調用vm.$on來實現監聽自定義事件的功能,當自定義事件觸發後會執行攔截器,將監聽器從事件列表中移除。
Vue.prototype.$once = function(event,fn){
const vm = this;
function on (){
vm.$off(event,on);
fn.apply(vm.arguments);
}
on.fn = fn;
vm.$on(event,on);
return vm;
}
1、首先,將函數on註冊到事件中。當自定義事件被觸發後,會先執行函數on(在這個函數中個,會使用vm.$off(event,on)將自定義事件移除),然後手動執行函數fn,並將參數arguments傳遞給函數fn,這就可以實現vm.$once的功能。
(4)注意
1、注意 on.fn = fn;這行代碼。在移除監聽器時,需要將用戶提供的監聽器函數與列表中的監聽器函數進行對比,相同部分會被移除,這導致當我們使用攔截器代替監聽器注入到事件列表中時,攔截器和用戶提供的函數是不相同的,此時用戶使用vm.$off來移除事件監聽器,移除操作會失敗。
2、解決方案
將用戶提供的原始監聽器保存到攔截器的fn屬性中,當vm.$off方法遍歷事件監聽器列表時,同時會檢查監聽器和監聽器的fn屬性是否與用戶提供的監聽器函數相同,只要有一個相同,就說明需要被移除的監聽器被找到了,將被找到的攔截器從事件監聽器列表中移除即可。
3、在vm.$off中,會檢查監聽器(cb)和監聽器的fn屬性是否與用戶提供的fn相同
if(cb === fn || cb.fn === fn ){
//做些什麼
}
七、vm.$emit
vm.$emit(event,[...args])
(1)參數
- { string } event
- [ args ]
(2)用法
觸發當前實例上的事件。附加參數都會傳給監聽器回調。
(3)實現思路
使用事件名從vm._events中取出對應的事件監聽器回調函數列表,然後依次執行列表中的監聽器回調並將參數傳遞給監聽器回調。
Vue.prototype.$emit = function(event,fn){
const vm = this;
let cbs = vm._events[event];
if(cbs){
const args = toArray(arguments,1);
for(let i =0,l = cbs.length; i<l i++){
try{
cbs[i].apply(vm,args);
}catch(e){
handleError(e,vm,'event handler for "${event}"')
}
}
}
return vm;
}
1、使用event從vm._events中取出事件監聽器回調函數列表,並將其賦值給變量cbs。
2、如果cbs存在,則循環它,依次調用每一個監聽器回調函數並將所有參數傳給監聽器回調函數。
3、toArray的作用是將類似於數組的數據轉換成真正的數組,它的第二個參數是起始位置。即args是一個數組,裏面包含除第一個參數之外的所有參數。
4、使用try。。catch語句來捕獲事件監聽器回調的錯誤。當監聽器回調發生錯誤時,會觸發handleError函數,在控制檯打印出錯誤提示。同時如果在Vue.config.errorHandler配置了錯誤處理函數,它將被觸發。