CSDN软件工程师能力认证(以下简称C系列认证)是由中国软件开发者网CSDN制定并推出的一个能力认证标准。C系列认证历经近一年的实际线下调研、考察、迭代、测试,并梳理出软件工程师开发过程中所需的各项技术技能,结合企业招聘需求和人才应聘痛点,基于公开、透明、公正的原则,甑别人才时确保真实业务场景、全部上机实操、所有过程留痕、存档不可篡改。
我们每天将都会精选CSDN站内技术文章供大家学习,帮助大家系统化学习IT技术。
一:vue中如何绑定事件? vue事件分为两类,一个是原生dom事件,一个是组件自定义事件,绑定方法类似:
#绑定原生dom事件
Add 1
The button above has been clicked {{ counter }} times.
Say hi
Say what
#绑定自定义事件,通过组件内部 $emit('myEvent')触发
#在自定义组件上绑定原生事件
#绑定动态事件,eventName为实例中能够访问到的变量
二:vue中的事件修饰符 dom原生事件往往我们需要的不只是绑定,我们还需要处理冒泡,捕获,取消默认事件等特殊场景。
vue提供了非常便捷的事件修饰符来方便我们很简单的实现这些功能。 .stop (取消冒泡) .prevent (取消默认事件) .capture (捕获阶段执行) .self (只有event.target 就是当前元素才执行) .once (只执行一次,执行完就销毁) .passive (滚动事件允许默认行为和scroll不阻塞执行) vue 还提供了按键修饰符来实现更多复杂的交互 .enter (回车触发) .tab (tab键盘触发) .delete (捕获“删除”和“退格”键) .esc (esc键触发) .space (空格键触发) .up (向上的键触发) .down(向上的键触发) .left(向左的键触发) .right(向右键触发) .ctrl(ctrl键触发) .alt(alt键触发) .shift(shift键触发) .meta(window键触发) .left(鼠标左键触发) .right(鼠标右键触发) .middle(鼠标中键触发) 3、使用方法
...
...
具体大家可以自己去一一尝试,其中有一些按键是有系统兼容问题,大家参考文档注意处理。
三:核心源码解读 1.v-on 指令或者@on 实现vue通过解析template里的html提取出dom上的所有属性
// 正则匹配 html字符串里的 a="xx" @a="xx" @click='xxx' v-on:click="xx" 等属性定义字符串
var attribute = /^\s*([^\s"'\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=`]+)))?/;
// 正则匹配 动态的属性写法 @[x]="handle1" v-on[x]="" :[x]=""
var dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=`]+)))?/;
function parseStartTag () {
var start = html.match(startTagOpen);
if (start) {
var match = {
tagName: start[1],
attrs: [],
start: index
};
advance(start[0].length);
var end, attr;
// 开始匹配,匹配后的属性压入到attrs里
while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {
attr.start = index;
advance(attr[0].length);
attr.end = index;
match.attrs.push(attr);
}
if (end) {
match.unarySlash = end[1];
advance(end[0].length);
match.end = index;
return match
}
}
}
通过正则匹配出对应的事件名和对应的事件执行方法
// 从属性中匹配和事件相关的属性
var onRE = /^@|^v-on:/;
// 匹配事件修饰符
function parseModifiers (name) {
var match = name.match(modifierRE);
if (match) {
var ret = {};
match.forEach(function (m) { ret[m.slice(1)] = true; });
return ret
}
}
//省略N行代码
if (onRE.test(name)) { // 匹配v-on或者@开头的属性
name = name.replace(onRE, '');
isDynamic = dynamicArgRE.test(name);
if (isDynamic) {
name = name.slice(1, -1);
}
addHandler(el, name, value, modifiers, false, warn$2, list[i], isDynamic);
}
// 生成渲染函数字符串
function addHandler (
el,
name,
value,
modifiers,
important,
warn,
range,
dynamic
) {
modifiers = modifiers || emptyObject;//事件修饰符
// warn prevent and passive modifier
/* istanbul ignore if */
if (
warn &&
modifiers.prevent && modifiers.passive // prevent 和passive 不能一起使用,两个是互斥的
) {
warn(
'passive and prevent can\'t be used together. ' +
'Passive handler can\'t prevent default event.',
range
);
}
// normalize click.right and click.middle since they don't actually fire
// this is technically browser-specific, but at least for now browsers are
// the only target envs that have right/middle clicks.
if (modifiers.right) {
if (dynamic) {
name = "(" + name + ")==='click'?'contextmenu':(" + name + ")";
} else if (name === 'click') {
name = 'contextmenu';
delete modifiers.right;
}
} else if (modifiers.middle) {
if (dynamic) {
name = "(" + name + ")==='click'?'mouseup':(" + name + ")";
} else if (name === 'click') {
name = 'mouseup';
}
}
// check capture modifier
if (modifiers.capture) { // 处理捕获
delete modifiers.capture;
name = prependModifierMarker('!', name, dynamic);
}
if (modifiers.once) { // 处理只执行一次
delete modifiers.once;
name = prependModifierMarker('~', name, dynamic);
}
/* istanbul ignore if */
if (modifiers.passive) { // 处理passive
delete modifiers.passive;
name = prependModifierMarker('&', name, dynamic);
}
var events;
if (modifiers.native) { // 处理原生事件
delete modifiers.native;
events = el.nativeEvents || (el.nativeEvents = {});
} else {
events = el.events || (el.events = {});
}
var newHandler = rangeSetItem({ value: value.trim(), dynamic: dynamic }, range);
if (modifiers !== emptyObject) {
newHandler.modifiers = modifiers;
}
var handlers = events[name];
/* istanbul ignore if */
if (Array.isArray(handlers)) {
important ? handlers.unshift(newHandler) : handlers.push(newHandler);
} else if (handlers) {
events[name] = important ? [newHandler, handlers] : [handlers, newHandler];
} else {
events[name] = newHandler;
}
el.plain = false;
}
最终结果是el的events里维护了事件和事件对应的内容方法以及修饰符,以及是否是动态事件名等信息。
通过gen方法生成事件虚拟渲染函数
function genData$2 (el, state) {
var data = '{';
// 省略N行代码
// event handlers
if (el.events) {
data += (genHandlers(el.events, false)) + ",";
}
if (el.nativeEvents) {
data += (genHandlers(el.nativeEvents, true)) + ",";
}
// v-on data wrap
if (el.wrapListeners) {
data = el.wrapListeners(data);
}
return data
}
效果如下
事件作为属性注入到虚拟dom 里
function createCompileToFunctionFn (compile) {
var cache = Object.create(null);
return function compileToFunctions (
template,
options,
vm
) {
options = extend({}, options);
// 省略N行
// check cache
var key = options.delimiters
? String(options.delimiters) + template
: template;
if (cache[key]) {
return cache[key]
}
// 省略N行
// turn code into functions
var res = {};
var fnGenErrors = [];
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
return createFunction(code, fnGenErrors)
});
// 省略N行代码
// 转化后的渲染函数会缓存起来避免重复生成,生成的函数模版内容见下一个截图
return (cache[key] = res)
}
}
// 将代码字符串转化为函数
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err: err, code: code });
return noop
}
}
这里可以看到通过compile生成的虚拟树和render函数字符串。这是vue的核心之一。
虚拟dom转化到实际dom,并调用原生addEventListener绑定事件
# 进入挂载方法
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && query(el);
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
warn(
"Do not mount Vue to or - mount to normal elements instead."
);
return this
}
var options = this.$options;
// resolve template/el and convert to render function
if (!options.render) {
var template = options.template;
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template);
/* istanbul ignore if */
if (!template) {
warn(
("Template element not found or is empty: " + (options.template)),
this
);
}
}
} else if (template.nodeType) {
template = template.innerHTML;
} else {
{
warn('invalid template option:' + template, this);
}
return this
}
} else if (el) {
template = getOuterHTML(el);
}
if (template) {
/* istanbul ignore if */
if (config.performance && mark) {
mark('compile');
}
var ref = compileToFunctions(template, {
outputSourceRange: "development" !== 'production',
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns;
/* istanbul ignore if */
if (config.performance && mark) {
mark('compile end');
measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
}
}
}
return mount.call(this, el, hydrating)
};
// 实际挂载方法
function mountComponent (
vm,
el,
hydrating
) {
vm.$el = el;
// 省略N行
} else {
updateComponent = function () {
// 执行模版更新,调用_render方法创建vnode,调用_update更新dom
vm._update(vm._render(), hydrating);
};
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
// 创建watcher 来执行updateComponent,会初次执行一次
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */); //
hydrating = false;
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
}
// 事件绑定函数
function add$1 (
name,
handler,
capture,
passive
) {
// async edge case #6566: inner click event triggers patch, event handler
// attached to outer element during patch, and triggered again. This
// happens because browsers fire microtask ticks between event propagation.
// the solution is simple: we save the timestamp when a handler is attached,
// and the handler would only fire if the event passed to it was fired
// AFTER it was attached.
if (useMicrotaskFix) {
var attachedTimestamp = currentFlushTimestamp;
var original = handler;
handler = original._wrapper = function (e) {
if (
// no bubbling, should always fire.
// this is just a safety net in case event.timeStamp is unreliable in
// certain weird environments...
e.target === e.currentTarget ||
// event is fired after handler attachment
e.timeStamp >= attachedTimestamp ||
// bail for environments that have buggy event.timeStamp implementations
// #9462 iOS 9 bug: event.timeStamp is 0 after history.pushState
// #9681 QtWebEngine event.timeStamp is negative value
e.timeStamp
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?