上一章没什么履历。直接写了组件机制。觉得涉及到的东西异常的多,不是很轻易讲。本日看了下vue的关于事宜的机制。有一些些体味。写出来。人人一同改正,分享。源码都是基于最新的Vue.js v2.3.0。下面我们来看看vue中的事宜机制:
老模样照样先上一段贯串全局的代码,罕见的事宜机制demo都邑包含在这段代码中:
<div id="app">
<div id="test1" @click="click1">click1</div>
<div id="test2" @click.stop="click2">click2</div>
<my-component v-on:click.native="nativeclick" v-on:componenton="parentOn">
</my-component>
</div>
</body>
<script src="vue.js"></script>
<script type="text/javascript">
var Child = {
template: '<div>A custom component!</div>'
}
Vue.component('my-component', {
name: 'my-component',
template: '<div>A custom component!<div @click.stop="toParent">test click</div></div>',
components: {
Child:Child
},
created(){
console.log(this);
},
methods: {
toParent(){
this.$emit('componenton','toParent')
}
},
mounted(){
console.log(this);
}
})
new Vue({
el: '#app',
data: function () {
return {
heihei:{name:3333},
a:1
}
},
components: {
Child:Child
},
methods: {
click1(){
alert('click1')
},
click2(){
alert('click2')
},
nativeclick(){
alert('nativeclick')
},
parentOn(value){
alert(value)
}
}
})
</script>
上面的demo中一共有四个事宜。基础涵盖了vue中最典范的事宜的四种状况
一般html元素上的事宜
好吧。想一想我们照样一个个来看。如果懂vue组件相干的机制会更容易懂。那末起首我们看看最简朴的第一、二个(两个事宜只差了个润饰符):<div id="test1" @click="click1">click1</div>
这是简朴到不能在简朴的一个点击事宜。
我们来看看竖立这么一个简朴的点击事宜,vue中发生了什么。
1:new Vue()中挪用了initState(vue):看代码
function initState (vm) {
vm._watchers = [];
var opts = vm.$options;
if (opts.props) { initProps(vm, opts.props); }
if (opts.methods) { initMethods(vm, opts.methods); }//初始化事宜
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true /* asRootData */);
}
if (opts.computed) { initComputed(vm, opts.computed); }
if (opts.watch) { initWatch(vm, opts.watch); }
}
//接着看看initMethods
function initMethods (vm, methods) {
var props = vm.$options.props;
for (var key in methods) {
vm[key] = methods[key] == null ? noop : bind(methods[key], vm);//挪用了bind要领,我们再看看bind
{
if (methods[key] == null) {
warn(
"method \"" + key + "\" has an undefined value in the component definition. " +
"Did you reference the function correctly?",
vm
);
}
if (props && hasOwn(props, key)) {
warn(
("method \"" + key + "\" has already been defined as a prop."),
vm
);
}
}
}
}
//我们接着看看bind
function bind (fn, ctx) {
function boundFn (a) {
var l = arguments.length;
return l
? l > 1
? fn.apply(ctx, arguments)//经由历程返回函数润饰了事宜的回调函数。绑定了事宜回调函数的this。而且让参数自定义。越发的天真
: fn.call(ctx, a)
: fn.call(ctx)
}
// record original fn length
boundFn._length = fn.length;
return boundFn
}
总的来说。vue初始化的时刻,将method中的要领代理到vue[key]的同时润饰了事宜的回调函数。绑定了作用域。
2:vue进入compile环节须要将该div变成ast(笼统语法树)。当编译到该div时经由中心函数genHandler:
function genHandler (
name,
handler
) {
if (!handler) {
return 'function(){}'
}
if (Array.isArray(handler)) {
return ("[" + (handler.map(function (handler) { return genHandler(name, handler); }).join(',')) + "]")
}
var isMethodPath = simplePathRE.test(handler.value);
var isFunctionExpression = fnExpRE.test(handler.value);
if (!handler.modifiers) {
return isMethodPath || isFunctionExpression//如果没有润饰符。直接返回回调函数
? handler.value
: ("function($event){" + (handler.value) + "}") // inline statement
} else {
var code = '';
var genModifierCode = '';
var keys = [];
for (var key in handler.modifiers) {
if (modifierCode[key]) {
genModifierCode += modifierCode[key];//处置惩罚润饰符数组,比方.stop就在回调函数里到场event.stopPropagation()再返回。完成润饰的目标
// left/right
if (keyCodes[key]) {
keys.push(key);
}
} else {
keys.push(key);
}
}
if (keys.length) {
code += genKeyFilter(keys);
}
// Make sure modifiers like prevent and stop get executed after key filtering
if (genModifierCode) {
code += genModifierCode;
}
var handlerCode = isMethodPath
? handler.value + '($event)'
: isFunctionExpression
? ("(" + (handler.value) + ")($event)")
: handler.value;
return ("function($event){" + code + handlerCode + "}")
}
}
genHandler函数简朴明了,如果事宜函数有润饰符。就处置惩罚完润饰符,增加润饰符对应的函数语句。再返回。这个历程还会零丁对native润饰符做特别处置惩罚。这个等会说。compile完后天然就render。我们看看render函数中这块地区长什么模样:
_c('div',{attrs:{"id":"test1"},on:{"click":click1}},[_v("click1")]),_v(" "),_c('div',{attrs:{"id":"test2"},on:{"click":function($event){$event.stopPropagation();click2($event)}}}
一览无余。末了在假造dom-》实在dom的时刻。会挪用中心函数:
function add$1 (
event,
handler,
once$$1,
capture,
passive
) {
if (once$$1) {
var oldHandler = handler;
var _target = target$1; // save current target element in closure
handler = function (ev) {
var res = arguments.length === 1
? oldHandler(ev)
: oldHandler.apply(null, arguments);
if (res !== null) {
remove$2(event, handler, capture, _target);
}
};
}
target$1.addEventListener(
event,
handler,
supportsPassive
? { capture: capture, passive: passive }//此处绑定点击事宜
: capture
);
}
组件上的事宜
好了下面就是接下来的组件上的点击事宜了。能够预见到他走的和一般的html元素应该是差别的途径。现实也是云云:
<my-component v-on:click.native="nativeclick" v-on:componenton="parentOn">
</my-component>
最简朴的一个例子。两个事宜的区分就是一个有.native的润饰符。我们来看看官方.native的作用:在原生dom上绑定事宜。好吧。很简朴。我们追随源码看看有何差别。这里能够往回看看我少的不幸的上一章组件机制。vue中的组件都是扩大的vue的一个新实例。在compile完毕的时刻你照样能够发明他也是相似的一个模样。如下图:
_c('my-component',{on:{"componenton":parentOn},nativeOn:{"click":function($event){nativeclick($event)}}
能够看到加了.native润饰符的会被放入nativeOn的数组中。等待后续特别处置惩罚。等不及了。我们直接来看看特别处置惩罚。render函数在实行时。如果碰到组件。看过上一章的能够晓得。会实行
function createComponent (
Ctor,
data,
context,
children,
tag
) {
if (isUndef(Ctor)) {
return
}
var baseCtor = context.$options._base;
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeof Ctor !== 'function') {
{
warn(("Invalid Component definition: " + (String(Ctor))), context);
}
return
}
// async component
if (isUndef(Ctor.cid)) {
Ctor = resolveAsyncComponent(Ctor, baseCtor, context);
if (Ctor === undefined) {
// return nothing if this is indeed an async component
// wait for the callback to trigger parent update.
return
}
}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor);
data = data || {};
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data);
}
// extract props
var propsData = extractPropsFromVNodeData(data, Ctor, tag);
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
var listeners = data.on;//listeners缓存data.on的函数。这里就是componenton事宜
// replace with listeners with .native modifier
data.on = data.nativeOn;//一般的data.on会被native润饰符的事宜所替代
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners
data = {};
}
// merge component management hooks onto the placeholder node
mergeHooks(data);
// return a placeholder vnode
var name = Ctor.options.name || tag;
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }
);
return vnode
}
整段代码关于事宜中心操纵:
var listeners = data.on;//listeners缓存data.on的函数。这里就是componenton事宜
// replace with listeners with .native modifier
data.on = data.nativeOn;//一般的data.on会被native润饰符的事宜所替代
经由这两句话。.native润饰符的事宜会被放在data.on上面。接下来data.on上的事宜(这里就是nativeclick)会按一般的html事宜往下走。末了实行target.add(”,”’)挂上原生的事宜。而先前的data.on上的被缓存在listeneners的事宜就没着么兴奋了。接下来他会在组件init的时刻。它会进入一下分支:
function initEvents (vm) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
// init parent attached events
var listeners = vm.$options._parentListeners;
if (listeners) {
updateComponentListeners(vm, listeners);
}
}
function updateComponentListeners (
vm,
listeners,
oldListeners
) {
target = vm;
updateListeners(listeners, oldListeners || {}, add, remove$1, vm);
}
function add (event, fn, once$$1) {
if (once$$1) {
target.$once(event, fn);
} else {
target.$on(event, fn);
}
}
发明组件上的没有.native的润饰符挪用的是$on要领。这个好熟习。进入到$on,$emit
大抵想到是一个典范的观察者形式的事宜。看看相干$on,$emit
代码。我加点注解:
Vue.prototype.$on = function (event, fn) {
var this$1 = this;
var vm = this;
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
this$1.$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;
console.log(vm);
{
var lowerCaseEvent = event.toLowerCase();
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
"Event \"" + lowerCaseEvent + "\" is emitted in component " +
(formatComponentName(vm)) + " but the handler is registered for \"" + 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 \"" + (hyphenate(event)) + "\" instead of \"" + event + "\"."
);
}
}
var cbs = vm._events[event];
console.log(cbs);
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
var args = toArray(arguments, 1);
for (var i = 0, l = cbs.length; i < l; i++) {
cbs[i].apply(vm, args);//当emit的时刻挪用该事宜。注重上面说的vue在初始化的等待。用bind润饰了事宜函数。所以组件上挂载的事宜都是在父作用域中的
}
}
return vm
};
看了上面的$on,$emit用法下面这个demo也就霎时秒解了(一个经经常使用的非父子组件通讯):
var bus = new Vue()
// 触发组件 A 中的事宜
bus.$emit('id-selected', 1)
// 在组件 B 建立的钩子中监听事宜
bus.$on('id-selected', function (id) {
// ...
})
是否是恍然大悟。
又到了兴奋的总结时候了。segementfault的编辑器真难用。内容多就卡。哎。烦。卡的时候够看很多肥皂剧了。
总的来说。vue关于事宜有两个底层的处置惩罚逻辑。
1:一般html元素和在组件上挂了.native润饰符的事宜。终究EventTarget.addEventListener() 挂载事宜
2:组件上的,vue实例上的事宜会挪用原型上的$on,$emit
(包含一些其他api $off,$once
等等)
荆轲刺秦王。下次见