這篇文章主要介紹了vue中事件機(jī)制原理的示例分析,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
創(chuàng)新互聯(lián)網(wǎng)站建設(shè)由有經(jīng)驗(yàn)的網(wǎng)站設(shè)計(jì)師、開(kāi)發(fā)人員和項(xiàng)目經(jīng)理組成的專業(yè)建站團(tuán)隊(duì),負(fù)責(zé)網(wǎng)站視覺(jué)設(shè)計(jì)、用戶體驗(yàn)優(yōu)化、交互設(shè)計(jì)和前端開(kāi)發(fā)等方面的工作,以確保網(wǎng)站外觀精美、成都網(wǎng)站設(shè)計(jì)、做網(wǎng)站易于使用并且具有良好的響應(yīng)性。
常見(jiàn)的事件機(jī)制demo都會(huì)包含在這段代碼中:
click1click2
上面的demo中一共有四個(gè)事件。基本涵蓋了vue中最經(jīng)典的事件的四種情況
普通html元素上的事件
好吧。想想我們還是一個(gè)個(gè)來(lái)看。如果懂vue組件相關(guān)的機(jī)制會(huì)更容易懂。那么首先我們看看最簡(jiǎn)單的第一、二個(gè)(兩個(gè)事件只差了個(gè)修飾符):
click1
這是簡(jiǎn)單到不能在簡(jiǎn)單的一個(gè)點(diǎn)擊事件。
我們來(lái)看看建立這么一個(gè)簡(jiǎn)單的點(diǎn)擊事件,vue中發(fā)生了什么。
1:new Vue()中調(diào)用了initState(vue):看代碼
function initState (vm) { vm._watchers = []; var opts = vm.$options; if (opts.props) { initProps(vm, opts.props); } if (opts.methods) { initMethods(vm, opts.methods); }//初始化事件 if (opts.data) { initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } if (opts.computed) { initComputed(vm, opts.computed); } if (opts.watch) { initWatch(vm, opts.watch); } } //接著看看initMethods function initMethods (vm, methods) { var props = vm.$options.props; for (var key in methods) { vm[key] = methods[key] == null ? noop : bind(methods[key], vm);//調(diào)用了bind方法,我們?cè)倏纯碽ind { if (methods[key] == null) { warn( "method \"" + key + "\" has an undefined value in the component definition. " + "Did you reference the function correctly?", vm ); } if (props && hasOwn(props, key)) { warn( ("method \"" + key + "\" has already been defined as a prop."), vm ); } } } } //我們接著看看bind function bind (fn, ctx) { function boundFn (a) { var l = arguments.length; return l ? l > 1 ? fn.apply(ctx, arguments)//通過(guò)返回函數(shù)修飾了事件的回調(diào)函數(shù)。綁定了事件回調(diào)函數(shù)的this。并且讓參數(shù)自定義。更加的靈活 : fn.call(ctx, a) : fn.call(ctx) } // record original fn length boundFn._length = fn.length; return boundFn }
總的來(lái)說(shuō)。vue初始化的時(shí)候,將method中的方法代理到vue[key]的同時(shí)修飾了事件的回調(diào)函數(shù)。綁定了作用域。
2:vue進(jìn)入compile環(huán)節(jié)需要將該div變成ast(抽象語(yǔ)法樹(shù))。當(dāng)編譯到該div時(shí)經(jīng)過(guò)核心函數(shù)genHandler:
function genHandler ( name, handler ) { if (!handler) { return 'function(){}' } if (Array.isArray(handler)) { return ("[" + (handler.map(function (handler) { return genHandler(name, handler); }).join(',')) + "]") } var isMethodPath = simplePathRE.test(handler.value); var isFunctionExpression = fnExpRE.test(handler.value); if (!handler.modifiers) { return isMethodPath || isFunctionExpression//假如沒(méi)有修飾符。直接返回回調(diào)函數(shù) ? handler.value : ("function($event){" + (handler.value) + "}") // inline statement } else { var code = ''; var genModifierCode = ''; var keys = []; for (var key in handler.modifiers) { if (modifierCode[key]) { genModifierCode += modifierCode[key];//處理修飾符數(shù)組,例如.stop就在回調(diào)函數(shù)里加入event.stopPropagation()再返回。實(shí)現(xiàn)修飾的目的 // left/right if (keyCodes[key]) { keys.push(key); } } else { keys.push(key); } } if (keys.length) { code += genKeyFilter(keys); } // Make sure modifiers like prevent and stop get executed after key filtering if (genModifierCode) { code += genModifierCode; } var handlerCode = isMethodPath ? handler.value + '($event)' : isFunctionExpression ? ("(" + (handler.value) + ")($event)") : handler.value; return ("function($event){" + code + handlerCode + "}") } }
genHandler函數(shù)簡(jiǎn)單明了,如果事件函數(shù)有修飾符。就處理完修飾符,添加修飾符對(duì)應(yīng)的函數(shù)語(yǔ)句。再返回。這個(gè)過(guò)程還會(huì)單獨(dú)對(duì)native修飾符做特殊處理。這個(gè)等會(huì)說(shuō)。compile完后自然就render。我們看看render函數(shù)中這塊區(qū)域長(zhǎng)什么樣子:
復(fù)制代碼 代碼如下:
_c('div',{attrs:{"id":"test1"},on:{"click":click1}},[_v("click1")]),_v(" "),_c('div',{attrs:{"id":"test2"},on:{"click":function($event){$event.stopPropagation();click2($event)}}}
一目了然。最后在虛擬dom-》真實(shí)dom的時(shí)候。會(huì)調(diào)用核心函數(shù):
function add$1 ( event, handler, once$$1, capture, passive ) { if (once$$1) { var oldHandler = handler; var _target = target$1; // save current target element in closure handler = function (ev) { var res = arguments.length === 1 ? oldHandler(ev) : oldHandler.apply(null, arguments); if (res !== null) { remove$2(event, handler, capture, _target); } }; } target$1.addEventListener( event, handler, supportsPassive ? { capture: capture, passive: passive }//此處綁定點(diǎn)擊事件 : capture ); }
組件上的事件
好了下面就是接下來(lái)的組件上的點(diǎn)擊事件了??梢灶A(yù)感到他走的和普通的html元素應(yīng)該是不同的道路。事實(shí)也是如此:
最簡(jiǎn)單的一個(gè)例子。兩個(gè)事件的區(qū)別就是一個(gè)有.native的修飾符。我們來(lái)看看官方.native的作用:在原生dom上綁定事件。好吧。很簡(jiǎn)單。我們跟隨源碼看看有何不同。這里可以往回看看我少的可憐的上一章組件機(jī)制。vue中的組件都是擴(kuò)展的vue的一個(gè)新實(shí)例。在compile結(jié)束的時(shí)候你還是可以發(fā)現(xiàn)他也是類似的一個(gè)樣子。如下圖:
復(fù)制代碼 代碼如下:
_c('my-component',{on:{"componenton":parentOn},nativeOn:{"click":function($event){nativeclick($event)}}
可以看到加了.native修飾符的會(huì)被放入nativeOn的數(shù)組中。等待后續(xù)特殊處理。等不及了。我們直接來(lái)看看特殊處理。render函數(shù)在執(zhí)行時(shí)。如果遇到組件。看過(guò)上一章的可以知道。會(huì)執(zhí)行
function createComponent ( Ctor, data, context, children, tag ) { if (isUndef(Ctor)) { return } var baseCtor = context.$options._base; // plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor); } // if at this stage it's not a constructor or an async component factory, // reject. if (typeof Ctor !== 'function') { { warn(("Invalid Component definition: " + (String(Ctor))), context); } return } // async component if (isUndef(Ctor.cid)) { Ctor = resolveAsyncComponent(Ctor, baseCtor, context); if (Ctor === undefined) { // return nothing if this is indeed an async component // wait for the callback to trigger parent update. return } } // resolve constructor options in case global mixins are applied after // component constructor creation resolveConstructorOptions(Ctor); data = data || {}; // transform component v-model data into props & events if (isDef(data.model)) { transformModel(Ctor.options, data); } // extract props var propsData = extractPropsFromVNodeData(data, Ctor, tag); // functional component if (isTrue(Ctor.options.functional)) { return createFunctionalComponent(Ctor, propsData, data, context, children) } // extract listeners, since these needs to be treated as // child component listeners instead of DOM listeners var listeners = data.on;//listeners緩存data.on的函數(shù)。這里就是componenton事件 // replace with listeners with .native modifier data.on = data.nativeOn;//正常的data.on會(huì)被native修飾符的事件所替換 if (isTrue(Ctor.options.abstract)) { // abstract components do not keep anything // other than props & listeners data = {}; } // merge component management hooks onto the placeholder node mergeHooks(data); // return a placeholder vnode var name = Ctor.options.name || tag; var vnode = new VNode( ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')), data, undefined, undefined, undefined, context, { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children } ); return vnode }
整段代碼關(guān)于事件核心操作:
var listeners = data.on;//listeners緩存data.on的函數(shù)。這里就是componenton事件 // replace with listeners with .native modifier data.on = data.nativeOn;//正常的data.on會(huì)被native修飾符的事件所替換
經(jīng)過(guò)這兩句話。.native修飾符的事件會(huì)被放在data.on上面。接下來(lái)data.on上的事件(這里就是nativeclick)會(huì)按普通的html事件往下走。最后執(zhí)行target.add('',''')掛上原生的事件。而先前的data.on上的被緩存在listeneners的事件就沒(méi)著么愉快了。接下來(lái)他會(huì)在組件init的時(shí)候。它會(huì)進(jìn)入一下分支:
function initEvents (vm) { vm._events = Object.create(null); vm._hasHookEvent = false; // init parent attached events var listeners = vm.$options._parentListeners; if (listeners) { updateComponentListeners(vm, listeners); } } function updateComponentListeners ( vm, listeners, oldListeners ) { target = vm; updateListeners(listeners, oldListeners || {}, add, remove$1, vm); } function add (event, fn, once$$1) { if (once$$1) { target.$once(event, fn); } else { target.$on(event, fn); } }
發(fā)現(xiàn)組件上的沒(méi)有.native的修飾符調(diào)用的是$on方法。這個(gè)好熟悉。進(jìn)入到$on,$emit大致想到是一個(gè)典型的觀察者模式的事件??纯聪嚓P(guān)$on,$emit代碼。我加點(diǎn)注解:
Vue.prototype.$on = function (event, fn) { var this$1 = this; var vm = this; if (Array.isArray(event)) { for (var i = 0, l = event.length; i < l; i++) { this$1.$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.$emit = function (event) { var vm = this; console.log(vm); { var 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 + "\"." ); } } var cbs = vm._events[event]; console.log(cbs); if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs; var args = toArray(arguments, 1); for (var i = 0, l = cbs.length; i < l; i++) { cbs[i].apply(vm, args);//當(dāng)emit的時(shí)候調(diào)用該事件。注意上面說(shuō)的vue在初始化的守候。用bind修飾了事件函數(shù)。所以組件上掛載的事件都是在父作用域中的 } } return vm };
看了上面的on,emit用法下面這個(gè)demo也就瞬間秒解了(一個(gè)經(jīng)常用的非父子組件通信):
var bus = new Vue() // 觸發(fā)組件 A 中的事件 bus.$emit('id-selected', 1) // 在組件 B 創(chuàng)建的鉤子中監(jiān)聽(tīng)事件 bus.$on('id-selected', function (id) { // ... })
是不是豁然開(kāi)朗。
又到了愉快的總結(jié)時(shí)間了。segementfault的編輯器真難用。內(nèi)容多就卡。哎。煩。卡的時(shí)間夠看好多肥皂劇了。
總的來(lái)說(shuō)。vue對(duì)于事件有兩個(gè)底層的處理邏輯。
1:普通html元素和在組件上掛了.native修飾符的事件。最終EventTarget.addEventListener() 掛載事件
2:組件上的,vue實(shí)例上的事件會(huì)調(diào)用原型上的$on,$emit(包括一些其他api $off,$once等等)
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“vue中事件機(jī)制原理的示例分析”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!
網(wǎng)頁(yè)題目:vue中事件機(jī)制原理的示例分析
文章鏈接:http://weahome.cn/article/gghcdd.html