vue学习2
观察订阅模式
关于cleanupDeps函数
Watcher.prototype.cleanupDeps = function cleanupDeps () {
var i = this.deps.length;
while (i--) {
var dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this);
}
}
var tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
// 上面4行代码旧数据的删除
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
// 新数据的更新
};
该方法用于进行依赖dep的更新, 添加新的订阅移除旧的不需要的订阅。
比如 v-if 已不需要的模板依赖的数据发生变化时就不会通知watcher去 update, 当v-if模板不需要渲染时,它其中涉及到的数据不需要进行该watcher的订阅。
a.Watcher依赖收集的观察者
b.Dep依赖收集器 订阅者
c.Observe 类主要给响应式对象的属性添加 getter/setter 用于依赖收集与派发更新
生成虚拟DOM的render相关函数
function installRenderHelpers (target) {
target._o = markOnce;
target._n = toNumber;
target._s = toString;
target._l = renderList;
target._t = renderSlot;
target._q = looseEqual;
target._i = looseIndexOf;
target._m = renderStatic;
target._f = resolveFilter;
target._k = checkKeyCodes;
target._b = bindObjectProps;
target._v = createTextVNode;
target._e = createEmptyVNode;
target._u = resolveScopedSlots;
target._g = bindObjectListeners;
target._d = bindDynamicKeys;
target._p = prependModifier;
}
createFunctionalComponent函数式组件
a.组件的functional设置为true
b.组件提供一个render函数
c. render函数的返回结构类似 h(ele, attr, [children])
示例
Vue.component('my-transition', {
functional:true,
render:function (h, ctx) {
var data = {
props:{
tag:'ul',
css:false
},
on:{
beforeEnter:function (el) {
el.style.opacity = 0
el.style.height = 0
},
enter:function (el, done) {
var delay = el.dataset.index * 150
setTimeout(function () {
Velocity(el, {opacity:1, height:'1.6em'},{complete:done})
}, delay)
},
leave:function (el, done) {
var delay = el.dataset.index * 150
setTimeout(function () {
Velocity(el, {opacity:0, height:0}, {complete:done})
}, delay)
}
}
}
return h('transition-group', data, ctx.children)
},
props:['query', 'list']
})
component初始化钩子函数
// inline hooks to be invoked on component VNodes during patch
var componentVNodeHooks = {
init: function init (node, hydrating) {},
prepunch: function prepunch (oldVnode, Vnode) {},
insert: function instert(vnode) {},
destroy: function destroy(vnode) {}
}
初始化merge相关钩子函数
代码结构如下
installComponentHooks
组件创建的过程
createComponent方法的相关代码
…
// 继承自Vue
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor);
}
…
// 如果是异步组件
asyncFactory = Ctor;
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context);
…
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor);
…
// extract props
var propsData = extractPropsFromVNodeData(data, Ctor, tag);
…
// 如果是functional component
return createFunctionalComponent(Ctor, propsData, data, context, children)
…
// install component management hooks onto the placeholder node
installComponentHooks(data);
…
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 },
asyncFactory
);
return vnode
v-model的实现
// transform component v-model info (value and callback) into
// prop and event handler respectively.
function transformModel (options, data) {
var prop = (options.model && options.model.prop) || 'value';
var event = (options.model && options.model.event) || 'input'
;(data.props || (data.props = {}))[prop] = data.model.value;
var on = data.on || (data.on = {});
var existing = on[event];
var callback = data.model.callback;
if (isDef(existing)) {
if (
Array.isArray(existing)
? existing.indexOf(callback) === -1
: existing !== callback
) {
on[event] = [callback].concat(existing);
}
} else {
on[event] = callback;
}
}
有上面代码可知v-model会转换成prop 和event事件
示例1
<input v-model=“message” >
<input :value=“message” @input=“message=$event.target.value” >
可以通过设置model的属性 prop自定义变量 event来自定义属性
示例2
<my-msg v-model="testmsg"></my-msg>
Vue.component('my-msg', {
template: `
<div>
<p>this is the value {{msg}}</p>
<span @click="clickMe"> to click</span>
</div>
`,
props: {
msg: ''
},
model: {
event: 'coustomEvent',
prop: 'msg'
},
methods: {
clickMe () {
this.$emit('coustomEvent', this.msg + 'sss ')
}
}
})
_createElement
_createElement构造虚拟DOM
_render函数
_render函数返回一个vnode
initMixin函数
initMixin函数包括Vue.prototype._init
其中_init执行Vue整个过程中涉及到的函数,实例化Vue的入口函数,其主要代码如下
// a flag to avoid this being observed
vm._isVue = true;
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
/* istanbul ignore else */
{
initProxy(vm);
}
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
/* istanbul ignore if */
if (config.performance && mark) {
vm._name = formatComponentName(vm, false);
mark(endTag);
measure(("vue " + (vm._name) + " init"), startTag, endTag);
}
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
resolveConstructorOptions
resolveConstructorOptions处理options的相关属性
vue插件原理
initUse
initUse主要为vue挂载静态方法use, 其代码结构如下
function initUse (Vue) {
Vue.use = function (plugin) {
var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
var args = toArray(arguments, 1);
// 将Vue对象注入
args.unshift(this);
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args);
} else if (typeof plugin === 'function') {
plugin.apply(null, args);
}
installedPlugins.push(plugin);
return this
};
}
开发插件的示例代码
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
mixin
mixin的代码如下所示
function initMixin$1 (Vue) {
Vue.mixin = function (mixin) {
this.options = mergeOptions(this.options, mixin);
return this
};
}
由代码可知mixin将一些方法或属性挂载到 vue的静态options属性上
例如
Vue.mixin({
created: function () {
console.log('mixin created')
}
})
Vue继承
initExtend实现Vue的继承, 在组件初始化时创建一个继承自Vue的子对象
主要代码结构如下
var Sub = function VueComponent (options) {
this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
Sub.options = mergeOptions(
Super.options,
extendOptions
);
Sub['super'] = Super;
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props) {
initProps$1(Sub);
}
if (Sub.options.computed) {
initComputed$1(Sub);
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type];
});
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub;
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({}, Sub.options);
// cache constructor
cachedCtors[SuperId] = Sub;
return Sub
}
pruneCache
删除keepAlive组件的缓存
其中keepAlive的生命周期函数还包括如下
var KeepAlive = {
…
created (){},
destroyed () {},
mounted() {},
render() {}
}
initGlobalAPI
initGlobalAPI注册Vue的全局函数
renderClass
renderClass用来生成元素的class, 其中concat代码如下
function concat (a, b) {
return a ? b ? (a + ' ' + b) : a : (b || '')
}
其中涉及字符串化基本方法主要
stringifyClass、stringifyArray、stringifyObject
registerRef方法
registerRef方法用过注册设置ref属性的组件
自定义指令
自定义指令主要方法updateDirectives, 而该函数其实是对_update函数的调用
_update主要用来触发指令创建及变更指令中所涉及到的一些方法, 其代码结构如下
function _update (oldVnode, vnode) {
var isCreate = oldVnode === emptyNode;
var isDestroy = vnode === emptyNode;
var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);
var dirsWithInsert = [];
var dirsWithPostpatch = [];
var key, oldDir, dir;
for (key in newDirs) {
oldDir = oldDirs[key];
dir = newDirs[key];
if (!oldDir) {
// new directive, bind
callHook$1(dir, 'bind', vnode, oldVnode);
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir);
}
} else {
// existing directive, update
dir.oldValue = oldDir.value;
dir.oldArg = oldDir.arg;
callHook$1(dir, 'update', vnode, oldVnode);
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir);
}
}
}
if (dirsWithInsert.length) {
var callInsert = function () {
for (var i = 0; i < dirsWithInsert.length; i++) {
callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
}
};
if (isCreate) {
mergeVNodeHook(vnode, 'insert', callInsert);
} else {
callInsert();
}
}
if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode, 'postpatch', function () {
for (var i = 0; i < dirsWithPostpatch.length; i++) {
callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
}
});
}
if (!isCreate) {
for (key in oldDirs) {
if (!newDirs[key]) {
// no longer present, unbind
callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
}
}
}
}
一个简单的例子如下
Vue.directive('mydir', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
console.log('look my direct')
}
})
注意
在模板编译的过程中会将指令名称转为小写,在创建指令时需采用小写方式
过滤器
parseFilters
parseFilters解析过滤器表达式
举个例子如下
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
{{message | capitalize}}
parseFilters返回的结果为"_f("capitalize")(message)"
编译过程
编译过程简单介绍
a.parser 将template模板生成抽象语法树
b.optimizer 对语法树进行优化处理,用来提升性能
c.codegen用来生成render函数
示例如下
<div>
<header>
<h1>I'm a template!</h1>
</header>
<p v-if="message">
{{ message }}
</p>
<p v-else>
No message.
</p>
function anonymous() { with(this){return _c('div',[_m(0),(message)?_c('p',[_v(_s(message))]):_c('p',[_v("No message.")])])} }
vue动画过渡
组件或元素进入或离开时执行的动画过渡效果
a.组件为transition
b.条件渲染使用 v-if
c.条件展示 v-show
过渡钩子函数
1.v-enter过渡开始状态,元素被插入之前生效,在元素被插入的下一帧移除
2.v-enter-active定义过渡生效时的状态,在元素被插入之前生效动画完成之后移除
3.v-enter-to元素插入之后下一帧生效
4.v-leave离开时过渡开始时的状态
5.v-leave-active离开时过渡生效的状态,在过渡被触发是立即生效
6.v-leave-to离开过渡结束状态
元素插入时的钩子函数 为function enter (vnode, toggleDisplay)
定义v-show指令时,在bind, 和 update中会涉及 enter函数的调用, 可用v-show进行相关代码的调试
补充
触发该动画时会进行_isMounted
的判断, bind方法发生在mountComponent之前,如果设置默认变量为ture切不进行变更的情况下则不会触发动画函数
其中涉及到的动画进入代码代码主要如下
// start enter transition
beforeEnterHook && beforeEnterHook(el);
if (expectsCSS) {
addTransitionClass(el, startClass);
addTransitionClass(el, activeClass);
nextFrame(function () {
removeTransitionClass(el, startClass);
if (!cb.cancelled) {
addTransitionClass(el, toClass);
if (!userWantsControl) {
if (isValidDuration(explicitEnterDuration)) {
setTimeout(cb, explicitEnterDuration);
} else {
whenTransitionEnds(el, type, cb);
}
}
}
});
}
示例如下
<transition name="fade">
<span v-show="isanimate" style="background: green; color: #fff">the animate</span>
</transition>
.fade-enter-active, .fade-leave-active {
transition: opacity 3s ease-out
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
mounted () {
console.log('to mounted')
setTimeout(() => {
this.isanimate = true
}, 30)
}
resolveTransition方法
macro task 和 micro task
其代码的执行顺序按照下列伪代码进行说明
for (macroTask of macroTaskQueue) {
handleMacroTask()
for (microTask of microTaskQueue) {
handleMicroTask(microTask)
}
}
在浏览器中,常见的macro task任务有setTimeout MessageChannel postMessage setImmetiate
常见的micro task 有 MutationObserver 和Promise.then