关于前端:FinClip-前端-VUE-核心原理归纳

3次阅读

共计 6226 个字符,预计需要花费 16 分钟才能阅读完成。

目前,简直所有的小程序框架都是反对前端 JavaScript 语言和 vue.js 框架的。FinClip 小程序是兼容各家平台的,因而在学习了框架应用之后的进阶就要相熟框架的底层原理。

1、数据响应式

  1. 首先判断数据的类型,如果是根底数据类型,间接返回,如果曾经有 ob 属性,示意曾经是响应式的数据了,间接返回该数据。如果是对象就走第 2 步,如果是数组就走第 3 步
  2. 对象是通过 Object.defineProperty,在 getter 里收集依赖,在 setter 里触发更新
  3. 数组是首先拷贝数组的原型,而后基于拷贝的原型改写 (push,pop,unshift,shift,sort,reverse,splice) 七个能够扭转数组长度的办法,而后将改写后的原型赋给数组的隐式原型
  4. 对数组的隐式原型赋值后,还要观测数组的每一项,反复第一步
  5. 如果 Object.defineProperty 的 setter 里赋值,如果新赋的值是对象,也要进行观测
  6. 如果对数组的操作是有数据新增 (push,unshift,splice),还须要观测数组新增的每一项,同第 4 步(这里 Vue 源码的实现是给每个响应式数据[对象和数组] 新增了一个不可枚举的属性 ob,它的作用有三,其一是用来判断数据是否曾经是响应式的数据,如果是就不需再次观测,其二是属性 ob 是 Observer 类的一个实例,实例上有对数组每一项进行响应式解决的办法),其三是 $set 办法中,ob 用来判断要设置属性的对象是不是响应式的对象,如果它自身就不是响应式对象,则该属性无需定义为响应式的属性
    对象是在 Object.defineProperty 的 getter 里进行依赖的收集,在 setter 里触发更新。具体是通过观察者模式,每一个属性都有一个 Dep 类的实例,Dep.target 有值即指向 watcher 的时候,在 dep 内收集 watcher,并且在 watcher 内收集 dep,dep 和 watcher 是多对多的关系,因为一个组件会有多个属性,而 watcher 是组件级的,所以 一个 watcher 可能对应多个 dep,dep 可能对应多个组件,组件外部的 computed 和 watch 都是 watcher。
    不论是根组件还是非根组件(函数),它们的 data 最终的值都是对象,所以只会在 data 最外层对象的某些属性值是数组,所以在 Object.defineProperty 的 getter 里对数组进行依赖收集,咱们晓得依赖的收集是调用 dep 类上收集依赖的办法,Vue 的做法是在创立 Observer 类的实例的时候,定义了一个属性 dep,dep 是 Dep 类的实例。对于多维数组和数组新增的数据,Vue 的做法是,在创立 Observer 类的实例的时候,设置了一个不可枚举的属性 ob,它的值是 Observer 类的实例,所以咱们在对多维数组进行依赖收集的时候,能够调用 ob 的 dep 的办法,对于数组新增的数据,调用 ob 上的办法对数组的每一项做数据响应式,并且调用 ob.dep 上的 notify 办法触发更新。

1.1、数据初始化的程序:props -> methods -> data -> computed -> watch

  • 如果 data 的层级过深会影响性能
  • 对象有新增和删除属性没方法做数据的响应式解决(通过 $set 解决)
  • 如果给对象的属性赋值为对象,也会对赋值后的对象进行响应式解决

1.2、data 中数组的响应式解决是通过改写数组原型上的七个办法(push/pop/shift/unshift/sort/reverse/splice)

  • 在重写数组原型之前,Vue 给每个响应式数据新增了一个不可枚举的 ob 属性,这个属性指向了 Observer 实例,能够用来避免曾经被响应式解决的数据重复被响应式解决,其次,响应式的数据能够通过 ob 获取到 Observer 实例的相干办法
  • 对于数组的新增操作(push/unshift/splice),会对新增的数据也做响应式解决
  • 通过索引批改数组内容和间接批改数组长度是观测不到的

2、Vue 如何进行依赖收集的?

  • 每个属性都有 dep 实例,dep 实例用来收集它所依赖的 watcher
  • 在模板编译的时候,会取值触发依赖的收集
  • 当属性发生变化时会触发 watcher 更新

3、Vue 的更新粒度是组件级?

  • 首先渲染 watcher 是组件级的。在初始化的时候,会调用 _init 办法,_init 外部会调用 $mount 办法,$mount 办法会调用 mountComponent 办法,mountComponent 办法外部定义了 updateComponent 办法,updateComponent 办法外部就是调用 _update 办法将 vnode 渲染成实在 DOM,mountComponent 办法会 new 一个渲染 watcher,并把 updateComponent 传给渲染 watcher,所以渲染 watcher 能够从新渲染 DOM(试想一下,如果咱们没有把更新 DOM 渲染的办法传递给 watcher,更改数据后,咱们须要手动去调用 DOM 渲染的办法;传递给 watcher 后,数据变动后,能够让 watcher 主动的去调用更新 DOM 渲染的办法)
  • 在 render 函数生成 vnode 时,会判断是否是原生的 HTML 标签,如果不是原生 HEML 标签即是 组件,会创立组件的 vnode,子组件实质是 VueComponent 函数,VueComponent 外部会调用 _init 办法,所以创立子组件 vnode 的时候,也会 new 一个渲染 watcher,所以说渲染 watcher 是组件级的,也就是说 Vue 的更新粒度是组件级的

