• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題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關鍵字專題關鍵字專題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
    當前位置: 首頁 - 科技 - 知識百科 - 正文

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

    來源:懂視網 責編:小采 時間:2020-11-27 22:16:17
    文檔

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

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

    上一章沒什么經驗。直接寫了組件機制。感覺涉及到的東西非常的多,不是很方便講。今天看了下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); }
    }
    
    //接著看看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);//調用了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)//通過返回函數修飾了事件的回調函數。綁定了事件回調函數的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 modifier
    data.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等等)

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

    文檔

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

    vue源碼解析之事件機制原理:上一章沒什么經驗。直接寫了組件機制。感覺涉及到的東西非常的多,不是很方便講。今天看了下vue的關于事件的機制。有一些些體會。寫出來。大家一起糾正,分享。源碼都是基于最新的Vue.js v2.3.0。下面我們來看看vue中的事件機制: 老樣子還是先上一段貫穿全局
    推薦度:
    標簽: 原理 VUE 事件
    • 熱門焦點

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 亚洲精品老司机在线观看| 国产欧美日韩精品a在线观看| 真实国产乱子伦精品视频| 99久久精品国产一区二区| 国产精品国产三级国产av品爱网| 久久久久久噜噜精品免费直播| 亚洲国产精品自在在线观看| 国产日产韩国精品视频| 亚洲国产精品久久久天堂| 久久精品国产99久久香蕉| 99国内精品久久久久久久| 国产精品欧美亚洲韩国日本| 久久久久人妻一区精品色| 亚洲情侣偷拍精品| 无码国模国产在线无码精品国产自在久国产 | 欧美精品在线免费| 国产精品激情综合久久| 91精品国产自产在线老师啪| 久久精品九九亚洲精品天堂 | 国产综合免费精品久久久| 91人前露出精品国产| 8050免费午夜一级国产精品| 久久久精品一区二区三区| 久久亚洲欧美日本精品| 国产原创精品视频| 久久国产精品一区二区| 久久国产精品久久| 亚洲国产精品一区二区久久| 99在线精品免费视频| 国产精品被窝福利一区| 国产微拍精品一区二区| 久久精品国产亚洲av瑜伽| 欧美日韩国产精品 | 国产vA免费精品高清在线观看| 杨幂国产精品福利在线观看| 91精品在线国产| 国产成人精品久久一区二区三区av| 国产99视频精品专区| 国产精品自在在线午夜福利| 精品人妻无码专区中文字幕| 欧美精品三区|