vue源码学习中的阻碍知识

vue思维导图.png

Object.keys()
延申:Object.keys().map() 一道面试题应用考察 27题
传送门:https://www.jianshu.com/p/d35a406acdc6

一、语法:

参数:对象
返回值:对象可枚举属性的字符串数组

二、应用

处理对象,返回可枚举的属性数组
image.png
处理数组,返回索引值数组

let arr=[1,2,3,4,5]


image.png
处理字符串,返回索引值数组

let str="sszmq"


image.png

https://www.cnblogs.com/llcdxh/p/9476347.html
Object.defineProperty() 数据劫持

  let Person={}
Object.defineProperty(Person,'name',{
  value:'jack',//值
  configurable:false, //是否可以再次配置
  writable:true, //是否可以改变
  enumerable:true //描述属性是否会出现在for in 或者Object.keys()得遍历中
})

https://www.jianshu.com/p/8fe1382ba135

vue的核心数据的监听,数组/对象深度遍历绑定,重写数组中的push/pop/shift/unshift/splice/sort/reverse七个方法

vue依赖收集做标记,如何做的标记还是不明白

vue数据依赖收集的精髓:
  • 子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个正在本身闭包中的depend,另一个是子元素的depend

  • 数组的话则需要对每一个成员都进行依赖收集,如果是数组的成员还是数组,则递归

vue的双向数据绑定原理:


image.png

通过component 渲染 函数形成虚拟dom,然后进行虚拟dom中用到的data定义属性进行dep 数据依赖收集(发布者),做标记,添加观察者(watcher),移除观察者,通知所有订阅者进行执行回调,修改视图中的data,。当数据修改的时候就是执行setter就会触发dep发布者的notify通知所有订阅者,执行回调修改视图。

vue得虚拟dom

把真实得dom树抽象成一颗javascript对象构成得抽象树,如果每次修改了dom都去重绘整个视图层,很消耗性能,这个时候我们通过虚拟得dom和真实得dom树去进行比较,差异修改,修改以后经过diff算法得出一些需要修改得最小单位,再将这些小单位得视图进行更新。这样就减少了很多不需要得dom操作,大大提高了性能

diff算法

diff算法是通过同层得树节点进行比较而非对逐层搜索遍历得方式


image.png

