好傢伙,
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;
}