在vue.config.js中打开 runtime+compiler版本
vue.esm.js 中 给方法
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data
if (isDef(i)) {
...
if (isDef(vnode.componentInstance)) {
debugger
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
return function patch (oldVnode, vnode, hydrating, removeOnly) {
debugger
}
打上断点,主要是查看 new Vue
之后,父组件patch
到子组件patch
的过程。
注意点:vm._vnode是作为子组件在父组件的占位节点,vm.$vnode是作为子节点的根节点,这样做成的一颗vnode树
function mergeOptions (
parent,
child,
vm
){
debugger
}
简单来说,根据例子,首先通过Vue.mixin
做了一次合并操作,将全局注册的mixin
放到了 vm
中,并且触发了钩子。
然后构造子组件,在Vue.extend
中,做了一次子组件的合并,并且是parent.concat(child)
保证的执行的顺序问题。
function initAssetRegisters (Vue) {
/**
* Create asset registration methods.
* 全局组件注册入口
*/
ASSET_TYPES.forEach(function (type) {
debugger
Vue[type] = function (
id,
definition
) {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
// 组件的构造函数执行入口
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
// 全局组件的执行入口 判断各种情况
function resolveAsset (
options,
type,
id,
warnMissing
) {
/* istanbul ignore if */
if (typeof id !== 'string') {
return
}
debugger
var assets = options[type]
// check local registration variations first
if (hasOwn(assets, id)) { return assets[id] }
var camelizedId = camelize(id)
if (hasOwn(assets, camelizedId)) { return assets[camelizedId] }
var PascalCaseId = capitalize(camelizedId)
if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] }
// fallback to prototype chain
var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
warn(
'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
options
)
}
return reqTask.abort();
}
// 这里初始化的时候还会判断 组件还是实例
Vue.prototype._init = function() {
...
debugger
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
)
}
...
}
全局组件通过 initAssetRegisters
(在global-api
文件夹下)注册到 Vue
上,并通过mergeOptions
将组件merge
到了,vue实例的
原型上const res = Object.create(parentVal || null)
, 因此我们能在所有组件中使用。通过components
下的原型就能找到,同理
keep-alive
。而局部组件是merge到组件实例上的,所以只能组件使用
function resolveAsyncComponent (
factory,
baseCtor
) {
debugger
}
例子1
Vue.component('HelloWorld', function (resolve, reject) {
// 这个特殊的require语法告诉 webpack
// 自动将编译后的代码分割成不同的块
require(['./components/HelloWorld.vue'], function (res) {
resolve(res)
})
})
例子2: 只是第一种的语法糖写法,源码执行上没什么不同
Vue.component('HelloWorld',
// 该 import 函数返回一个 promise 对象
() => import('./components/HelloWorld.vue')
)
例子3 高级用法
// 第三种写法 高级异步组件
const AsyncComp = () => ({
// 需要加载的组件。应当是一个 Promise
component: import('./components/HelloWorld.vue'),
// 加载中应当渲染的组件
loading: LoadingComp,
// 出错时渲染的组件
error: ErrorComp,
// 渲染加载中组件前的等待时间。默认:200ms。
delay: 200,
// 最长等待时间。超出此时间则渲染错误组件。默认:Infinity
timeout: 3000
})
const LoadingComp = {
template: '<div>loading</div>'
}
const ErrorComp = {
template: '<div>error</div>'
}
Vue.component('HelloWorld', AsyncComp)
在进入异步组件解析的时候,会返回undefined
,然后通过createAsyncPlaceholder
创建一个空节点,
<!---->
然后在回调的resolve(res)
中触发执行factory.resolved = ensureCtor(res, baseCtor)
,在
forceRender
执行 $forceUpdate
触发渲染
原理上三种并没有什么不用,但是第三种巧妙的通过对象和settimeout
实现了延迟和等待时长
data () {
const a = {}
const b = {}
b.a = a
a.b = b
return {
a: a
}
}
断点处
function observe (value, asRootData) {
debugger
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
上面就是核心代码,当vue判断他属性上没有 __ob__
的时候,就会执行 new Oberver
方法,并且在其中定义一个
不可枚举的__ob__
属性。并且当再次进来的时候判断它上面是否有该属性,有的话就直接返回
<div v-if="flag">{{ msg }}</div>
<div v-else>{{ msg1 }}</div>
<button @click="change">change</button>
<button @click="toggle">toggle</button>
methods: {
change () {
this.msg = Math.random()
},
toggle () {
this.flag = !this.flag
}
},
断点处
function mountComponent() {
updateComponent = function () {
debugger
vm._update(vm._render(), hydrating)
}
...
debugger
new Watch()
}
// 响应式收集处
function defineReactive$$1 () {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
debugger
var value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
}
// get 方法处
Watcher.prototype.get = function get () {
pushTarget(this)
var value
var vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""))
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
debugger
popTarget()
this.cleanupDeps()
}
return value
}
首次收集依赖后,当我们再次修改属性,点击change方法会再次触发依赖收集,这时newdeps
是空的,
但是上一次收集的记录,deps
是有的,这时候vue就不会进入到adddep
方法,因此也就不会再次收集依赖
而当我们toggle这个属性,即msg
不显示,那么这时候,也会触发依赖收集,这时候在cleanupDeps
的时候,我们发现在这段方法中
let i = this.deps.length
while (i--) {
// 有时候新的已经不监听旧的属性了,这时候就需要删除旧属性的watcher
// 循环查找dep在newdepids是否不存在
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
// 将该观察者对象从Dep实例中移除
dep.removeSub(this)
}
}
循环遍历了上一次的deps
,当其中存在newdeps
中不存在的依赖时,就会通过removeSub
删除依赖,这样
就不会出现我不监听依赖了,但是还是会重新渲染一遍的情况,做到了性能优化
例子
<div>{{msg}}</div>
<button @click="change">change</button>
data () {
return {
// flag: true, // 6
// msg: 'hello world', // 7
// msg1: 'hello Vue' // 8
msg: {
a: 1
}
}
},
change () {
this.$set(this.msg, 'b', 4)
// this.msg = Math.random()
},
断点
function set (target, key, val) {
...
debugger
defineReactive$$1(ob.value, key, val)
ob.dep.notify()
return val
}
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
){
var childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
...
dep.depend()
if (childOb) {
debugger
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
}
在这里断点后单步调试我们发现,当首次他会为最外层msg
对象先做一层响应式监听,这里会有__ob__
。
然后通过递归对msg
做响应式处理,并且也添加了一个__ob__
并且闭包了childOb
。这里因为childObj
有值,所以会触发给__ob__
中的deps
再添加了一个依赖。
然后执行到$set
先defineReactive$$1(ob.value, key, val)
给msg.__ob__
添加了新属性。然后运行ob.dep.notify()
,执行了ob中dep的渲染
使用computedApp的代码,在
Vue.extend = function() {
if (Sub.options.computed) {
debugger
initComputed$1(Sub)
}
}
function initState (vm) {
...
debugger
if (opts.computed) { initComputed(vm, opts.computed) }
...
}
function createComputedGetter (key) {
// 计算属性拦截器
return function computedGetter () {
debugger
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// dirty = lazy = true
// 执行了 this.get 对 计算属性里的方法的data值做了一次依赖
// 求值运算 计算watcher
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
首先要注意,我们的代码是写在组件中的,所以初次的new Vue
我们不会运行
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
而是在Vue.extend
中运行的,在申明了computedGetter
之后,首次渲染name
的时候我们就会执行到这个方法了
这时候当前watcher,我们观察是有dirty=lazy=true
的值,所以我们可以称它为 计算watcher 。这时候我们就给当前watcher
的dep依赖,添加了这个watcher.
然后切换到下一步,
if (Dep.target) {
watcher.depend()
}
这时候Dep.target
是渲染watcher,然后给这个subs
也就是添加了计算watcher的subs,添加了渲染Watcher。结束操作
这时候点击change
方法,在
set: function reactiveSetter (newVal) {
debugger
...
dep.notify()
}
主要在这里,在进入该方法后,我们会发现该依赖触发的时候subs
中有两个watcher,计算watcher和渲染watcher
update () {
/* istanbul ignore else */
// 计算属性值是不参与更新的
if (this.lazy) {
this.dirty = true
// 是否同步更新变化
} else if (this.sync) {
this.run()
} else {
// 将当前观察者对象放到一个异步更新队列
queueWatcher(this)
}
}
计算watcher直接跳过,第二个渲染watcher会放到异步队列等待更新
查看最新的watchApp.vue
,在computed
的相当位置打上断点。
单步调试可以发现,useless
走的和普通data
数据没什么不同,只不过watcher换成了用户watcher
user=true
。而name
因为在computed已经定义,所以它走到的是computed
的依赖流程。这时候我们可以发现,和上面一样,第一次收集了计算watcher的依赖。第二次收集的才是用户watcher。并且通过
// 立即执行
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
// 获取观察者实例对象,执行了 this.get
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
马上求值了了一次。而nested
通过traverse
方法递归调用收集依赖,并通过__ob__避免循环调用。
点击change
查看set流程的时候我们发现,useless
通过update方法放到了queueWatcher
执行队列中,
而nested
因为有sync
属性,直接执行了run
方法
查看compPatch.vue
代码,在
function updateChildComponent() {
debugger
}
function patch() {
debugger
}
function defineReactive$$1() {
set: function reactiveSetter() {
debugger
}
}
首次执行全部跳过,点击toggle
方法后,在patch
方法中,首先会判断新旧节点
- 新旧节点不同
不同很好处理,直接创建新节点,然后找到其父占位节点,更新它(执行一些钩子函数),然后删除旧节点
- 新旧节点相同
执行patchVnode
方法,将新的vnode patch到旧节点上,当更新的是一个组件时执行了prepatch
方法,
拿到新的组件配置和实例,然后执行updateChildComponent
。 将占位符 vm.$vnode 更新、slot 更新,listeners 更新,props 更新等等。然后执行update
钩子。最后对dom节点进行更新。当然如果再碰到组件会继续上面的内容。最后执行组件自定义的钩子函数
例子代码
// "{on:{"click":function($event){return clickHandler($event)}},"
const Child = {
template: '<button @click="clickHandler($event)">' +
'click me' +
'</button>',
methods: {
clickHandler (e) {
console.log('Button clicked!', e)
this.$emit('select')
}
}
}
// with(this){return _c('div',[_c('child',{on:{\"select\":selectHandler},nativeOn:{\"click\":function($event){$event.preventDefault();return clickHandler.apply(null, arguments)}}})],1)}
const vm = new Vue({
el: '#app',
template: '<div>' +
'<child @select="selectHandler" @click.native.prevent="clickHandler"></child>' +
'</div>',
methods: {
clickHandler () {
console.log('Child clicked!')
},
selectHandler () {
console.log('Child select!')
}
},
components: {
Child
}
})
断点位置
// compiler/parse/index.js
function processAttrs(el) {
debugger
}
function genData$2 (el, state) {
debugger
}
编译时,在 processAttrs
方法中,拿到el.attrsList
在实例中存在两种情况
- 不具有修饰符,被转化成
event: {
select: {
value: 'selectHandler'
}
}
- 具有修饰符, 被转化成
nativeEvents: {
click: {
vlue: "clickHandler"
}
}
parse
完成后,经过gencode
的genData
方法生成对应的字符串代码
"{on:{"select":selectHandler},"
// 原生dom事件
"nativeOn:{"click":function($event){$event.preventDefault();return clickHandler.apply(null, arguments)}},"
可以看到在具有修饰符的状态下,其实就是vue
将代码写好了,然后通过一个函数再去执行用户的代码,并且可以看到event
通过$event
传递。
最终我们就拿到了一个被with
包裹的字符串
with(this){
return _c('div',[_c('child',{on:{\"select\":selectHandler},nativeOn:{\"click\":function($event){$event.preventDefault();return clickHandler.apply(null, arguments)}}})],1)
}
同理 子组件的方法
"{on:{"click":function($event){return clickHandler($event)}},"
上面就是解析完成了,完成后就是对代码的执行。
function updateDOMListeners (oldVnode, vnode) {
debugger
}
function add$1 (
name,
handler,
capture,
passive
) {
debugger
}
根据执行堆栈发现,在createElm
以后,执行invokeCreateHooks
,然后执行了 updateDOMListeners
最后执行add
方法将函数添加到目标事件监听上。
点击执行创建的监听事件,执行的其实是createFnInvoker
返回的invoker
函数,里面保存了执行数组,顺序执行
对于自定义事件,vue的操作略有不同
断点
Vue.prototype._init = function () {
if (options && options._isComponent) {
debugger
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
}
}
Vue.prototype.$emit = function(event) {
debugger
}
我们可以看到组件会执行initInternalComponent
,在其中会拿到_parentListeners
这里保存的就是父组件的回调方法。然后在initEvent
中会执行updateComponentListeners--->updateListeners
,注意这里不同的是,我们将add
方法传入到了updateListeners
。所以会执行到Vue.prototype.$on
方法。这样自定义事件的添加就结束了
执行过程:
点击 事件,触发$emit
方法,同样会执行invoker
函数。然后就会执行到父组件上的回调方法。
总结:可以看到其实自定义事件是往当前实例上派发的。写在父组件上的只是一个回调方法
const vm = new Vue({
el: '#app',
template: `<div>
<p>Message is {{message}}</p>
<input type="text" v-model="message" />
</div>`,
data () {
return {
message: ''
}
}
})
断点处
var code = generate(ast, options)
debugger
function processAttrs(el) {
if (dirRE.test(name)) {
debugger
}
}
function genData$2 (el, state) {
debugger
}
在processAttrs
中首先会执行parse
阶段,将v-model
的内容提取,通过addDirective
添加到el.directives
上。这样parse
阶段就完成了。
之后执行genData
生成代码字符串。在state.directives
中拿到model
方法,最后返回
code = "if($event.target.composing)return;message=$event.target.value"
执行addProps
,在el
添加prop
。即
<input type="text" :value="value" >
执行addHandler
,给el
添加event
。并将input
的值指向之前的code
。
最后就会生成字符串 code
"with(this){return _c('div',[_c('p',[_v(\"Message is \"+_s(message))]),_v(\" \"),_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(message),expression:\"message\"}],attrs:{\"type\":\"text\"},domProps:{\"value\":(message)},on:{\"input\":function($event){if($event.target.composing)return;message=$event.target.value}}})])}"
代码解析完成后,就到了执行环节。
function _update (oldVnode, vnode) {
debugger
}
可以看debugger前的堆栈执行,也是从createElm
中执行了createHook
开始。首先通过normalizeDirectives
获取到序列化好后的v-model
对象,然后根据里面的def
对象,添加hook
方法。
并通过mergeVNodeHook
将方法保存。之后在patch
时调用invokeInsertHook
方法触发之前的回调。
然后调用之前注册的insert方法
,完成注册两个事件
compositionstart
compositionend
和中文输入有关。
在输入中文的时候,执行compositionend
,然后triggle``v-model
的input
方法
组件v-model
断点
function genData$2() {
debugger
}
function createComponent() {
debugger
}
组件v-model的parse过程和表单v-model相同,只有在gencode有些许不同,在genDirectives
执行到model
方法的时候,组件v-model
通过genComponentModel
生成
"{model:{value:(message),callback:function ($$v) {message=$$v},expression:"message"}}"
之后跳到运行时,在createComponent
中会执行transformModel(Ctro.options, data)
var prop = (options.model && options.model.prop) || 'value'
var event = (options.model && options.model.event) || 'input'
在这里我们可以使用子组件定义的model
去重写默认的值,之后就会执行到render
去触发方法完成执行
查看例子代码
断点处
function processElement() {
debugger
}
function genData() {
debugger
}
function genSlot() {
debugger
}
var code = generate(ast, options)
debugger
首先查看父组件的ast
树生成过程,可以发现
el: {
attrs: [
{
name: 'slot',
value: 'footer'
}
],
attrsMap: {
slot: 'footer'
}
}
通过genData
生成代码
with(this){return _c('div',[_c('app-layout',[_c('h1',{attrs:{\"slot\":\"header\"},slot:\"header\"},[_v(_s(title))]),_c('p',[_v(_s(msg))]),_c('p',{attrs:{\"slot\":\"footer\"},slot:\"footer\"},[_v(_s(desc))])])],1)}
在子组件,前面的没什么不同, 主要在生成阶段,会调用genSlot
- 如果slot内不存在内容
"_t("default"}"
- 如果slot存在内容
"_t("default",function(){return [_v("默认内容")]}"
最终生成
with(this){return _c('div',{staticClass:\"container\"},[_c('header',[_t(\"header\")],2),_c('main',[_t(\"default\",function(){return [_v(\"默认内容\")]})],2),_c('footer',[_t(\"footer\")],2)])}
运行时断点处
function initRender() {
debugger
}
function renderSlot() {
debugger
}
通过initRender
拿到vm.$slots
,子组件下的所有slot。然后通过renderSlot
,如果scopedSlotFn
有值,就拿到它的vnode节点,如果没有值,就渲染_v
内的内容。_v
会生成一个文本节点
普通插槽:在父组件编译和渲染阶段生成vnode,所以数据的作用于在父组件,子组件渲染时直接拿到vnode
断点处
var code = generate(ast, options)
debugger
function closeElement() {
debugger
if (currentParent && !element.forbidden) {
}
}
processElement() {
debugger
}
function genData$2() {
debugger
if (el.scopedSlots) {
data += (genScopedSlots(el, el.scopedSlots, state)) + ","
}
}
function genSlot() {
debugger
}
在执行到el.tag = 'tempalte'
的时候,通过processSlotContent
赋值el.slotScope = 'props'
。然后在closeElement
中,将规整好的element
push 到 currentParent(child)
中。然后在genScopedSlots
中拿到字符串
"{scopedSlots:_u([{key:"default",fn:function(props){return [_c('p',[_v("Hello from parent")]),_c('p',[_v(_s(props.text + props.msg))])]}}]),"
在子组件中,通过genSlot
会生成
with(this){return _c('div',{staticClass:\"child\"},[_t(\"default\",null,{\"text\":\"Hello \",\"msg\":msg})],2)}
运行阶段
function resolveScopedSlots() {
debugger
}
function renderSlot() {
debugger
}
通过resolveScopedSlots
将fns
解析成一个对象,放到res
中。其实resolveScopedSlots
就是上面的_u
方法。之后执行到上面的_t
即renderSlot
方法。在这个方法中,我们主要运行了scopedSlotFn
这个方法就是上面保存的fn
,因此我们是在子组件,运行了保存的父组件slot
内的代码。在这里生成了vnode。
因为在子组件生成,所以我们可以方便的拿到子组件的数据进行渲染
断点位置
function createComponent() {
if (isDef((i = i.hook)) && isDef((i = i.init))) {
debugger
i(vnode, false /* hydrating */)
}
if (isDef(vnode.componentInstance)) {
debugger
}
}
const componentVNodeHooks = {
init: function() {
debugger
},
prepatch:function() {
debugger
},
insert:function() {
debugger
}
}
function patch() {
debugger
}
render: function render () {
debugger
}
init
keep-alive组件,初始化完成后进行render
,通过this.$slots.default
拿到默认的vnode
。拿到第一个组件节点。然后将组件保存到
this.vnodeToCache = vnode
this.keyToCache = key
并将当前组件的data.keepalive = true
,返回vnode
。然后执行该vnode
的初始化过程,之后执行到patch
,然后执行该vnode
的初始化,这时候我们就能拿到vnode
实例,然后初始化组件和insert
。执行完组件之后,执行之前初始化的keep-alive
组件,将keep-alive
下的elm
,挂载到parentElm
下面。
之后就执行到了patch
方法的最后,invokeInsertHook
方法,调用一些钩子函数,首先是组件的钩子函数mounted
然后执行keep-alive
组件的钩子函数,这里注意,在mounted
中执行cacheVnode
方法,该方法进行了,组件的缓存,并通过pruneCacheEntry
方法,LRU 进行数据缓存的优化。并监听include
和exclude
。
当点击方法触发组件的修改时,执行到patch
方法,会先判断是否是sameVnode
,如果是,就会执行patchVnode
,查看执行栈,就能看到。最终执行prepatch
, 然后执行updateChildComponent
,在这里keep-alive
其实和slot一样,判断是否需要强制刷新,重新解析slot,然后执行到render
就能拿到新组建的vnode
。
之后就会运行新旧节点的patch
,因为是走新旧节点不同的流程,所以会创建新节点,挂载,然后直接删除旧节点。
新节点通过创建执行create
钩子,然后挂载执行mounted
钩子
render: function render (h) {
debugger
}
function _enter {
debugger
}
通过render函数给,transition
内的子节点,赋值了key
和data.transition
。然后进入enter
方法,将data.transition
解析成需要的格式。通过mergeVNodeHook
注册insert
钩子。
然后执行到insert
钩子,这时候dom节点上已经有p标签了。在此回调中,会执行用户设定的enterHook
。之后我们直接看异步的nextFrame
。这个函数其实就是requestAnimalFrame
这个api。其中我们会先移除
startClass
, 然后添加toClass
,然后申明whenTransitionEnds
,这里面我们会通过回调移除之前的两个class
render:function(h) {
debugger
}
this._update = function (vnode, hydrating) {
debugger
}
updated: function updated () {
debugger
}
首次渲染,在render
函数中,给children
字段每个data.transition
赋值transitionData
。第一次渲染不会执行重写的patch
方法,之后就是直接渲染
运行add
方法。这时候我们具有了prevChildren
数组,通过再次的transition
和pos
赋值,拿到了kept
数组,这时候remove
中并没有数组,所以之后的patch
没有什么作用。两者数组没有差别。
之后就执行update
方法,要进行数组的更新了。这时候已经存在节点了,但是我们用的过渡所以他现在不可见。
hasMove
中通过getComputedStyle
拿到css
属性进行赋值。如果是delete
方法,这时候因为kept
值和原数组不同,就会进行重写的patch
进行vnode
的移除,然后才执行 原Patch
vue-router
通过vue
插件的形式进行挂载,在挂载过程中会注册Vue.mixin
,在实例化过程中,beforeCreate
中初始化init
,定义响应式属性$router
和$route
。注册view
和link
组件。
在初始化init
过程中,使用transitionTo
方法进行路由过渡
在 new vue-router
的时候会执行createMatcher
,
function createMatcher() {
debugger
}
该方法中,执行createRouteMap
,通过循环router
执行addRouteRecord
,生成pathMap、pathList、nameMap
function match() {
debugger
}
在init-->transitionTo
时,会执行match
,match
会根据传入的位置和路径,计算出新位置。并匹配到对应的路由record
。然后根据新的位置和record
创建新的路径返回
debugger
this._modules = new ModuleCollection(options)
和router
一样,vuex
也通过vue.use
注册,并通过vue.mixin
执行vuexInit
。在$options
上赋值store
。
new Store
中会执行new ModuleCollection(options)
。执行module
的注册。执行new Module
。通过递归将子module``addChild
到root
上。
debugger
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
installModule
方法注册,所有的getter mutation action
函数,首先是注册的根函数。然后循环注册module
内的方法,注意这时候vuex
会给所有方法前面添加上namespaced
。通过Vue.set
给rootState
上添加module
的state
debugger
resetStoreVM(this, state)
执行getter
的响应式操作。第一步申明一个computed
将getters
的方法名作为key
,并赋值之前定义的
wrappedGetter(store)
。这里面包裹的是,用户定义的函数。并对getters
做了get
的拦截。对store._vm
进行new Vue
。
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
})
申明了$$state
,并且将之前定义的computed
作为vue
实例的computed
。当我访问store
的getters
时,我们其实会走vue
的computed
,然后拿到state