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 源码分析草稿 #45

Open
iliuyt opened this issue Aug 23, 2019 · 0 comments
Open

vue 源码分析草稿 #45

iliuyt opened this issue Aug 23, 2019 · 0 comments

Comments

@iliuyt
Copy link
Owner

iliuyt commented Aug 23, 2019

VUE初始化

1、package.json查看构建
2、构建执行了script/build.js
3、build调用了config.js,可以看到config中配置了各种环境的构建参数
4、我们看一下浏览器开发环境的构建 web-full-dev,这里引用了web/entry-runtime-with-compiler.js
5、看一下web/entry-runtime-with-compiler.js中重写了$mount,代码可以看到在解析完模板后重新调用vue原型的$mount
6、Vue引用来自./runtime/index文件
7、Vue引用来自/core/index文件
8、Vue 来源于 '/core/instance/index'
9、/core/instance/index 定义了真正的vue。调用Vue时,会执行一系列mixin,
10、以下为初始化mixin,这里主要用于拆分模块进行定义
initMixin:vue初始化方法
stateMixin:数据方法定义
eventsMixin;事件方法初定义
lifecycleMixin:声明周期方法定义
renderMixin:渲染方法定义
11、除了这些之外,在core/index中还进行定义了全局api方法
initGlobalAPI:全局api定义,方法定义在global-api/index下
全局定义主要方法有use,mixin,extend,ASSET_TYPES,set,delete,nextTick等等

调试方法

vue cli通过在配置文件中设置vue别名实现(这里设置vue为别名会出现问题,我会改成别的名字进行引用,例如Avue,Bvue这样)

const path = require("path");
function resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
chainWebpack: config => {
config.resolve.alias.set("vue", resolve("node_modules/vue/dist/vue.esm.js"));
}
};

webpack的话同样的道理,更改别名引用即可

数据驱动

起步

1、Vue构造函数中调用了_init方法,init方法在initMixin时初始化好了,
2、可以看到initMixin中_init方法进行了一系列对vue对象实例的初始化,
3、其中initState对数据进行了初始化。
4、initState分别对props,methods,data,computed,watch进行了初始化
5、initData对data进行了初始化,然后进行了校验判断(props,method判断),属性代理(proxy),最好做了一个observe监听
6、watcher observe dep之间的关系 watcher定义后调用get方法,get方法设置Dep.target,同时设置触发update,update获取observe数据,调用observe的get,
get收集Dep.target,即watcher,放入dep的subs里,observe被调用set时候,set调用dep的notify,通知watcher的update进行更新
7、watcher和dep是多对多的关系,watcher存了dep的id数组,dep中存了watcher实例数组,在收集watcher的时候会进行去重判断,否则就无限循环了

挂载

1、如果有render函数,会直接执行render函数,否则会将templete转换成render函数,最终执行mountComponent方法
2、mountComponent 方法来自 core/instance/lifecycle,下面看下 mountComponent 都做了什么

  • 判断render 函数是否存在,否认默认 render空节点
  • 触发 beforeMount 生命周期事件
  • 执行 _update 方法
  • 创建一个渲染watcher,定义watcher触发事件处理和 触发 beforeUpdate 声明周期事件
  • 判断是否为 vnode 元素,如果是,触发 mounted 生命周期事件
  • 返回组件

render 函数

1、_render就是吧VUE的createElement函数穿进去,具体createElement怎么去实现可以参照文档即可,最终返回vnode。
2、这里需要看一下render函数是可以返回vnode数组的,会默认取第一个元素,如果是非vnode元素,会创建一个空vnode。vnode中会挂载当前组件的vm

什么是虚拟dom

1、个人理解就是把dom的结构描述存在内存里,在内存里进行操作,只渲染最终的结果。

createElement

1、createElement 其实就是将相关的参数赋值给vnode,生成vnode节点
2、normalizationType 这个参数是用来区分是系统编译的render还是用户手写的
3、vnode生成时候,会对tag走一些判断,判断是注册的组件还是普通的vnode

update

1、只有在首次渲染和数据响应时进行渲染执行。
2、update最终将vnode渲染成真正的dom元素,核心方法__patch__,具体实现在src/platforms/web/runtime/patch.js中,根据平台不同,目录也不同
3、这里注意下vm._vnode,每次upadte都会将子vnode设置到当前vm的_vnode下

渲染

render具体做了什么?
render生成一个vnode,vnode中挂载了子组件的vm
所以在循环中判断vnode是组件的话,直接在vnode中获取vm,然后进行init,
然后再调用mount进行循环,这样就可以递归渲染下去。

我这边简单的理解vnode是vm的子类,vnode同时又包含了自己的vm,这样就形成了递归。
递归中非常重要的一个函数componentVNodeHooks
// 实例化vm
var child = (vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance));
// 挂载vm 进行循环
child.$mount(hydrating ? vnode.elm : undefined, hydrating);

$mount->mount->mountComponent->watcher->getter
->vnode=vm.render->vm._update(vnode)->vm.patch
->patch->createElm->createComponent
->componentVNodeHooks->vnode转vm->继续递归
->判断vnode
->是组件
-> 如果有parentElm就插入vnode.elm(dom元素)
->非组件
->document.createElement创建dom
->将当前元素插入父类元素(vnode.elm)

patchVnode

1、相同节点直接返回vnode
2、key相同的静态节点或克隆节点或只渲染一次的节点替换dom(elm)和vm(componentInstance)

updateChildren

开始
必须有oldStart和oldEnd

对比逻辑
oldStart->newStart
oldEnd->newEnd
oldStart->newEnd
oldEnd->newStart
都不符合
1、获取old->key组成对象OldKey
2、判断oldKeys[new.key]是否存在
3、如果不存在遍历old查找new的key
4、如果都没有就创建dom
5、如果有就对比vnode,相同就继续patchVnode
6、否则就创建dom

循环开始的步骤直到结束

如果old循环完了,那就把剩下的new创建dom添加到当前循环完的位置
如果new循环完了,那就把剩下的old删除

   // 未编译
   <template>
      <div id="app">
         <h1>{{ computed }}</h1>
         <button @click="change">更改</button>
      </div>
   </template>

   <script>
   export default {
      name: "app",
      data: function() {
         return {
               message: "asdfasfasdf",
               none: "none",
         };
      },
      computed: {
         computed: function() {
            return this.none+"333"
         },
      },
      methods: {
         change() {
               this.none = "set";
         },
      },
   };
   </script>

   // 编译后
   var render = function() {
   var _vm = this
   var _h = _vm.$createElement
   var _c = _vm._self._c || _h
   return _c("div", { attrs: { id: "app" } }, [
      _c("h1", [_vm._v(_vm._s(_vm.computed))]),
      _c("button", { on: { click: _vm.change } }, [_vm._v("更改")])
   ])
   }
   var staticRenderFns = []
   render._withStripped = true

   export { render, staticRenderFns }
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