vue原理相关

Vue.js实现原理

  • 初始化
    1. 创建Vue实例对象
    2. init过程会初始化生命周期,初始化事件中心,初始化渲染、执行beforeCreate周期函数、初始化 data、props、computed、watcher执行created周期函数等
    3. 初始化后,调用$mount方法对Vue实例进行挂载(挂载的核心过程包括模板编译渲染以及更新三个过程)
    4. 如果没有在Vue实例上定render方法而是定义了template,那么需要经历编译阶段。需要先将template字符串编译成render function
    5. 编译成render function后,调用$mount的mountComponent方法,先执行beforeMount钩子函数,然后核心是实例化一个渲染Watcher,在它的回调函数(初始化的时候执行,以及组件实例中监测到数据发生变化时执行)中调用updateComponent方法(此方法调用render方法生成虚拟Node,最终调用update方法更新 DOM)
    6. 调用render方法将render function渲染成虚拟的Node,render方法的第一个参数是createElement
    7. 生成虚拟DOM树后,需要将虚拟DOM树转化成真实的DOM节点,此时需要调用update方法,update方法又会调用pacth方法把虚拟DOM转换成真正的DOM节点。在已有虚拟Node的情况下,会通过sameVnode判断当前需要更新的Node节点是否和旧的Node节点相同,如果节点不同那么将旧节点采用新节点替换即可,如果相同且存在子节点,需要调用patchVNode方法执行DIFF算法更新DOM
  • 响应式(实现数据和视图的双向绑定)
    • init的时候会利用Object.defineProperty方法监听Vue实例的响应式数据的变化从而实现数据劫持能力(利用了JS对象的访问器属性getset,在Vue3中使用ES6的Proxy来优化响应式原理)。在初始化流程中的编译阶段,当render function被渲染的时候,会读取Vue实例中和视图相关的响应式数据,此时会触发getter函数进行依赖收集(将观察者Watcher对象存放到当前闭包的订阅者Dep中)
    • 数据发生变化时,会触发数据劫持的setter函数,setter会通知初始化依赖收集中的Dep中的和视图相应的Watcher,告知需要重新渲染视图,Wather就会再次通过update方法来更新视图

new Vue做了哪些操作?

  • new关键字在js中代表实例化一个对象,而vue实际上是一个类,类在js中是用Function来实现的
  • vue只能通过new关键字初始化,然后会调用this._init方法,同时把options参数传入,_init是vue原型上的一个方法
  • Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等
  • initData拿到对象的keys,props,methods时进行一个循环判断,我们在data上定义的属性值有没有在props中也被定义,如果有,则报警告,这是因为我们定义的所有值最终都会绑定到vm上,通过proxy实现,调用proxy,将_data作为sourceKey传入
  • 通过proxy对象定义了一个get,set。当我们访问this.message时也就是访问了this._data.message。所以我们能获取到this.message的值

Vue实例挂载的实现

  • Vue中我们是通过$mount实例方法去挂载vm的
  • 最开始先获取Vue原型上的mount方法。然后调用了query方法,如果你是一个字符串就调用了原生的api->querySelector,如果说找不到的话,就会报一个错误,然后返回一个空的div,如果有的话就直接返回。如果不是字符串,代表着就已经是一个Dom对象,那么就直接return
  • 然后拿到了$options,会判断有没有定义render方法,如果没有定义render方法,则会把el或template字符串转换成render方法。所有Vue的组件渲染最终都需要render方法,无论我们是单文件.vue方式开发组件,还是写了el或template属性,最终都会转换成render方法,这个过程是Vue的一个编译过程,通过compileToFunctions生成render,然后分别赋值给options.render,options.staticRenderFns
  • 如果没有template就会去调用getOuterHTML方法,如果dom对象的outerHtml存在就直接返回,如果不存在,就直接在外面包一层,返回他的innerHTML
  • mount方法支持传入2个参数,第一个是el,它表示挂载的元素,第二个是和服务器渲染相关,在浏览器环境下我们不需要传第二个参数。 $mount 会去调用mountComponent方法
  • mountComponent核心就是先调用vm.render方法先生成虚拟Node,下面是来猜测你是出现哪里错误,还提示一些警告,然后再实例化一个渲染watcher,在它的回调函数中会高用updateComponent方法,最终调用vm._update更新DOM。
  • Watcher在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当vm实例中的监测的数据发生变化时候执行回调函数
  • 函数最后判断为根节点的时候设置vm.isMounted为true,表示这个实例已经挂载了,同时执行mounted钩子函数

