Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue源码分析-数据驱动细节总结 #78

Open
kekobin opened this issue May 31, 2020 · 0 comments
Open

vue源码分析-数据驱动细节总结 #78

kekobin opened this issue May 31, 2020 · 0 comments

Comments

@kekobin
Copy link
Owner

kekobin commented May 31, 2020

手写 Vue框架
Vue模板编译原理
进阶高级前端,这9种Vue技术你掌握了吗?

一 数据驱动
1.初始化链路:

new Vue -> _init ->  mount -> (1) -> mountComponent -> (2) -> _render -> (3) -> createElement -> 
(4) -> _update -> __patch__ -> patch -> createPatchFunction -> (5) -> patch -> (6)

(1) 没有编译成render函数的,调用compileToFunctions将template编译成render函数
(2) 实例化watch,监听vm,通过vm._render得到vnode,然后通过vm._update将vnode渲染到成真实的dom:

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

new Watcher(vm, updateComponent, noop, {
	before () {
	  if (vm._isMounted) {
	    callHook(vm, 'beforeUpdate')
	  }
	}
}, true)

(3) 主要是获取vnode节点树

vnode = render.call(vm._renderProxy, vm.$createElement)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

(4) 上面的底层实现

vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
)

(5) 这里用到了一个函数柯里化的技巧,通过 createPatchFunction 把差异化参数提前固化,这样不用每次调用 patch 的时候都传递 nodeOps 和 modules 了。

export const patch: Function = createPatchFunction({ nodeOps, modules })

function createPatchFunction({ nodeOps, modules })  {
    //....
    return function patch (oldVnode, vnode, hydrating, removeOnly) {}
}

其实就是一个闭包,初始化一次传进去一次初始化参数,然后返回一个函数用于使用。

(6) 这里是patch的过程:
分成两种,一种是第一次初始化,第二种是更新操作。这里只说第一种:

// 1.得到父级elm(这里第一次一般是 #app 的dom):    
oldVnode = isRealElement ? emptyNodeAt(oldVnode) ... // 就算传的是真实的dom元素,会先转换成一个空的Vnode节点
parentElm = oldVnode.elm
// 2.根据vnode的标签,创建其dom元素,缓存到其elm属性上
vnode.elm = nodeOps.createElement(tag)
// 3.创建元素,这个过程中,会根据vnode树,一步步递归的创建出一颗dom节点树,并最终挂在到跟元素上     
 createElm(vnode,xxx,parentElm) =>
createChildren(vnode, children) => 
createElm(children[i], insertedVnodeQueue, vnode.elm)  => 
// 最后调用 insert 方法把 DOM 插入到父节点中,因为是递归调用,子元素会优先调用 insert,所以整个 vnode 树节点的插入顺序是先子后父。
insert(parentElm, vnode.elm)

-- 这里,将所有的children的elm挂在到vnode的elm,得到一颗以vnode.elm为根节点的节点树,最后将vnode.elm挂在到parentElm,即查到到dom中。实际上整个过程就是递归创建了一个完整的 DOM 树并插入到 Body 上。

Vue动态创建dom的实质:

function insert (parent, elm, ref) {
  // ...
  nodeOps.appendChild(parent, elm)
}
export function appendChild (node: Node, child: Node) {
  node.appendChild(child) // 这就是实质: 使用原生方法动态创建DOM
}

jquery时代,是操作的dom树;vue时代,其实是在逻辑层操作的vnode树,最后将差异patch到dom上。每个vnode实例拥有类似下面的诸多属性,其中就报错该实例对应的dom属性elm。这么设置是为了在js中便于处理逻辑。

VNode属性 
{
	tag,
	data,
	children,
	elm,
	//...	
}
  • vue有一点非常值得学习,即把不同的功能逻辑拆成一些单独的函数执行,让主线逻辑一目了然:
function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
  • 还有一点是,Vue的很多原型方法调用时,传的参数特别少,而是在前面的方法中,将相关参数解析、整合到了 this.$options上,这样就不用一个个得显示的往下传了。

new Vue执行链再梳理

new Vue -> 得到 render -> 执行render(createElement) -> 得到vnode实例 -> patch(oldVnode,vnode) -> 得到 vnode.elm Dom节点树 -> insert 该节点树到body上

例子

import Vue from 'vue'
new Vue({
  el:'#app',
  data: {
    message: 'hello world'
  },
  // render: h => h(App),
  render: function(h){
    return h('div', {
      attrs: {
        id: 'test'
      }
    }, this.message)
  }
})

image

  • initLifecycle(通过链表方式建立父子vnode映射关系):
var parent = options.parent;
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
    parent = parent.$parent;
}
parent.$children.push(vm);
}

vm.$parent = parent;
vm.$root = parent ? parent.$root : vm;

参考

vue技术揭秘

@kekobin kekobin changed the title vue源码分析细节总结 vue源码分析-数据驱动细节总结 May 31, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant