Vue源码学习(二十):$emit、$on实现原理
好家伙,
0、一个例子
<!DOCTYPE html>
<html lang="zh-CN"> <head>
<meta charset="UTF-8">
<title>Vue 父子组件通信示例</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head> <body>
<div id="app">
<parent-component></parent-component>
</div> <script>
// 子组件
Vue.component('child-component', {
template: `
<div>
<button @click="sendDataToParent">发送数据给父组件</button>
</div>
`,
methods: {
sendDataToParent() {
this.$emit('data-sent', '这是从子组件发送的数据');
}
}
}); // 父组件
Vue.component('parent-component', {
template: `
<div>
<child-component @data-sent="handleDataReceived"></child-component>
<p>从子组件接收到的数据:{{ receivedData }}</p>
</div>
`,
data() {
return {
receivedData: ''
};
},
methods: {
handleDataReceived(data) {
this.receivedData = data;
}
}
}); // 创建Vue实例
let vm = new Vue({
el: '#app'
});
</script>
</body> </html>
1、$emit、$on源码
源码实现,我们来看$emit、$on的源码实现部分
Vue.prototype.$on = function (event, fn) {
var vm = this;
if (isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
vm.$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;
// 处理大小写
{
var lowerCaseEvent = event.toLowerCase();
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip("Event \"".concat(lowerCaseEvent, "\" is emitted in component ") +
"".concat(formatComponentName(vm), " but the handler is registered for \"").concat(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 \"".concat(hyphenate(event), "\" instead of \"").concat(event, "\"."));
}
}
var cbs = vm._events[event];
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
var args = toArray(arguments, 1);
var info = "event handler for \"".concat(event, "\"");
for (var i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info);
}
}
return vm;
};
function invokeWithErrorHandling(handler, context, args, vm, info) {
var res;
try {
res = args ? handler.apply(context, args) : handler.call(context);
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
res._handled = true;
}
}
catch (e) {
handleError(e, vm, info);
}
return res;
}
2.代码解释
看着比较复杂,所以我们精简一下,去掉性能优化和一些正则表达式还有一些数组处理
精简下来无非几句代码
$on
(vm._events[event] || (vm._events[event] = [])).push(fn);
$emit
var cbs = vm._events[event];
invokeWithErrorHandling(cbs[i], vm, args, vm, info);
function invokeWithErrorHandling(handler, context, args, vm, info) {
res = args ? handler.apply(context, args) : handler.call(context);
return res;
}
分析:
$emit、$on的实现使用了观察者模式的设计思想
$on方法用于在当前Vue实例上注册事件监听器。
vm._events:维护一个事件与其处理函数的映射。每个事件对应一个数组,数组中存放了所有注册的处理函数。
$emit方法用于触发事件,当事件被触发时,调用所有注册在该事件上的处理函数。
非常简单
3.源码注释版本
// 在Vue的原型上定义一个方法$on
Vue.prototype.$on = function (event, fn) {
// vm指的是Vue的实例
var vm = this;
// 如果event是一个数组,那么对每个事件递归调用$on方法
if (isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn);
}
}
// 如果event不是一个数组,那么将函数fn添加到vm._events[event]中
else {
(vm._events[event] || (vm._events[event] = [])).push(fn);
// 如果event是一个钩子事件,那么设置vm._hasHookEvent为true
if (hookRE.test(event)) {
vm._hasHookEvent = true;
}
}
// 返回Vue的实例
return vm;
}; // 在Vue的原型上定义一个方法$emit
Vue.prototype.$emit = function (event) {
// vm指的是Vue的实例
var vm = this;
// 处理事件名的大小写
{
var lowerCaseEvent = event.toLowerCase();
// 如果事件名的小写形式和原事件名不同,并且vm._events中有注册过小写的事件名
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
// 那么提示用户事件名的大小写问题
tip("Event \"".concat(lowerCaseEvent, "\" is emitted in component ") +
"".concat(formatComponentName(vm), " but the handler is registered for \"").concat(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 \"".concat(hyphenate(event), "\" instead of \"").concat(event, "\"."));
}
}
// 获取vm._events[event]中的所有回调函数
var cbs = vm._events[event];
// 如果存在回调函数
if (cbs) {
// 如果回调函数的数量大于1,那么将其转换为数组
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
// 获取除event外的其他参数
var args = toArray(arguments, 1);
// 定义错误处理信息
var info = "event handler for \"".concat(event, "\"");
// 对每个回调函数进行错误处理
for (var i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info);
}
}
// 返回Vue的实例
return vm;
}; // 定义一个错误处理函数
function invokeWithErrorHandling(handler, context, args, vm, info) {
var res;
try {
// 如果存在参数args,那么使用apply方法调用handler,否则使用call方法调用handler
res = args ? handler.apply(context, args) : handler.call(context);
// 如果返回结果res存在,且res不是Vue实例,且res是一个Promise,且res没有被处理过
if (res && !res._isVue && isPromise(res) && !res._handled) {
// 那么对res进行错误处理,并标记res已经被处理过
res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
res._handled = true;
}
}
// 如果在执行过程中抛出错误,那么进行错误处理
catch (e) {
handleError(e, vm, info);
}
// 返回结果res
return res;
}
Vue源码学习(二十):$emit、$on实现原理的更多相关文章
- Vue源码学习二 ———— Vue原型对象包装
Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...
- Vue源码学习(零):内部原理解析
本篇文章是在阅读<剖析 Vue.js 内部运行机制>小册子后总结所得,想要了解详细内容,请参考原文:https://juejin.im/book/5a36661851882538e2259 ...
- Vue源码学习(二)$mount() 后的做的事(1)
Vue实例初始化完成后,启动加载($mount)模块数据. (一)Vue$3.protype.$mount 标红的函数 compileToFunctions 过于复杂,主要是生 ...
- vue 源码学习二 实例初始化和挂载过程
vue 入口 从vue的构建过程可以知道,web环境下,入口文件在 src/platforms/web/entry-runtime-with-compiler.js(以Runtime + Compil ...
- Vue源码学习三 ———— Vue构造函数包装
Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...
- 手牵手,从零学习Vue源码 系列二(变化侦测篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...
- Vue源码学习1——Vue构造函数
Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...
- Vue源码分析(二) : Vue实例挂载
Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...
- Dubbo源码学习(二)
@Adaptive注解 在上一篇ExtensionLoader的博客中记录了,有两种扩展点,一种是普通的扩展实现,另一种就是自适应的扩展点,即@Adaptive注解的实现类. @Documented ...
- 最新 Vue 源码学习笔记
最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...
随机推荐
- 动态规划-DP 完整版
动态规划 学完了五大基础dp 做个简单总结 dp特征 动态规划问题 首要是找到做题的目的 是求最大/小值 还是其他: 其二 要确定问题的状态转移方程 这是关键: 第三 为dp数组找到边界. 最后 检查 ...
- Base64 java图片工具类
import org.springframework.util.Assert; import javax.imageio.ImageIO; import java.awt.image.Buffered ...
- 可视化学习:使用极坐标参数方程和SDF绘制有趣的图案
前言 本文将介绍如何使用极坐标参数方程和上一篇文章提到的距离场SDF来绘制有趣的图案. 说到曲线和几何图形的绘制,我们知道图形系统默认支持的是通过直角坐标绘制,但是有些曲线呢,不太容易使用直角坐标系来 ...
- [NOIP2000 提高组] 单词接龙
传送锚点:https://www.luogu.com.cn/problem/P1019 题目描述 单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以 ...
- ubuntu 虚拟机安装完docker 以后 出现tls时遇到的坑
网上很多都是更改镜像源,发现更改以后还是不行.请更改网路模式为桥接模式就ok了.
- Android 12(S) ALooper AHandler AMessage(一)
卧榻之侧岂容他人酣睡,到现在ALooper AHandler AMessage的工作原理一直都没搞懂,很慌!看他们的路径都在libstagefright/foundation下,作为一个foundat ...
- 使用 OWIN Self-Host ASP.NET Web API 自宿主 Swagger Swashbuckle 在线文档
使用 OWIN Self-Host ASP.NET Web APIhttps://learn.microsoft.com/zh-cn/aspnet/web-api/overview/hosting-a ...
- 批量处理图片(resize;grayscale)以及重命名
批量处理图片(resize:grayscale)以及重命名 做深度学习,有大量图片需要做为训练集,为方便批量resize,rename,灰度化,制作python脚本. 先纪录一些函数: resize ...
- 开源云原生平台对比 KubeSphere vs Rainbond
最近因为工作需要,需要找一个功能完善的云原生应用平台,经过自己筛选和朋友推荐,剩下 KubeSphere和Rainbond ,这两个产品都是基于 Kubernetes 之上构建的云原生应用平台,功能都 ...
- 抖音abogus逆向分析
声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 目标网站 aHR0cHM6 ...