vue原理分析(十)研究new Vue()中的initEvents

在Vue.prototype._init 中有一些init函数,今天我们来研究这些init函数

Vue.prototype._init = function (options) {
  ......
  {
    initProxy(vm);
  }
  ......
  initLifecycle(vm);
  initEvents(vm);
  initRender(vm);
  callHook$1(vm, 'beforeCreate', undefined, false /* setContext */);
  initInjections(vm); // resolve injections before data/props
  initState(vm);
  initProvide(vm); // resolve provide after data/props
  callHook$1(vm, 'created');
  ......
}

上一篇中已经研究了initLifecycle,今天我们往下研究

Vue.prototype._init = function (options) {
  ......
  initEvents(vm);
  ......
}

看下源码 initEvents(vm);

function initEvents(vm) {
    vm._events = Object.create(null);
    vm._hasHookEvent = false;
    // init parent attached events
    const listeners = vm.$options._parentListeners;
    if (listeners) {
        updateComponentListeners(vm, listeners);
    }
}
vm._events = Object.create(null);

该行代码创建了一个原型为null的空对象,并将这个对象赋值给vm实例的_events属性

vm._hasHookEvent = false;

该行代码 给vm实例的_hasHookEvent属性赋值为false

const listeners = vm.$options._parentListeners;

从英文注释,我们可以知道这行代码是为了初始化父组件添加的事件

因为是取的组件$options属性的_parentListeners,这个比较复杂,涉及到双向绑定和虚拟dom,我们后续研究,先暂时理解为父组件绑定在当前组件上的事件

如果存在事件listeners

则执行下面这段代码

if (listeners) {
  updateComponentListeners(vm, listeners)
}
function updateComponentListeners(vm, listeners, oldListeners) {
    target$1 = vm;
    updateListeners(listeners, oldListeners || {}, add$1, remove$1, createOnceHandler$1, vm);
    target$1 = undefined;
}
target$1 = vm;

这行代码的主要作用是保留对vm实例的引用

updateListeners(listeners, oldListeners || {}, add, remove, vm)

这行代码执行updateListeners方法

function add$1(event, fn) {
    target$1.$on(event, fn);
}
function remove$1(event, fn) {
    target$1.$off(event, fn);
}

涉及到 target$1.$on 和 target$1.$off

Vue.prototype.$on = function (event, fn) {
    const vm = this;
    if (isArray(event)) {
        for (let 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;
};

else之前的代码都很简单,先缓存this,如果传入的事件是事件数组的话,则分别对数组内的每一项调用$on绑定事件

else之前的代码 如果event不是个数组,给vm._events[event]传入fn函数

if (hookRE.test(event)) {
    vm._hasHookEvent = true;
}

它表示的是父组件有没有直接绑定钩子函数在当前组件上

return vm

最后 返回vm实例对象

现在我们知道了vm.$on方法主要就是把传入的方法给push到_events属性里,方便之后被$emit调用

Vue.prototype.$off = function (event, fn) {
    const vm = this;
    // all
    if (!arguments.length) {
        vm._events = Object.create(null);
        return vm;
    }
    // array of events
    if (isArray(event)) {
        for (let i = 0, l = event.length; i < l; i++) {
            vm.$off(event[i], fn);
        }
        return vm;
    }
    // specific event
    const cbs = vm._events[event];
    if (!cbs) {
        return vm;
    }
    if (!fn) {
        vm._events[event] = null;
        return vm;
    }
    // specific handler
    let cb;
    let i = cbs.length;
    while (i--) {
        cb = cbs[i];
        if (cb === fn || cb.fn === fn) {
            cbs.splice(i, 1);
            break;
        }
    }
    return vm;
};
const vm = this;
// all
if (!arguments.length) {
    vm._events = Object.create(null);
    return vm;
}

从代码我们看出来当arguments.length为0的时候,说明没有任何参数,这时就需要移除所有的事件监听器,因此我们重置了 vm.events 属性。

// array of events
if (isArray(event)) {
    for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn);
    }
    return vm;
}

如果参数event是个数组,则循环调用vm.$off

分析特殊情况

const cbs = vm._events[event];
if (!cbs) {
    return vm;
}
if (!fn) {
    vm._events[event] = null;
    return vm;
}

表示,有event但是没有cbs,没有callbacks,没有回调函数

则vm._events[event] = null,返回vm实例对象

let cb;
let i = cbs.length;
while (i--) {
    cb = cbs[i];
    if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1);
        break;
    }
}
return vm;

事件名和回调都有,从从列表中找到与参数提供回调函数相同的那个函数,并将它从列表中移除

再回到updateListeners方法

function updateListeners(on, oldOn, add, remove, createOnceHandler, vm) {
    let name, cur, old, event;
    for (name in on) {
        cur = on[name];
        old = oldOn[name];
        event = normalizeEvent(name);
        if (isUndef(cur)) {
            warn$2(`Invalid handler for event "${event.name}": got ` + String(cur), vm);
        }
        else if (isUndef(old)) {
            if (isUndef(cur.fns)) {
                cur = on[name] = createFnInvoker(cur, vm);
            }
            if (isTrue(event.once)) {
                cur = on[name] = createOnceHandler(event.name, cur, event.capture);
            }
            add(event.name, cur, event.capture, event.passive, event.params);
        }
        else if (cur !== old) {
            old.fns = cur;
            on[name] = old;
        }
    }
    for (name in oldOn) {
        if (isUndef(on[name])) {
            event = normalizeEvent(name);
            remove(event.name, oldOn[name], event.capture);
        }
    }
}
for (name in on) {
    cur = on[name];
    old = oldOn[name];
    event = normalizeEvent(name);
    if (isUndef(cur)) {
        warn$2(`Invalid handler for event "${event.name}": got ` + String(cur), vm);
    }
    else if (isUndef(old)) {
        if (isUndef(cur.fns)) {
            cur = on[name] = createFnInvoker(cur, vm);
        }
        if (isTrue(event.once)) {
            cur = on[name] = createOnceHandler(event.name, cur, event.capture);
        }
        add(event.name, cur, event.capture, event.passive, event.params);
    }
    else if (cur !== old) {
        old.fns = cur;
        on[name] = old;
    }
}

遍历on中对象

如果on[name]未定义,则调用警告warn$2

如果oldOn[name]未定义,如果原事件 没有定义,则调用 createFnInvoker() 进行定义;但如果是一个 .once 修饰的事件,则会用 createOnceHandler() 重新定义该事件;最后将其添加到当前实例的 $on 属性中

如果on[name]与oldOn[name]不一致,将原事件定义的 函数定义部分 重新指向为当前新的事件定义,并赋值给新事件对象中

for (name in oldOn) {
    if (isUndef(on[name])) {
        event = normalizeEvent(name);
        remove(event.name, oldOn[name], event.capture);
    }
}

遍历oldOn中对象,将不存在于新事件对象中的事件去除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值