vue源码学习

vue 源码学习

# 学习笔记

  • 在vue里的渲染过程:
    1. 将templete里面的内容转成字符串
    2. 利用正则去解析语法内容,生成一个ast树。

# 0. vue源码解析

1.new Vue的后,Vue是个方法,里面有初始化的数据的方法,把new的时候传入的options挂在到$options里面。然后出书画state 2.响应式数据处理。如果optionss传入了data,就把data挂在到this._data里,并且遍历做了一层代理,把_data的数据直接映射到到this上面。就可以直接this.a这样取值和设置了 3.Observe里,递归遍历data,如果是对象,就递归进行代理,如果是数组,改写了数组的几个变异方法进行劫持

# 1. 执行 new Vue 时到底发生了什么(一)

https://segmentfault.com/a/1190000019980173 (opens new window)

--再最开始导入vue的时候,就会把给VUE的原型上混入一些初始化、编译和挂载的方法

然后new的时候

  1. 首先调用init方法
  2. init方法里,把组件的Option挂载到vue实例的$option上,然后进行状态初始化
  3. 状态初始化时,会判断有没有data、computed、watcher等各种属性,如果有就依次进行初始化
  4. 初始化data--
    • 把$option里的data放到vm._data里
    • 遍历_data,用Object.defineProperty劫持,将this._data.xxx直接映射到vm上。==注意,实际上的data是在vm._data上==
  5. 对data进行响应式处理
    • 判断有没有__ob__,没有就加上,有就代表已经响应式处理过了,结果过
    • 判断是数组还是对象,数组的话,就是直接改写几个原型方法,例如push。再条用这些方法的时候,加上遍历观测的步骤去对内部元素进行响应式处理
    • 如果是对象,遍历和递归所有属性,直到最下层。
    • 给这个属性加上dep,然后用Object.defineProperty劫持。在get的时候给dep加上使用这个属性的watcher,在set的时候通知dep对dep里存储的所有watcher进行更新
  6. 挂载--
    1. 判断有没有render,没有的话就用templete去编译出vnode
    2. 给组件创建一个watcher。然后把watcher付给Dep.tartget(这个是用来区分这个取值的地方是不是模板取的。也有可能是)
    3. 进行diff对比,然后进行元dom的操作

# 2. data为什么是函数

https://zhuanlan.zhihu.com/p/159452050 (opens new window)

注意data的函数就是一个闭包!

因为在多次使用通一个组件时,如果data是一个对象,所有组件都会取同一个data里取值,因为data存储的是同一个对象的内存地址。 使用函数返回,可以每次返回出不同的data对象

# 3.nexttick 原理

nexttick 方法,利用异步的原理,根据你的环境判断是否支持 promise,支持就用 promise,不行就 settimeout。

实际上是用的浏览器的微任务和宏任务的区别

在 vue 里 dom 更新是异步的,再数据变化后,会调用 nexttick 方法,把 watcher 更新放进微任务队列中,然后等同步代码执行完毕,一起更新。 而$nexttick 方法就是调用了一下 nexttick,把回调函数也放进微任务队列中 比如

a = 1
this.$nexttick(fn)
这样的话,先运行a=1,然后触发watcher更新,调用nexttick将dom更新放进微任务里
然后运行this.$nexttick(fn),把fn放进微任务里
同步代码执行完之后,此时微任务队列是 【更新dom,fn】,依次执行就行

# 4. vue diff 原理

https://zhuanlan.zhihu.com/p/478003083?utm_id=0 (opens new window)

  1. patch() 初步比较是否是同一个节点
  • 首先进入patch方法,会传入老节点和新节点。
  • 如果老节点存在,新节点不存在,择代表销毁老节点
  • 如果新节点存在,老节点不存在,这是初次渲染。直接创建dom
  • 如果老节点不是真实元素,sameVnode去比较是否是 同一个节点,如果是则代表是同一个节点需要更新,这就走到diff的流程了.patchVnode()
  • 如果老节点是真实元素——只有服务端渲染流程会出现
  • 如果老节点不是真实元素,且sameVnode比较不通过。说明元素改变了不是同一个节点,就根据新的node创建元素插入父节点,同时移除老节点
  1. sameVnode 比较是否是同一节点的方法

function sameVnode (a, b) {
  return (
    // key 必须相同,需要注意的是 undefined === undefined => true
    a.key === b.key && (
      (
        // 标签相同
        a.tag === b.tag &&
        // 都是注释节点
        a.isComment === b.isComment &&
        // 都有 data 属性
        isDef(a.data) === isDef(b.data) &&
        // input 标签的情况
        sameInputType(a, b)
      ) || (
        // 异步占位符节点
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}
  • 首先判断KEY,如果undefined,则相当于一直都相等,所以要加key
  • 然后判断标签类型、是否注释、是否有data属性,input标签还会额外判断type是否一样
  1. patchVnode 详细比较node更新,子节点递归
  • 首先判断是否完全相等,完全相等直接返回。如果不完全一致就进行详细的属性比较和更新。 包含新的位置、文本、属性等
  • 然后比较子节点。老节点没有孩子,新节点有则新增。老节点有新节点没有则删除。 其中如果都有 childeren 的话,就递归调用 updateChildren 去比较子节点。
  1. updateChildren 子节点diff流程
  • 旧头旧尾新头新尾4个指针
  • 在不为undefined的情况下:
    • 旧头新头比较: 先sameVnode初步比较是否是同一节点。是:则patchVnode详细比较更新节点同时比较下一层子节点。旧头+1,新头+1
    • 旧尾新尾比较:sameVnode=>是:patchVnode,旧尾-1,新尾-1
    • 旧头新尾比较:sameVnode=>是:patchVnode,移动元素到新尾,旧头+1,新尾-1
    • 旧尾新头比较:sameVnode=>是:patchVnode,移动元素到新头,旧尾-1,新头+1
    • 上面四种IF没过,用新头的key去老节点中找对应的节点。没找到或者没有key=》新建dom。找到:sameVnode=>否:不是同一中节点,当成新建的;是:patchVnode,移动dom到老头,旧index=undefined(因为这个老节点已经在新节点中用到了,所以下一轮比较的时候直接跳过这个老节点),新头+1
  • 下一轮比较
  • 当头尾节点重合或者超过长度,代表已经比较完。新节点有剩余代表有新增,老节点有剩余代表删除则移除
  1. 流程总结:

    • 先patch函数,判断是否有新老节点,然后根据判断新增、删除、或者进行初步比较
    • 初步比较是sameVnode,判断是否是同节点复用的依据就是。里面通过key、tag、inputtype等条件判断
    • 如果sameVnode为否则当成是新元素,走创建dom。如果是则复用老元素,且进行patchVnode。
    • patchVnode里面进行详细的属性内容比较,并且更新。切如果有子节点,还会进行子节点的比较updateChildren
    • updateChildren就是核心的diff流程。
    • 在diff的时候,如果需要移动元素位置,这一操作是在patchVnode理进行的。.就是把老节点的dom直接放到新节点上
  2. 关于加 key 的问题。因为 sameVnode 里面,首先会判断 key 是不是相等,然后再判断 tag 和其他的一些条件,如果是用 v-for 列表循环出来的,往往元素除了内容以外都是一样的。如果不加 key,sameVnode 就会判断通过,初步认为两个节点是一样的,就会就地复用而不是重新创建元素。这样,比如删除掉中间一个节点的话,diff 的时候头头比较因为没有 key,tag 和其他条件也一样,就误认为全部相同,然后修改内容,直到最后少了一个,认为是最后一个元素被删除了。

也就是说这种情况下的流程是。假设删除第 3 个节点

老节点 0 - 新节点 0 =》 相同,然后详细判断,文本内容不一样,修改文本

老节点 1 - 新节点 1 =》 相同,然后详细判断,文本内容不一样,修改文本

老节点 2 - 新节点 2 =》 相同,然后详细判断,文本内容不一样,修改文本

老节点 3 - 新节点 3 =》 相同,然后详细判断,文本内容不一样,修改文本

老节点 4 - 无 =》 新节点少一个,判断为已删除,所以删除最后一个

  • 值得注意的是,因为虽然复用了元素,但是根据数据渲染出来的内容其实还是正常的,每个节点都会修改。但是如果有 input 输入框或者 checkbox 勾选这种是元素里带的属性,就会发生数据混乱了.必须是dom带的临时数据才会!!!!!!!

  • 且,用 index 做 key,因为增删元素 index 自然也会变,所以等同于没有 key

vite踩坑

vite踩坑

vite踩坑

webpack及vue框架配置

webpack及vue框架配置