深入淺出vue.js----實例方法與全局API的實現原理---數據、事件相關的實例方法

一、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配置了錯誤處理函數,它將被觸發。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章