patch得实现方法

 /*createPatchFunction的返回值,一个patch函数*/
  return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
    /*vnode不存在则直接调用销毁钩子*/
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      /*oldVnode未定义的时候,其实也就是root节点,创建一个新的节点*/
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue, parentElm, refElm)
    } else {
      /*标记旧的VNode是否有nodeType*/
      /*Github:https://github.com/answershuto*/
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        /*是同一个节点的时候直接修改现有的节点*/
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
      } else {
        if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            /*当旧的VNode是服务端渲染的元素,hydrating记为true*/
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            /*需要合并到真实DOM上*/
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              /*调用insert钩子*/
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (process.env.NODE_ENV !== 'production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '

, or missing . Bailing hydration and performing ' + 'full client-side render.' ) } } // either not server-rendered, or hydration failed. // create an empty node and replace it /*如果不是服务端渲染或者合并到真实DOM失败,则创建一个空的VNode节点替换它*/ oldVnode = emptyNodeAt(oldVnode) } // replacing existing element /*取代现有元素*/ const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) createElm( vnode, insertedVnodeQueue, // extremely rare edge case: do not insert if old element is in a // leaving transition. Only happens when combining transition + // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) if (isDef(vnode.parent)) { // component root element replaced. // update parent placeholder node element, recursively /*组件根节点被替换,遍历更新父节点element*/ let ancestor = vnode.parent while (ancestor) { ancestor.elm = vnode.elm ancestor = ancestor.parent } if (isPatchable(vnode)) { /*调用create回调*/ for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, vnode.parent) } } } if (isDef(parentElm)) { /*移除老节点*/ removeVnodes(parentElm, [oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { /*Github:https://github.com/answershuto*/ /*调用destroy钩子*/ invokeDestroyHook(oldVnode) } } } /*调用insert钩子*/ invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm }

当老节点和新节点相同得时候才会执行patchVnode,如果为同一个节点
就进行patchVnode,否则创建新得dom。
怎样得算相同得sameVnode
当节点得key,tag,iscomment,是否data包含了具体得一些数据信息,一个VnodeData类型,
当标签是input得时候,type也要相同
这里key,tag,iscomment是vue源码Vnode中讲到得,dom渲染属性,


image.png

image.png
patchVnode得规则是这样得
  • 如果新旧vnode都是静态得,同时他们得key相同(代表同一节点)并且新得vnode时clone或者标记了once(v-once,只渲染一次,)那么只需要替换elm以及componentIntance即可
  • 新老节点均有children子节点,则对子节点进行diff操作
  • 如果老节点没有子节点而新节点存在子节点,先清空老节点DOM的文本内容,然后为当前DOM节点加入子节点
  • 当新节点没有子节点而老节点有子节点的时候,则移除该DOM节点的所有子节点。
  • 当新老节点都无子节点的时候,只是文本的替换。

vue得template得编译

image.png

这个时候我们就结合一下vue的生命周期图示
新建vue实例,初始化事件,注入,初始化data,然后去render,渲染,判断是否指定el选项,如果有在判断是否指定template,如果是就讲template编译到render函数中,如果没有就将el外部的html作为template编译。当没有指定el选项的时候执行$mount(el)函数

mount函数主要做了什么?

处理template,变成render函数,render最终会返回一个Vnode(虚拟dom节点)供页面渲染,或者在_update的时候,经过patch与之前的Vnode节点进行比较,得出差异然后将这些差异渲染到真实的dom中。
render函数不存在的时候才会编译tempate,否则优先使用render
template会被编译成语法树


img

我们可以看到最外层的div是这颗AST的根节点,节点上有许多数据代表这个节点的形态,比如static表示是否是静态节点,staticClass表示静态class属性(非bind:class)。children代表该节点的子节点,可以看到children是一个长度为4的数组,里面包含的是该节点下的四个div子节点。children里面的节点与父节点的结构类似,层层往下形成一棵AST。
再来看看由AST得到的render函数

with(this){
    return _c(  'div',
                {
                    /*static class*/
                    staticClass:"main",
                    /*bind class*/
                    class:bindClass
                },
                [
                    _c( 'div', [_v(_s(text))]),
                    _c('div',[_v("hello world")]),
                    /*这是一个v-for循环*/
                    _l(
                        (arr),
                        function(item,index){
                            return _c(  'div',
                                        [_c('p',[_v(_s(item.name))]),
                                        _c('p',[_v(_s(item.value))]),
                                        _c('p',[_v(_s(index))]),
                                        _c('p',[_v("---")])]
                                    )
                        }
                    ),
                    /*这是v-if*/
                    (text)?_c('div',[_v(_s(text))]):_c('div',[_v("no text")])],
                    2
            )
}

代码中的_c,_l都是vue源码中定义的

/*处理v-once的渲染函数*/
  Vue.prototype._o = markOnce
  /*将字符串转化为数字,如果转换失败会返回原字符串*/
  Vue.prototype._n = toNumber
  /*将val转化成字符串*/
  Vue.prototype._s = toString
  /*处理v-for列表渲染*/
  Vue.prototype._l = renderList
  /*处理slot的渲染*/
  Vue.prototype._t = renderSlot
  /*检测两个变量是否相等*/
  Vue.prototype._q = looseEqual
  /*检测arr数组中是否包含与val变量相等的项*/
  Vue.prototype._i = looseIndexOf
  /*处理static树的渲染*/
  Vue.prototype._m = renderStatic
  /*处理filters*/
  Vue.prototype._f = resolveFilter
  /*从config配置中检查eventKeyCode是否存在*/
  Vue.prototype._k = checkKeyCodes
  /*合并v-bind指令到VNode中*/
  Vue.prototype._b = bindObjectProps
  /*创建一个文本节点*/
  Vue.prototype._v = createTextVNode
  /*创建一个空VNode节点*/
  Vue.prototype._e = createEmptyVNode
  /*处理ScopedSlots*/
  Vue.prototype._u = resolveScopedSlots

  /*创建VNode节点*/
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)

你可能感兴趣的:(vue源码学习中的阻碍知识)