国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

vue源碼解析之事件機制原理

2019-11-19 13:59:13
字體:
來源:轉載
供稿:網友

上一章沒什么經驗。直接寫了組件機制。感覺涉及到的東西非常的多,不是很方便講。今天看了下vue的關于事件的機制。有一些些體會。寫出來。大家一起糾正,分享。源碼都是基于最新的Vue.js v2.3.0。下面我們來看看vue中的事件機制:
老樣子還是先上一段貫穿全局的代碼,常見的事件機制demo都會包含在這段代碼中:

<div id="app"> <div id="test1" @click="click1">click1</div> <div id="test2" @click.stop="click2">click2</div> <my-component v-on:click.native="nativeclick" v-on:componenton="parentOn"> </my-component></div></body><script src="vue.js"></script><script type="text/javascript">var Child = { template: '<div>A custom component!</div>'} Vue.component('my-component', { name: 'my-component', template: '<div>A custom component!<div @click.stop="toParent">test click</div></div>', 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中一共有四個事件。基本涵蓋了vue中最經典的事件的四種情況

普通html元素上的事件

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

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

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

我們來看看建立這么一個簡單的點擊事件,vue中發生了什么。

1:new Vue()中調用了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); }}//接著看看initMethodsfunction initMethods (vm, methods) { var props = vm.$options.props; for (var key in methods) { vm[key] = methods[key] == null ? noop : bind(methods[key], vm);//調用了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  );  } } }}//我們接著看看bindfunction bind (fn, ctx) { function boundFn (a) { var l = arguments.length; return l  ? l > 1  ? fn.apply(ctx, arguments)//通過返回函數修飾了事件的回調函數。綁定了事件回調函數的this。并且讓參數自定義。更加的靈活  : fn.call(ctx, a)  : fn.call(ctx) } // record original fn length boundFn._length = fn.length; return boundFn}

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

2:vue進入compile環節需要將該div變成ast(抽象語法樹)。當編譯到該div時經過核心函數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//假如沒有修飾符。直接返回回調函數  ? 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];//處理修飾符數組,例如.stop就在回調函數里加入event.stopPropagation()再返回。實現修飾的目的  // 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函數簡單明了,如果事件函數有修飾符。就處理完修飾符,添加修飾符對應的函數語句。再返回。這個過程還會單獨對native修飾符做特殊處理。這個等會說。compile完后自然就render。我們看看render函數中這塊區域長什么樣子:

復制代碼 代碼如下:

_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-》真實dom的時候。會調用核心函數:

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 );}

組件上的事件

好了下面就是接下來的組件上的點擊事件了。可以預感到他走的和普通的html元素應該是不同的道路。事實也是如此:

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

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

復制代碼 代碼如下:
_c('my-component',{on:{"componenton":parentOn},nativeOn:{"click":function($event){nativeclick($event)}}

可以看到加了.native修飾符的會被放入nativeOn的數組中。等待后續特殊處理。等不及了。我們直接來看看特殊處理。render函數在執行時。如果遇到組件。看過上一章的可以知道。會執行

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的函數。這里就是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}

整段代碼關于事件核心操作:

var listeners = data.on;//listeners緩存data.on的函數。這里就是componenton事件// replace with listeners with .native modifierdata.on = data.nativeOn;//正常的data.on會被native修飾符的事件所替換

經過這兩句話。.native修飾符的事件會被放在data.on上面。接下來data.on上的事件(這里就是nativeclick)會按普通的html事件往下走。最后執行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); }}

發現組件上的沒有.native的修飾符調用的是$on方法。這個好熟悉。進入到$on,$emit大致想到是一個典型的觀察者模式的事件。看看相關$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);//當emit的時候調用該事件。注意上面說的vue在初始化的守候。用bind修飾了事件函數。所以組件上掛載的事件都是在父作用域中的  } } return vm };

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

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

是不是豁然開朗。

又到了愉快的總結時間了。segementfault的編輯器真難用。內容多就卡。哎。煩。卡的時間夠看好多肥皂劇了。

總的來說。vue對于事件有兩個底層的處理邏輯。

1:普通html元素和在組件上掛了.native修飾符的事件。最終EventTarget.addEventListener() 掛載事件

2:組件上的,vue實例上的事件會調用原型上的$on,$emit(包括一些其他api $off,$once等等)

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 广河县| 门源| 肇源县| 怀宁县| 锡林浩特市| 西充县| 和田市| 望都县| 烟台市| 东安县| 锡林浩特市| 柳河县| 教育| 太白县| 浦城县| 六盘水市| 石棉县| 西吉县| 西乌珠穆沁旗| 鸡泽县| 左云县| 湖北省| 巴林左旗| 潜山县| 通州区| 定边县| 中宁县| 灌阳县| 翁牛特旗| 叶城县| 永新县| 清河县| 虞城县| 正镶白旗| 丁青县| 佳木斯市| 华池县| 渭源县| 永仁县| 津市市| 琼海市|