日拱一卒。
前情回顧上篇文章大致分析了Vue 的init方法,大致的流程是,構造函數接收options,然後構造函數中調用this._init(options),之後通過initMixin(Vue)方法,在構造函數的原型上添加_init(options)方法。_init方法的主要功能是合併options,同時設置proxy。代碼框架如下:
// index.js
function Vue(options){
this._init(options)
// init相關
initMixin(vue)
// 事件相關
eventsMixin(Vue)
}
// init.js
export function initMixin(Vue){
Vue.prototype._init = function(options){
const vm = this;
// 合併options
if(options){
mergeOptions()
}
// 設置代理
initProxy(vm)
// 生命周期
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
...
// 最後調用$mount()
if(vm.$options.el){
vm.$mount(vm.$options.el)
}
}
}
initLifecycle(vm)initLifecycle(vm)定義在src/core/instance/lifecycle.js中。該方法似乎什麼也沒做,只是在Vue的私有屬性上添加了默認值。該方法代碼如下:
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}可以看出,該方法只是設置了vm的$parent,$root,$children,$refs,_watcher,__isMounted,_isDestroyed,_isbeingDestroyed的默認值。
重點內容執行完initLifecycle(vm)添加了屬性的默認值之後,接著執行了initEvent(vm)方法,然而我們先不考慮initEvent(vm),重點關注一下,文章開頭的eventsMixin(Vue)。
之前也知道vue的事件系統是基於發布訂閱模式,也知道發布訂閱模式的簡單實現如下:
let event = {
listener:[],
add:(name,fn)=>{
if( !this.listener[ name ] ){
this.listener[name] = []
}
this.listener[name].push(fn)
},
trigger:function (key)=>{
let fns = this.listener[key]
if(!fns || fns.length ==0){
return
}
for(let i = 0;i<fns.length;i++){
fns[i].call(this,arguments)
}
}
}大致流程就是用數組緩存,事件名及對應的函數,然後觸發事件時,遍歷緩存列表,執行對應的函數。
eventsMixin(Vue) 這個方法就是將上面說的流程,添加到了Vue的原型上面,對外暴露了幾個API,$on,$once,$off,#emit。
$on方法,過濾掉了鉤子函數中的方法。
$off方法,當事件是字符串時,將對應的回調設置為null,當為數組時,將對應的函數從緩存列表中刪除。
$emit就不用說了,從緩存列表中找到對應的函數去執行。
源碼如下:
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = 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)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
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
}
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
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]
if (!cbs) {
return vm
}
if (arguments.length === 1) {
vm._events[event] = null
return vm
}
if (fn) {
// 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
}
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++) {
try {
cbs[i].apply(vm, args)
} catch (e) {
handleError(e, vm, `event handler for "${event}"`)
}
}
}
return vm
}
}
今日總結開卷有益,書要多讀,細讀。勤於思考總結。
最後說兩句javascript基礎知識總結