摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之拼接綁定的事件今天我們
寫文章不容易,點個贊唄兄弟
專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧
研究基于 Vue版本 【2.5.17】
如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧
【Vue原理】Compile - 源碼版 之 generate 拼接綁定的事件
今天我們來探索事件的拼接啦,上一篇我們已經講過了generate 階段 節點數據的拼接
事件也屬于其中一部分內容,但是由于內容實在太多太多太多太多,所以打算多帶帶拿出來講啦
這就是 Compile 的最后一篇文章了!終于終于發完了....真的發惡心了,估計網上找不著我這么詳細的 compile 文章了(找到當我沒說)
本文章很長,一萬多字,但是其實都是代碼和例子,幫助我們理解的,沒有什么,一路看下來絕壁沒有壓力,源碼也是簡化過的
好吧,開始正文
從上篇文章我們知道了,節點數據拼接,調用的是 genData$2 方法
現在我們就來回顧下這個方法(只保留了 事件 相關)
function genData$2(el, state) { var data = "{"; .....已省略其他屬性拼接 // 事件 if(el.events) { data += genHandlers(el.events, false) + ","; } // 原生事件 if(el.nativeEvents) { data += genHandlers(el.nativeEvents, true) + ","; } data = data.replace(/,$/, "") + "}"; return data }
沒錯,事件,分為 組件原生事件 和 事件
組件原生事件顧名思義,就是給組件綁定的原生DOM事件,像這樣
事件而這個呢,包含范圍很廣,包括 組件上的自定義事件,以及 標簽上的原生事件,如下
他們調用的方法都是 genHandlers,都是把結果值直接拼接上 data ,那有什么不同呢?
不同就在于給 genHandlers 傳的最后一個參數,現在我們把視角切到 genHandlers 方法上
genHandlersfunction genHandlers( events, isNative ) { var res = isNative ? "nativeOn:{": "on:{"; var handler = events[name] for (var name in events) { res += ` ${name} : ${ genHandler(name,handler )} ,` } return res.slice(0, -1) + "}" }
這里非常明顯看到
當你傳入 isNative=true 的時候,該事件即為 組件原生DOM 事件,需要拼接在 nativeOn:{} 字符串上
否則,就是事件,拼接在 on:{} 字符串上
先給個簡單的例子來熟悉下
拼接后的字符串是這樣的
`_c("div", { on: { "click": aa } },[ _c("test", { on: { "test": cc }, nativeOn: { "input":$event=>{ return bb($event) } } }) ])`
好的,大家大概有個了解了,下面我們將會探索事件內部復雜的拼接了
因為需要涉及到各種許多的 modifiers
這次之前,先給大家介紹 Vue 內部已經設置了的按鍵值
按鍵內部配置Vue 把幾個常用的按鍵給配置了,當然了,內部設置的鍵名,你都是可以覆蓋配置的
很簡單,過下眼,沒啥好說的,不過下面會用到
記住,keyCodes 和 keyName 是 Vue 內部配置鍵盤的變量
// 鍵盤和 code 映射 var keyCodes = { esc: 27, tab: 9, enter: 13, space: 32, up: 38, left: 37, right: 39, down: 40, delete: [8, 46] }; // 鍵盤名映射 var keyNames = { esc: "Escape", tab: "Tab", enter: "Enter", space: " ", // IE11 使用沒有 Arrow 前綴的鍵名 up: ["Up", "ArrowUp"], left: ["Left", "ArrowLeft"], right: ["Right", "ArrowRight"], down: ["Down", "ArrowDown"], delete: ["Backspace", "Delete"] };
看完了 Vue 的按鍵配置,我們來看 Vue 內部配置的修飾符
修飾符內部配置相信大家在項目中,都有用到過修飾符吧,很方便對不對,就是下面這些 stop prevent,很方便對不對
簡直不要太方便,其實 Vue 也沒做什么太多東西,不過是幫我們自動組裝上了幾個語句
來看看 Vue 配置的修飾符
var modifierCode = { stop: "$event.stopPropagation();", prevent: "$event.preventDefault();", ctrl: genGuard("!$event.ctrlKey"), shift: genGuard("!$event.shiftKey"), alt: genGuard("!$event.altKey"), meta: genGuard("!$event.metaKey"), self: genGuard("$event.target !== $event.currentTarget"), left: genGuard(""button" in $event && $event.button !== 0"), middle: genGuard(""button" in $event && $event.button !== 1"), right: genGuard(""button" in $event && $event.button !== 2") };
像 stop,prevent 直接就可以拼接在回調中
比如你綁定的事件名是 aaa 吼,并且使用了修飾符 stop,就會這么拼接
"function($event ){ " + "$event.stopPropagation();" + " return "+ aaa +"($event);" + "}"
你肯定看到了其他修飾符使用了一個函數 genGuard ,馬上看下
var genGuard = function(condition) { return ` if ( ${ condition } ) return null ` }
這個函數炒雞簡單,就是給你的事件回調拼接執行條件(當你使用了相關修飾符)
比如使用了 ctrl ,那么就會拼接上
if( !$event.ctrlKey ) return null
也就是說,當你按的不是 ctrl 的時候,直接 return,不執行下面
在比如一個 middle,鼠標中間,會拼接上
if( ""button" in $event && $event.button !== 1" ) return null
當鼠標點擊的按鍵不是 1 的時候,直接 return,不執行下面(下面就會執行你綁定的方法)
鍵盤修飾符現在再來看看 Vue 是怎么給你拼接上按鍵的修飾符的吧,來看一個方法
這個方法,后面會用到,這里預言,記住哦
function genKeyFilter(keys) { var key = keys.map(genFilterCode).join("&&") return ` if( !("button" in $event) && ${ key } ) return null ` }
這個方法跟前面的方法,拼接的內容差不多,只是條件不一樣,條件是這樣
` if( !("button" in $event) && ${ key } ) return null `
這個 key 肯定是一個很重要的條件,怎么得到呢,兩個東西
keys,genFilterCode
現在一個個來說一下
1參數 keys是一個數組,保存的是你添加的按鍵修飾符,可以是數字,可以是鍵名,比如
于是 keys = [ "enter" , 86 ],其中 enter 就是 enter 鍵,Vue 內部定義過,86 就是 鍵名 v 的值
然后會遍歷 keys,逐個調用 genFilterCode 去處理每個鍵值
現在看下它的源碼
2出現的函數 genFilterCodefunction genFilterCode(key) { // 綁定的 modifier 是數字 var keyVal = parseInt(key); if (keyVal) { return "$event.keyCode!==" + keyVal } // 綁定的 modifier 是鍵名 var keyCode = keyCodes[key]; var keyName = keyNames[key]; return ` _k( $event.keyCode , ${ key } , ${ keyCode }, ${ $event.key } , ${ keyName } ) ` }如果參數 key 是數字
這就簡單了,直接 返回字符串
"$event.keyCode!==" + keyVal
于是回調就可以拼接上一個執行條件,比如key 是數字 86
if( !("button in $event) && $event.keyCode!== 86 ) return null
這句話的意思就是當你按下的鍵不是 86 的時候,就return,因為你監聽的是 86 嘛,按其他鍵肯定是不執行的啦
如果參數 key 是鍵名如果你的 key 是個名字,經過 parseInt 就變成 NAN,于是就走到下面一步了
哦吼,好像直接返回了呢,并且還從 keyName 和 keyCode 兩個對象中去獲取鍵值和鍵名
keyName 和 keyCode 前面已經列出來了,就是兩個大對象,忘記可以翻上去看,Vue 內部的變量,這里獲取會得到什么呢
比如你的鍵名是 enter, 獲取之后
那么 keyName = "Enter" , keyCode = 13
但是,這個鍵名不一定要存在 Vue 自己定義的 keyName 和 keyCode 中,所以你從這兩個變量中獲取也可能是 undefined
因為這個鍵名,你可以自定義
自定義鍵名具體可以看官方文檔
https://cn.vuejs.org/v2/api/#...
3genFilterCode 返回值我們現在來看看他的返回值
`_k( $event.keyCode , ${ key } , ${ keyCode }, $event.key , ${ keyName } )`
看著上面的字符串,我們一個個講
1 其中參數五個參數,key 就是你綁定的鍵名或鍵值,keyName 和 keyCode 上面講過了
剩下兩個參數
$event.keyCode / $event.key
$event 是事件對象,不用說了
$event.keyCode 是你按下的鍵的值
$event.key 是你按下的鍵的名
比如你按下 v,事件對象就會包括這個鍵的信息
2 方法 _k 是什么返回 _k 函數的話,比如按下 v,事件回調的執行條件就變成
if( !("button" in $event) && _k( $event.keyCode , "v" , undefined, $event.key , undefined ) ) return null
也就是說,每次按下鍵盤,都會去調用一遍 _k 去檢查按鍵
_k 的本體其實是函數 checkKeyCodes,源碼如下
function checkKeyCodes( eventKeyCode, key, keyCode, eventKeyName, keyName ) { // 比如 key 傳入的是自定義名字 aaaa // keyCode 從Vue 定義的 keyNames 獲取 aaaa 的實際數字 // keyName 從 Vue 定義的 keyCode 獲取 aaaa 的別名 // 并且以用戶定義的為準,可以覆蓋Vue 內部定義的 var mappedKeyCode = config.keyCodes[key] ||keyCode; // 該鍵只在 Vue 內部定義的 keyCode 中 if (keyName && eventKeyName && !config.keyCodes[key]) { return isKeyNotMatch(keyName, eventKeyName) } // 該鍵只在 用戶自定義配置的 keyCode 中 else if (mappedKeyCode) { return isKeyNotMatch(mappedKeyCode, eventKeyCode) } // 原始鍵名 else if (eventKeyName) { return hyphenate(eventKeyName) !== key } }
乍一看,好像很復雜?其實這個函數作用,就是檢查 key
比如你綁定 v,當你按下 v 的時候,這個函數就返回 false
如果按下其他鍵,則返回 true,回調執行條件為 真,所以就直接 return null,不會執行下面綁定的回調
番外:"button" in $event
當鼠標按下的時候,這個表達式就為 true
而鍵盤按下的時候,事件對象不存在 button 這個值
所以 【!"button" in $event】 表示按鍵時必須沒有鼠標按下
但是這個條件只有在【 非內置修飾符 】才會存在,就是說 ctrl 那些鍵是沒有這個條件的
所以你可以綁定 click + ctrl 事件,比如
這么綁定時候,直接點擊是無效的,必須按下 ctrl 再點擊
但是你不能綁定 click + 普通按鍵,比如 click+v
就算你這么綁定了,也是沒用的,直接點擊也會觸發 而不用 v
下面現在我們繼續分析上面的 checkKeyCodes
1 config.keyCodes這個就是你自己配置的鍵值鍵名表,下面是我的配置
2 函數 isKeyNotMatch檢查按下的鍵,是否【不和】 配置的鍵值對 匹配
function isKeyNotMatch( expect, actual ) { if (Array.isArray(expect)) { return expect.indexOf(actual) === -1 } else { return expect !== actual } }
注意里面的匹配符是 ===-1 和 !== ,意思就是不匹配才返回 true,匹配則返回 false
比如你按下的鍵是 enter,Vue 內部配置了 { enter:"Enter" }
而 參數 actual 是調用 isKeyNotMatch 傳入的事件對象獲取的鍵名,也是 "Enter"
于是調用這個函數
isKeyNotMatch( "Enter" , "Enter" )
匹配成功,返回 false,也就是 checkKeyCode(_k) 返回false,回調執行過濾條件為假,不會 return null,這樣才會執行下面
配置數組的話也是同理
3 出現的函數 hyphenatevar hyphenate = function(str) { return str.replace(/B([A-Z])/g, "-$1").toLowerCase() }
把駝峰改成 - 命名
Vue 官方文檔說了,不支持駝峰修飾符
4 checkKeyCodes 的 匹配順序1、先匹配 Vue 內部配置的鍵值對
但是要保證這個鍵不存在用戶重定義中,因為必須要用戶自定義為主
2、匹配用戶自定義的鍵值對
3、匹配原始鍵值對
也就是說,你直接寫鍵盤上的鍵名也是ok 的
像這樣,就綁定了 鍵 v 和 鍵 b
哎喲,我們已經講了這么多啊,下面準備開始我們的主菜了
在一開始的 genHandlers 中出現的女豬腳 genHandler,用于逐個處理 修飾符的 她
genHandler這個函數有點長,用于處理修飾符,但是其實內容并不復雜,邏輯也很清晰,但是第一眼肯定看得煩,雖然我已經極大簡化,你可以跳到后面的解析,對照著看一下
function genHandler(name,handler) { // 沒有綁定回調,返回一個空函數 if (!handler) { return "function(){}" } // 如果綁定的是數組,則逐個遞歸一遍 if (Array.isArray(handler)) { return "[" + handler.map(handler) => { return genHandler(name, handler); }).join(",") + "]" } // 開始解析單個回調 var isMethodPath = simplePathRE.test(handler.value); var isFunctionExpression = fnExpRE.test(handler.value); // 沒有 modifier if (!handler.modifiers) { // 是一個方法,或者有 function 關鍵字,可以直接作為回調 if (isMethodPath || isFunctionExpression) { return handler.value } // 內聯語句,需要包裹一層 return "function($event){" + handler.value + ";}" } else { var code = ""; // 保存按鍵修飾符 var genModifierCode = ""; // 保存內部修飾符 var keys = []; for (var key in handler.modifiers) { if (modifierCode[key]) { ....被抽出,放在后面 } // 精確系統修飾符 else if (key === "exact") { ....被抽出,放在后面 } // 普通按鍵 else { keys.push(key); } } // 開始拼接事件回調!!!! // 拼接 Vue 定義外的按鍵修飾符 if (keys.length) { code += genKeyFilter(keys); } // 把 prevent 和 stop 這樣的修飾符在 按鍵過濾之后執行 if (genModifierCode) { code += genModifierCode; } // 事件回調主體 var handlerCode = isMethodPath // 執行你綁定的函數 ? "return " + handler.value + "($event)" : ( isFunctionExpression // 如果回調包含 function 關鍵字,同樣執行這個函數 ? "return " + handler.value + "($event)" : handler.value; ) return ` function($event) { ${ code + handlerCode } } ` } }
下面開始一點點解析上面的代碼
1出現的正則// 包含function 關鍵字 var fnExpRE = /^([w$_]+|([^)]*?))s*=>|^functions*(/; // 普通方法名 var simplePathRE = /^[A-Za-z_$][w$]*(?:.[A-Za-z_$][w$]*|["[^"]*?"]|["[^"]*?"]|[d+]|[[A-Za-z_$][w$]*])*$/;
不解釋這么多,直接看例子
fnExpRE 專門匹配有函數體的
simplePathRE 專門匹配函數名
所以其中的 isMethodPath 表示事件綁定的值 是否是函數名
isFunctionExpression 表示綁定值是否是 函數體
2綁定的 handler.modifiers在 parse 階段,會把綁定的事件解析過,所有的 modifiers 都解析成一個對象
比如
所以 handler.modifiers 可以直接遍歷而取到每一個 修飾符
3 收集內部修飾符這是被抽取的代碼
if (modifierCode[key]) { // 拼接內部的的事件修飾符 genModifierCode += modifierCode[key]; }
先判斷這個修飾符是否存在 Vue 內部的修飾符中
關于 modifierCode 可以看上面,已經記錄過啦
比如吼
那么拼接修飾符吼,genModifierCode 就是
` $event.stopPropagation(); if( "button" in $event && $event.button !== 1" ) return null `4收集系統修飾鍵 exact
或許你不知道什么是系統修飾鍵,我一開始也不知道,看了源碼才知道有這個東西,如下的 exact
官方文檔地址:https://cn.vuejs.org/v2/guide...
在我的理解是,取消組合鍵的意思
比如你綁定了 按鍵 v,觸發的條件有幾個?
1、直接 按 v 鍵
2、ctrl + v 、 shift +v 、 alt +v 等等組合鍵
如果你添加了 exact 這個修飾符的話
那么觸發條件,只有你只按下這個鍵的時候才會觸發,也就是說一起按其他鍵無效
來看下從 genHandler 抽出的源碼
else if (key === "exact") { var modifiers = handler.modifiers; genModifierCode += genGuard( ["ctrl", "shift", "alt", "meta"] // 過濾拿到沒寫 modifiers 的按鍵 .filter(keyModifier = >{ return ! modifiers[keyModifier]; }) .map(keyModifier = >{ return "$event." + keyModifier + "Key" }) .join("||") ); }
看源碼中,匹配的組合鍵有四個
ctrl , shift , alt ,meta
看源碼中,就是篩選出你沒有綁定的功能鍵
然后篩選出來的鍵被按下時,直接 return null
比如你綁定了 ctrl,并使用了 exact
那么只有你【只按下】ctrl 的時候,才會觸發回調
看下拼接結果
` _c("input", { on: { "keyup" $event =>{ if (!$event.ctrlKey) return null; if ( $event.shiftKey || $event.altKey || $event.metaKey) return null; return testMethod($event) } } }) `
1、沒有按下 ctrl 的時候
2、按下了 ctrl的時候,但是也按下了 shfit 等其他功能鍵
同理,對于其他普通鍵也是一樣的,當你不需要使用到組合鍵的時候,添加 exact 就好了
5收集其他按鍵else{ keys.push(key); }
這是最普通的,當你添加的修飾符,不在Vue內部定義的修飾符中,也沒有 exact
那么就直接數組收集你添加的按鍵
比如
那么 keys = [ "v" , "b" ,"c" ]
6拼接事件回調下面開始本函數最重要的部分了,打起精神嘞
拼接事件回調的三個重點
1、拼接按鍵修飾符
2、拼接內置修飾符
3、拼接事件回調
1 拼接按鍵修飾符if (keys.length) { code += genKeyFilter(keys); }
genKeyFilter 上面已經講過了,忘記的可以回頭看看,這里給個結果就好了
比如
那么 keys = [ "v" , "b" ,"c" ]
最后拼接得到的 code 是
` if( !("button" in $event)&& _k($event.keyCode,"v",undefined,$event.key,undefined)&& _k($event.keyCode,"b",undefined,$event.key,undefined)&& _k($event.keyCode,"c",undefined,$event.key,undefined) ) return null; `
調用了三個 _k 函數
2 拼接內部修飾符if (genModifierCode) { code += genModifierCode; }
genModifierCode 就是收集的 內部修飾符 和 帶有 exact 的修飾符,上面有說明
拼接結果是 code
` $event.stopPropagation(); if(!$event.shiftKey) return null; `3 拼接事件回調主體
這里開始涉及到你綁定的回調了,你可以先嘗試看下源碼
var handlerCode = isMethodPath // 執行你綁定的函數 ? "return " + handler.value + "($event)" : ( isFunctionExpression // 如果回調包含 function 關鍵字,同樣執行這個函數 ? "return (" + handler.value + "($event))" : handler.value; )
1、如果你綁定的是方法名
比如你綁定的方法名是
那么 handlerCode 是
"return aaa($event)"`
2、如果有函數體
比如
那么 handlerCode 是
"return (function(){}($event))"
3、如果只是內聯語句
比如
那么 handlerCode 是
aa = 33
直接把這條語句當做 回調的一部分就好了
走流程不知不覺,我們已經講了很多內容,最后我們用一個例子去玩一下流程
收集內置修飾符
1、拼接按鍵修飾符
2、拼接內置修飾符
3、拼接事件回調
4、包裝事件回調
比如下面的模板,我們來看看怎么拼接事件
1、開始遍歷修飾符 2、碰到 ctrl , 是內置修飾符,收集起來genModifierCode = ` if( !$event.ctrlKey ) return null `3、繼續遍歷,碰到 v,不是內置,收集到 keys
keys.push("v")4、遍歷結束,開始拼接按鍵修飾符
調用 genKeyFilter ,傳入 keys,得到 code
code = ` if( !("button" in $event)&& _k($event.keyCode,"v",undefined,$event.key,undefined) ) return null; `5、把內置修飾符放到 按鍵修飾符后面
code = code + genModifierCode6、開始拼接事件回調,是個方法名
handlerCode = ` return test($event) `7、ok,最后一步,開始組裝
` funciton($event){ if( !$event.ctrlKey ) return null; if( !("button" in $event)&& _k($event.keyCode,"v",undefined,$event.key,undefined) ) return null; return test($event) }`
然后事件拼接就完成了!!!!!!!!!!
喲,別忘了,我們是要拼接到 render,趕快往上翻到 genHandlers
上面講的只是事件回調,如果要拼接到 render 回調,我們還要做下操作
加上事件名,拼接 on 對象字符串里面,像這樣,具體看上面的 genHandlers
`,on:{ keyup: ...上面我們拼接的回調 } `
謝謝大家觀看,辛苦了
最后鑒于本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵,如果有任何描述不當的地方,歡迎后臺聯系本人,領取紅包
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/106725.html
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版之節點數據拼接上一篇我們 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究...
摘要:還原的難度就在于變成模板了,因為其他的什么等是原封不動的哈哈,可是直接照抄最后鑒于本人能力有限,難免會有疏漏錯誤的地方,請大家多多包涵,如果有任何描述不當的地方,歡迎后臺聯系本人,有重謝 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理白話版終于到了要講白話的時候了 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究...
摘要:一旦我們檢測到這些子樹,我們可以把它們變成常數,這樣我們就不需要了在每次重新渲染時為它們創建新的節點在修補過程中完全跳過它們。否則,吊裝費用將會增加好處大于好處,最好總是保持新鮮。 寫文章不容易,點個贊唄兄弟 專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,...
摘要:首先,兄弟,容我先說幾句涉及源碼很多,篇幅很長,我都已經分了上下三篇了,依然這么長,但是其實內容都差不多一樣,但是我還是毫無保留地給你了。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也...
閱讀 3212·2023-04-26 01:30
閱讀 675·2021-11-08 13:15
閱讀 1796·2021-09-24 10:35
閱讀 1009·2021-09-22 15:41
閱讀 1934·2019-08-30 15:44
閱讀 603·2019-08-30 13:22
閱讀 1013·2019-08-30 13:06
閱讀 1203·2019-08-29 13:22