Vue源码学习(二十):$emit、$on实现原理

cnblogs 2024-07-09 16:41:00 阅读 73

好家伙,

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 >

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

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。