寫在前面
因爲對Vue.js很感興趣,而且平時工作的技術棧也是Vue.js,這幾個月花了些時間研究學習了一下Vue.js源碼,並做了總結與輸出。 文章的原地址:https://github.com/answershuto/learnVue。 在學習過程中,爲Vue加上了中文的註釋https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以對其他想學習Vue源碼的小夥伴有所幫助。 可能會有理解存在偏差的地方,歡迎提issue指出,共同學習,共同進步。
Vue事件API
衆所周知,Vue.js爲我們提供了四個事件API,分別是$on,$once,$off,$emit。
初始化事件
初始化事件在vm上創建一個_events對象,用來存放事件。_events的內容如下:
{
eventName: [func1, func2, func3]
}
存放事件名以及對應執行方法。
/*初始化事件*/
export function initEvents (vm: Component) {
/*在vm上創建一個_events對象,用來存放事件。*/
vm._events = Object.create(null)
/*這個bool標誌位來表明是否存在鉤子,而不需要通過哈希表的方法來查找是否有鉤子,這樣做可以減少不必要的開銷,優化性能。*/
vm._hasHookEvent = false
// init parent attached events
/*初始化父組件attach的事件*/
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
$on
$on方法用來在vm實例上監聽一個自定義事件,該事件可用$emit觸發。
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
/*如果是數組的時候,則遞歸$on,爲每一個成員都綁定上方法*/
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)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
/*這裏在註冊事件的時候標記bool值也就是個標誌位來表明存在鉤子,而不需要通過哈希表的方法來查找是否有鉤子,這樣做可以減少不必要的開銷,優化性能。*/
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
$once
$once監聽一個只能觸發一次的事件,在觸發以後會自動移除該事件。
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
/*在第一次執行的時候將該事件銷燬*/
vm.$off(event, on)
/*執行註冊的方法*/
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
$off
$off用來移除自定義事件
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
/*如果不傳參數則註銷所有事件*/
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
/*如果event是數組則遞歸註銷事件*/
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$off(event[i], fn)
}
return vm
}
// specific event
const cbs = vm._events[event]
/*Github:https://github.com/answershuto*/
/*本身不存在該事件則直接返回*/
if (!cbs) {
return vm
}
/*如果只傳了event參數則註銷該event方法下的所有方法*/
if (arguments.length === 1) {
vm._events[event] = null
return vm
}
// specific handler
/*遍歷尋找對應方法並刪除*/
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
$emit
$emit用來觸發指定的自定義事件。
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (process.env.NODE_ENV !== 'production') {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}".`
)
}
}
let cbs = vm._events[event]
if (cbs) {
/*將類數組的對象轉換成數組*/
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
/*遍歷執行*/
for (let i = 0, l = cbs.length; i < l; i++) {
cbs[i].apply(vm, args)
}
}
return vm
}
關於
作者:染陌
Email:[email protected] or [email protected]
Github: https://github.com/answershuto
Blog:http://answershuto.github.io/
知乎主頁:https://www.zhihu.com/people/cao-yang-49/activities
知乎專欄:https://zhuanlan.zhihu.com/ranmo
掘金: https://juejin.im/user/58f87ae844d9040069ca7507
osChina:https://my.oschina.net/u/3161824/blog
轉載請註明出處,謝謝。
歡迎關注我的公衆號