4、模板编译原理

  • 留神一:咱们平时开发中应用的是不带编译的 Vue 版本(runtime-only),所以在传入选项的时候是不能应用 template 的
  • 留神二:咱们 .vue 文件中的 template 是通过 vue-loader 解决的,vue-loader 其实也是应用 vue-template-compiler 解决
  1. 如果选项 options 里有 render 间接应用 render,如果没有 render 看选项里有没有 tempalte,如果有就用 template,如果没有就看选项里有没有 el,如果有 template = document.querySelector(el),最初用 compileToFunctions(tempalte) 生成 render
    最终都是生成 render 函数,优先级是 render > tempalte > el
  2. 模板编译的整体逻辑次要分为三个局部:
    第一步:将模板字符串转换成 element ASTs (解析器)
    第二步:对 AST 进行动态节点标记,次要用来做虚构 DOM 的渲染优化 (优化器)(进行新旧 vnode 比照的时候能够跳过动态节点)
    第三步:应用 elements ASTs 生成 render 函数代码字符串 (代码生成器)

4.1、生成 AST 的过程

  • 其实就是 while 循环里一直的通过正则匹配字符串,如果是匹配到是开始标签,就触发 start 钩子解决开始标签和属性,如果匹配到文本,就触发 chars 钩子解决文本,如果匹配到完结标签,就调用 end 钩子解决完结标签。解决完后就把模板中曾经匹配到子串截取进去,始终这样循环操作,直到模板的字符串被截取成空串跳出 while 循环。
  • 在匹配到开始标签后,就把开始标签压入栈中,匹配到完结标签就把栈顶元素出栈。第一个进栈的元素就是根节点,除了第一根元素外,其余元素在进栈之前,栈顶的元素就是该元素的父亲节点,所以能够保护元素之间的父子关系(入栈元素的 parent 是栈顶元素,该入栈元素是栈顶元素的儿子),当栈被清空之后,根节点就是生成的 AST 匹配到文本内容是没有子节点的,所以它间接作为栈顶元素的儿子即可。

    4.2、解析器运行过程

  • AST 是用 JS 中的对象来形容节点,一个对象代表一个节点,对象的属性用来保留节点所需的各种数据。
  • 解析器内局部了好几个子解析器,比方 HTML 解析器,文本解析器,过滤器解析器。其中最次要的是 HTML 解析器,HTML 解析器的作用就是解析 HTML,它在解析的过程中会一直的触发各种钩子函数。这些钩子函数包含,开始标签钩子函数 (start)、完结标签钩子函数(end),文本钩子函数(chars) 和正文钩子函数(comment)。
  • 实际上,模板解析的过程就是一直的调用钩子函数的过程,读取 template,应用不同的正则表达式匹配到不同的内容,而后触发对应的钩子函数解决匹配到的字符串截取片段。比方比配到开始标签,触发 start 钩子函数,start 钩子函数解决匹配到开始标签片段,生成一个标签节点增加到形象语法树上。
  • HTML 解析器解析 HTML 的过程就是循环 (while 循环) 的过程,简略来说就是利用 HTML 模板字符串来循环,每轮循环都从 HTML 字符串中截取一小段字符串,反复以上过程,始终到 HTML 字符串被截取成一个空串完结循环,解析结束。
  • 在解析开始标签和完结标签是用栈来保护的,解析到开始标签就压入栈中,解析到完结标签,就从栈顶取出对应的开始标签的 AST,栈顶的前一个开始标签就是该标签的父元素,而后就能够建设父子元素之间的关系。
  • 文本解析器是对 HTML 解析器解析进去的文本进行二次加工。文本分为两种类型,一种是纯文本,一种是带变量的文本。HTML 解析器在解析文本的时候,并不会辨别是纯文本还是带变量的文本,如果是纯文本,不须要进行任何解决,带变量的文本须要文本解析器的进一步解析,因为带变量的文本在应用虚构 DOM 进行渲染时,须要将变量替换成变量中的值。
  • 文本解析器通过正则匹配出变量,把变量改写成 _s(x)的模式增加到数组中

