JavaScript逆向Vue处理事件和捕获错误的核心逻辑
Vue处理事件和捕获错误的核心逻辑
- 代码
- 解释
- 函数 at: 事件调用器工厂
- 逻辑分解
- 小结
- 函数 Ue: 错误处理执行器
- 逻辑分解
- 小结
- 它们如何协同工作
代码
在逆向一个Vue框架的前端代码时,发现一段反复出现的代码逻辑,这里做一个探究,我们直接上代码!
function Ue(e, t, n, r, o) {var i;try {(i = n ? e.apply(t, n) : e.call(t)) && !i._isVue && f(i) && !i._handled && (i.catch((function (e) {return Ve(e, r, o + " (Promise/async)")})),i._handled = !0)} catch (e) {Ve(e, r, o)}return i
}function at(e, t) {function n() {var e = arguments, r = n.fns;if (!Array.isArray(r))return Ue(r, null, arguments, t, "v-on handler");for (var o = r.slice(), i = 0; i < o.length; i++)Ue(o[i], null, e, t, "v-on handler")}return n.fns = e,n
}
解释
这段代码是 Vue.js 框架(很可能是 Vue 2)内部用于处理事件和捕获错误的核心逻辑。
简而言之,at 函数创建了一个“事件调用器”,而 Ue 函数是这个调用器用来安全执行具体事件处理函数(并捕获任何错误)的工具。
函数 at: 事件调用器工厂
这个函数是一个工厂函数,它的作用是创建一个用于 v-on 事件的包装函数(即“调用器”)。这个包装函数非常巧妙,它允许 Vue 动态地添加、删除或替换事件处理器,而无需操作真实的 DOM 事件监听器。
- function at(e, t)
e: 真正的事件处理器。这可以是一个函数,也可以是一个函数数组(例如v-on:click="[handler1, handler2]")。t: 相关的 Vue 实例 (vm),会传递给Ue。
逻辑分解
function n() { ... }:- 定义了一个新函数
n(我们称之为invoker或 “调用器”)。 - 这个
invoker函数是at最终返回的函数。它就是最终被绑定到 DOM 元素上的那个事件监听器。
- 定义了一个新函数
var e = arguments: 当invoker被(例如点击事件)调用时,e会捕获所有的事件参数(比如event对象)。var r = n.fns:- 这是最关键的部分。
invoker会从它自己的一个属性.fns上读取真正要执行的处理器。 - 这个
.fns属性是在函数返回前被设置的。
- 这是最关键的部分。
if (!Array.isArray(r)):- 检查
n.fns(即r) 是不是一个数组。 - 如果不是数组(即只是一个单独的函数),就调用
Ue(错误处理执行器) 来执行这一个函数。
- 检查
for (...):- 如果
n.fns是一个数组,它会遍历这个数组。 var o = r.slice(): 复制数组以防止迭代过程中数组被修改。Ue(o[i], null, e, t, "v-on handler"): 对数组中的每一个函数都调用Ue来安全地执行它。
- 如果
return n.fns = e, n:n.fns = e: 把传入at的原始处理器 (e) 存储为invoker的一个属性fns。return n: 返回这个invoker函数。
小结
它不直接执行事件,而是制造并返回另一个函数(invoker)。这个 invoker 被触发时,会查找自己 .fns 属性上的“真实”处理器(或处理器数组),然后使用 Ue 去安全地执行它们。
函数 Ue: 错误处理执行器
这个函数的核心目的是安全地执行一个函数,并捕获它在执行过程中可能抛出的同步错误和异步 (Promise) 错误。
function Ue(e, t, n, r, o)e: 要执行的目标函数 (handler)。t: 执行函数时的this上下文。n: 传递给目标函数的参数数组。r: 相关的 Vue 实例 (vm),用于错误报告。o: 一个信息字符串,用于描述错误的来源 (例如 “v-on handler”)。
逻辑分解
var i;: 声明一个变量i,用来存放函数的执行结果。try { ... } catch (e) { ... }: 这是一个标准的try...catch块,用于捕获同步错误。i = n ? e.apply(t, n) : e.call(t):- 这是实际执行函数的地方。
- 如果
n(参数数组) 存在,就使用e.apply(t, n)来调用函数,并传入参数。 - 如果
n不存在,就使用e.call(t)来调用(无参数)。 i被赋值为函数的返回值。
i && !i._isVue && f(i) && !i._handled:- 这串检查是用来处理异步 (Promise) 错误的。
i: 检查函数是否有返回值。!i._isVue: 确保返回值不是一个 Vue 实例。f(i): 这是一个(未显示的)辅助函数,作用是检查i是否是一个 Promise。!i._handled: 确保这个 Promise 的错误还未被处理过。
i.catch(...):- 如果上述所有条件都为真(即:函数返回了一个未处理的 Promise),它会给这个 Promise 挂载一个
.catch()处理器。 - 如果 Promise 被
reject(即发生异步错误),Ve函数(一个全局错误处理器)会被调用,并附带上额外的信息 “(Promise/async)”。 i._handled = !0: 标记这个 Promise 已经被处理,防止重复捕获。
- 如果上述所有条件都为真(即:函数返回了一个未处理的 Promise),它会给这个 Promise 挂载一个
catch (e):
- 如果在执行同步函数时就出错,会立即进入这个catch块。
- 调用Ve错误处理器,报告这个同步错误。return i:返回目标函数的原始返回值(可能是undefined、一个值,或者那个 Promise)。
小结
它是一个健壮的函数执行器,能统一处理同步和异步 Promise 错误,并将它们报告给 Vue 的主错误处理系统 (Ve)。
它们如何协同工作
- 当 Vue 编译模板,遇到一个
v-on:click="myHandler"时,它会调用at(myHandler, vm)。 at函数创建了一个invoker函数,并设置invoker.fns = myHandler,然后返回这个invoker。- Vue 将这个
invoker绑定到 DOM 元素的click事件上。 - 当用户点击元素时,
invoker被触发。 invoker内部执行,它读取自己的.fns属性(即myHandler)。invoker调用Ue(myHandler, null, arguments, vm, "v-on handler")。Ue负责在try...catch中执行myHandler,并处理可能发生的任何同步或异步错误。
这种设计的最大好处是:如果 myHandler 后来改变了(例如在 data 中),Vue 只需要更新 invoker.fns = newHandler 即可,而不需要解绑和重新绑定 DOM 事件监听器,效率极高。
