最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
當(dāng)前位置: 首頁 - 科技 - 知識百科 - 正文

vue底層事件邏輯解析

來源:懂視網(wǎng) 責(zé)編:小采 時間:2020-11-27 19:40:10
文檔

vue底層事件邏輯解析

vue底層事件邏輯解析:這次給大家?guī)韛ue底層事件邏輯解析,使用vue底層事件的注意事項有哪些,下面就是實戰(zhàn)案例,一起來看一下。<p id="app"> <p id="test1" @click="click1">click1</p> &
推薦度:
導(dǎo)讀vue底層事件邏輯解析:這次給大家?guī)韛ue底層事件邏輯解析,使用vue底層事件的注意事項有哪些,下面就是實戰(zhàn)案例,一起來看一下。<p id="app"> <p id="test1" @click="click1">click1</p> &

這次給大家?guī)韛ue底層事件邏輯解析,使用vue底層事件的注意事項有哪些,下面就是實戰(zhàn)案例,一起來看一下。

<p id="app">
 <p id="test1" @click="click1">click1</p>
 <p id="test2" @click.stop="click2">click2</p>
 <my-component v-on:click.native="nativeclick" v-on:componenton="parentOn">
 </my-component>
</p>
</body>
<script src="vue.js"></script>
<script type="text/javascript">
var Child = {
 template: '<p>A custom component!</p>'
} 
Vue.component('my-component', {
 name: 'my-component',
 template: '<p>A custom component!<p @click.stop="toParent">test click</p></p>',
 components: {
 Child:Child
 },
 created(){
 console.log(this);
 },
 methods: {
 toParent(){
 this.$emit('componenton','toParent')
 }
 },
 mounted(){
 console.log(this);
 }
})
 new Vue({
 el: '#app',
 data: function () {
 return {
 heihei:{name:3333},
 a:1
 }
 },
 components: {
 Child:Child
 },
 methods: {
 click1(){
 alert('click1')
 },
 click2(){
 alert('click2')
 },
 nativeclick(){
 alert('nativeclick')
 },
 parentOn(value){
 alert(value)
 }
 }
})
</script>

上面的demo中一共有四個事件?;竞w了vue中最經(jīng)典的事件的四種情況

普通html元素上的事件

好吧。想想我們還是一個個來看。如果懂vue組件相關(guān)的機制會更容易懂。那么首先我們看看最簡單的第一、二個(兩個事件只差了個修飾符):

<p id="test1" @click="click1">click1</p>

這是簡單到不能在簡單的一個點擊事件。

我們來看看建立這么一個簡單的點擊事件,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方法,我們再看看bind
 {
 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)//通過返回函數(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
}

總的來說。vue初始化的時候,將method中的方法代理到vue[key]的同時修飾了事件的回調(diào)函數(shù)。綁定了作用域。

2:vue進入compile環(huán)節(jié)需要將該p變成ast(抽象語法樹)。當(dāng)編譯到該p時經(jīng)過核心函數(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//假如沒有修飾符。直接返回回調(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()再返回。實現(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ù)簡單明了,如果事件函數(shù)有修飾符。就處理完修飾符,添加修飾符對應(yīng)的函數(shù)語句。再返回。這個過程還會單獨對native修飾符做特殊處理。這個等會說。compile完后自然就render。我們看看render函數(shù)中這塊區(qū)域長什么樣子:

代碼如下:


_c('p',{attrs:{"id":"test1"},on:{"click":click1}},[_v("click1")]),_v(" "),_c('p',{attrs:{"id":"test2"},on:{"click":function($event){$event.stopPropagation();click2($event)}}}

一目了然。最后在虛擬dom-》真實dom的時候。會調(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 }//此處綁定點擊事件
 : capture
 );
}

組件上的事件

好了下面就是接下來的組件上的點擊事件了??梢灶A(yù)感到他走的和普通的html元素應(yīng)該是不同的道路。事實也是如此:

<my-component v-on:click.native="nativeclick" v-on:componenton="parentOn">
 </my-component>

最簡單的一個例子。兩個事件的區(qū)別就是一個有.native的修飾符。我們來看看官方.native的作用:在原生dom上綁定事件。好吧。很簡單。我們跟隨源碼看看有何不同。這里可以往回看看我少的可憐的上一章組件機制。vue中的組件都是擴展的vue的一個新實例。在compile結(jié)束的時候你還是可以發(fā)現(xiàn)他也是類似的一個樣子。如下圖:

代碼如下:

_c('my-component',{on:{"componenton":parentOn},nativeOn:{"click":function($event){nativeclick($event)}}

可以看到加了.native修飾符的會被放入nativeOn的數(shù)組中。等待后續(xù)特殊處理。等不及了。我們直接來看看特殊處理。render函數(shù)在執(zhí)行時。如果遇到組件??催^上一章的可以知道。會執(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會被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會被native修飾符的事件所替換

經(jīng)過這兩句話。.native修飾符的事件會被放在data.on上面。接下來data.on上的事件(這里就是nativeclick)會按普通的html事件往下走。最后執(zhí)行target.add('',''')掛上原生的事件。而先前的data.on上的被緩存在listeneners的事件就沒著么愉快了。接下來他會在組件init的時候。它會進入一下分支:

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)組件上的沒有.native的修飾符調(diào)用的是$on方法。這個好熟悉。進入到$on,$emit大致想到是一個典型的觀察者模式的事件??纯聪嚓P(guān)$on,$emit代碼。我加點注解:

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的時候調(diào)用該事件。注意上面說的vue在初始化的守候。用bind修飾了事件函數(shù)。所以組件上掛載的事件都是在父作用域中的
 }
 }
 return vm
 };

看了上面的on,emit用法下面這個demo也就瞬間秒解了(一個經(jīng)常用的非父子組件通信):

var bus = new Vue()
// 觸發(fā)組件 A 中的事件
bus.$emit('id-selected', 1)
// 在組件 B 創(chuàng)建的鉤子中監(jiān)聽事件
bus.$on('id-selected', function (id) {
 // ...
})

是不是豁然開朗。

相信看了本文案例你已經(jīng)掌握了方法,更多精彩請關(guān)注Gxl網(wǎng)其它相關(guān)文章!

推薦閱讀:

Vue做出分頁器

怎樣使用nodeJS模塊

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

文檔

vue底層事件邏輯解析

vue底層事件邏輯解析:這次給大家?guī)韛ue底層事件邏輯解析,使用vue底層事件的注意事項有哪些,下面就是實戰(zhàn)案例,一起來看一下。<p id="app"> <p id="test1" @click="click1">click1</p> &
推薦度:
標(biāo)簽: VUE 事件 解析
  • 熱門焦點

最新推薦

猜你喜歡

熱門推薦

專題
Top