4.3、初始渲染原理

  1. 首先是生成 render 函数
  2. vm._render 函数生成虚构 DOM render 函数次要返回了这样的代码 _c(‘div'{id:”app”},_c(‘div’,undefined,_v(“hello”+_s(name)),_c(‘span’,undefined,_v(“world”)))),所以须要定义 _c, _v, _s 这样的函数能力真正转换成虚构 DOM
  3. vm._update 办法将生成的虚构 DOM 进行实例挂载 update 办法的外围是利用 patch 办法来渲染和更新视图,这里是首次渲染,patch 办法的第一个参数是实在 DOM,更新阶段第一个参数是 oldVnode

5、Vue.mixin 的应用场景和原理

  • Vue.mixin 的作用就是抽离公共的业务逻辑,原理相似“对象的继承”,当组件初始化的时候会调用 mergeOptions 办法进行合并,对于不同的 key(data,hooks,components…)有不同的合并策略。如果混入的数据和组件自身的数据有抵触,会采纳“就近准则”,以组件自身的为准。
  • mixin 有很多的缺点:命名抵触,起源不清晰,依赖问题

6、nextTick 在哪里应用?原理是什么?

  • nextTick 可用于获取更新后的 DOM
  • Vue 的数据更新是异步的,会把所有的数据更新操作都放入工作队列中,而后在 nextTick 中去顺次执行这些工作,nextTick 是一个异步工作,采纳的是优雅降级(Promise -> MutationObserver -> setImmediate -> setTimeout)

7、watch 原理

  1. watch 的应用形式,能够是对象,能够是函数,也能够是数组
  2. 不论是哪种应用形式,watch 的每一个属性对应的函数 (数组的应用形式,数组中的每一项(函数)) 都是一个 用户 watcher,其实现都是调用的 $watch(vm, handler)
  3. $watch 办法的实现都是 new Watcher(),只不过是 options 参数里标记了是用户自定义的 watcher(options.user = true)
  4. watch 的属性对应的函数里有新值和旧值,咱们是如何返回新值和旧值的呢?
  5. new Watcher() 的时候传递的是属性的 key,咱们要把它包装成一个函数(函数外部就是依据 key 取值),赋值给 Watcher 类的 getter 属性,在 Watcher 类实例化的时候,会调用一次 get 办法,咱们就能够拿到它的值(取值同时会进行依赖收集)
  6. 在值更新后,会再次调用 Watcher 类的 get 办法取得新值
  7. 而后判断 watcher 的类型,如果是用户 watcher,执行 callback,把新值旧值传递给 callback
  • watch api 不论是哪种应用形式,最终都是一个 key, 一个函数,对应一个 user watcher,每一个 watcher 都有一个 getter 办法,watch api 对应的 getter 办法是依据 key 来封装的,getter 办法就是取 key 对应的数据,因为 watcher 在初始化的时候默认会调用一次 getter,所以就拿到 key 对应的旧值了,取值也就进行了依赖收集,当 key 对应的数据扭转了,watcher 的 getter 办法会再次执行,这时就拿到了新值,而后调用 key 对应的回调函数,将新值和旧值传给它

8、computed 原理

  1. 每个计算属性实质上也是一个用户 watcher,在它取值的时候进行依赖收集,computed 依赖的值扭转后触发更新
  2. 计算属性的 watcher 在初始化的时候会有两个属性 lazy 和 dirty
  3. watcher 在初始化的时候,会默认调用一次 get 办法,然而 computed 默认是不执行的,所以用 lazy 属性来标记是 computed watcher
  4. computed 是有缓存的,即依赖的值没有产生扭转,屡次获取,是不会屡次调用 watcher 的 get 办法获取值的,所以用 dirty 属性来标记是否须要从新计算值,如果不须要计算,间接返回 watcher 的 value,如果须要计算,再来调用 get 办法获取新的值,再返回 watcher 的 value

补充:什么时候 dirty 的值是 true 呢?

  • computed watcher 初始化的时候
  • computed watcher 依赖的值扭转时(调用了 computed watcher 的 update 办法,即可示意依赖的值扭转了)

9、diff 算法

  1. Vue 的 diff 算法是平级比拟,不思考跨级比拟的状况。外部采纳深度递归和双指针的形式
  2. 首先比对是否是雷同的节点,如果不是删除旧的 DOM,生成新的 DOM 插入
  3. 如果是雷同的节点,比对更新属性
  4. 判断是否是文本节点,如果是,判断文本内容是否雷同,不同更新文本内容
  5. 比对新旧子节点,如果只有新的有子节点,新增子节点插入;如果只有旧的有子节点,将元素的 innerHTML 置为空
  6. 如果新旧都有子节点,比对新旧子节点(采纳双指针)
  7. 顺次是头头、尾尾、头尾、尾头比拟,没有匹配到,就乱序比对
  8. 乱序比对:建设旧的节点的映射表(key->index)
  9. 新的起始节点是否能在旧的映射表中找到,不能找到间接在旧的后面插入,如果找到,将映射表找到的旧的节点,挪动到后面,并将该地位置为 null
  10. 因为在乱序比对中,有将旧节点置为 null 的状况,所以在进行子节点比对前,先判断该节点是否为 null,为 null 顺移
  11. 比对完之后如果新的节点还有,插入新的节点(插入的地位要判断是否在哪里插入),如果旧的节点还有,删除旧的节点(null 的地位跳过)

学习框架技术是为了更好的开发,学习底层原理是为了让产品更好用,更好的让 finclip 小程序在各家平台上更好的兼容和晦涩应用。


如果您有对小程序的设计,研发与应用的相干想法,都欢送登录 FinClip.com 查找答案,如果在应用过程中遇到问题,也欢送与咱们分割。

正文完
 0