render方法实现

  • Vue的_render方法是实例的私有方法,它用来把实例渲染成一个虚拟Node
  • 在原型上定义了一个_render,从options中拿到这个render函数,我们了解到这个render函数可以是编译生成的也可以是用户自己写的,在这里通过render.call方法传入两个参数,一个是vm._renderProxy,一个是vm.createElement,vm._renderProxy在生产环境下其实就是vm,vm.$createElement是在initRender函数中定义的,initRender是在init过程中执行initRender方法
  • render方法实际会生成一个vnode,我们拿到这个vnode之后会判断是否是VNode的instance,如果不是的情况下是否是一个数组,如果是一个数组的话证明我们的模板有多个根节点,会返回多个vnode,这时候会报出一个警告,所以说根节点只能有一个vnode

Virtual DOM与createElement实现

  • Virtual Dom 它产生的前提是浏览器中的DOM是很复杂的,浏览器的标准就是把dom设计得很庞大,当我们频繁去更新DOM的时候,会产生一定的性能问题
  • Virtual Dom 除了它的数据结构的定义,映射到真实的DOM实际上要经历VNode的create、diff、patch等过程,在Vue.js中,VNode的create是createElement方法创建的
  • createElement定义了6个参数,第一个是vnode的实例,第二个tag是一个标签,第三个data是跟vnode相关的一些数据,children是它的一些子节点,可以构造vnode tree完美的映射我们的dom tree,createELement最终调用的是_createElement这个函数,createElement其实是对参数做一次封装,封装好了然后去调用_createElement去处理这个vnode
  • 每一个VNode有children,children每个元素也是一个VNode,这样就形成了一个VNodeTree,对应我们的DomTree

update

  • _update是实例的一个私有方法,主要将VNode渲染成真实的DOM
  • update调用的时机有两个,一个是在首次渲染的时候,调用update,将VNode映射成真实的DOM,还有一个是在我们改变数据的时候,数据改变也会驱动视图的变化,同样也会调update方法
  • 当数据改变的时候执行vm.patch这个方法,然后判断是否在浏览器环境,如果在有patch方法,否则是一个空函数。这是因为在服务端是不可能触碰到dom的
  • patch方法调用了createPatchFunction,然后返回一个函数,传入了两个值,一个是nodeOps,一个是modules,nodeOps实际是一些操作dom的方法,modules定义了一些模块的钩子函数的实现
  • 拿到modules,nodeOps,对hooks执行循环,遍历所有模块。然后定义了很多辅助函数,最后返回一个function patch

虚拟DOM

虚拟DOM就是一个普通的JS对象,包含了 tag、props、children 三个属性;

原生DOM 因为浏览器厂商需要实现众多的规范(各种 HTML5 属性、DOM事件),即使创建一个空的div也要付出昂贵的代价

计算属性/侦听属性

  • computedwatch用法异同
    • 相同:都起到监听/依赖一个数据,并进行处理的作用
    • 不同:其实都是vue对监听器的实现,只不过computed主要用于对同步数据的处理,watch则主要用于观测某个值的变化去完成一段开销较大的复杂业务逻辑。使用watch允许我们执行异步操作 (访问一个 API),并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的;能用computed的时候优先用computed,避免了多个数据影响其中某个数据时多次调用watch的尴尬情况
  • watch的高级用法
    • handler方法:初始化的时候watch是不会执行的,只有当侦听的值改变的时候才会执行监听计算。但如果想在第一次它在被绑定的时候就执行,我们要给侦听的值绑定一个handler方法
    • immediate:true代表如果在wacth里声明了侦听对象的值之后,就会立即先去执行里面的handler方法,如果为 false就跟我们以前的效果一样,不会在绑定的时候就执行
    • deep属性:代表是否开启深度监听,默认为false

你可能感兴趣的:(vue原理相关)