🎀 基本概念
一、MVVM 是一种软件架构模式,具体指什么
- Model 是指数据模型,泛指后端的各种业务逻辑处理和数据操控,对于前端而言主要是后端提供的 api 接口
- View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建
- ViewModel 是视图数据层,完全解耦了 View 层和 Model 层
- ViewModel 负责与 Model 层交互,所封装出来的数据模型包括视图的状态和行为两部分。Model 层的数据模型只包含视图的状态
- MVVM 采用
**双向数据绑定**
,ViewModel 的内容会实时展现在 View 层
二、真实DOM
的解析过程? 虚拟DOM
实现原理
1、浏览器渲染引擎工作流程:创建 DOM 树 —> 创建 Style Rules -> 构建 Render 树 —> 布局 Layout -—> 绘制 Painting
- 构建 DOM 树:用 HTML 解析器,解析 HTML 元素,构建一棵 DOM 树;
- 生成样式表:用 CSS 解析器,解析 CSS 文件和元素上的 inline 样式,生成页面的样式表
- 构建 Render 树:将 DOM 树和样式表关联起来(Attachment),每个 DOM 节点都有 attach 方法,接受样式信息,返回一个 render 对象(aka renderer),这些 render 对象最终会被构建成一棵 Render 树
- 布局(确定节点坐标):根据 Render 树结构,为每个 Render 树上的节点确定一个在显示屏上出现的精确坐标
- 绘制页面:根据 Render 树和节点显示坐标,调用每个节点的 paint 方法,调用 GPU 绘制,合成图层
2、Virtual DOM 优缺点及算法主要实现
- 优点:保证性能下限;无需手动操作 DOM;跨平台
- 缺点:无法进行极致优化
- 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象
- diff 算法 — 比较两棵虚拟 DOM 树的差异
- pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树渲染
三、Event Loop 原理分析
1、背景知识:浏览器渲染进程(浏览器内核)
GUI渲染线程
:负责渲染浏览器界面,解析 HTML、CSS、构建 DOM 树和 RenderObject 树,布局和绘制等。可用于重绘(Repaint)、重排(Reflow)JS引擎线程
:js 内核,负责处理 javascript 脚本程序(V8 引擎)事件触发线程
:用来控制事件循环,当对应的事件符合触发条件被触发时,事件线程会把事件添加到待处理事件队列的队尾,等待 js 引擎的处理定时触发器线程
:负责执行异步定时器一类的函数的线程,如: setTimeout,setInterval- 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待 JS 引擎线程执行
异步http请求线程
:负责执行异步请求一类的函数的线程,如: Promise,axios,ajax 等- 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待 JS 引擎线程执行。
注意:GUI 渲染线程和 JS 引擎线程是互斥的,后者执行时前者会被挂起;JS 执行时间过长,会造成页面渲染不连贯,导致页面渲染加载阻塞
2、Event Loop
机制
JS 是单线程的,事件环可以理解为实现异步的一种方式。
- 主线程(
main thread
):JS 引擎线程,主线程的执行过程是一个 tick - 执行栈(
execution stack
):同步任务在主线程上运行,会形成一个执行栈 - 任务队列(
task queue
):由事件触发线程
管理,含宏任务/微任务队列- 异步任务触发时,将异步线程(如
定时触发器线程
、异步http请求线程
)提供的回调事件,缓存到任务队列中 - 异步任务执行时(即主线程空闲时,同步任务执行完毕),将任务队列中的异步任务回调事件,提供给主线程读取,执行栈执行
- 异步任务触发时,将异步线程(如
- 宏任务:macrotask,也叫 tasks。
- script(主代码块)
- setTimeout、setInterval、setImmediate、MessageChannel
- requestAnimationFrame(rAF)、UI Rendering、I/O
- 微任务:microtask,也叫 jobs。当前宏任务执行后立即执行(渲染前)的任务
- Promise(Async/await)、MutationObserver(监听 DOM 修改事件)
- Process.nextTick(Node 独有)
- 区别:事件环迭代开始后,若宏任务队列中安排了新任务,则直到下一次迭代才会运行。每次宏任务退出且执行上下文堆栈为空时,微任务队列中的每个微任务都会按顺序执行,若微任务队列中安排了新任务,微任务会继续执行直到队列为空。
注意:
- 等待
delay
时间后,setTimeout、setInterval 的回调事件才放入任务队列中 - new Promise() 构造函数是宏任务的同步代码,而非微任务
- HTML5 中规定 setTimeout 的最小时间延迟是 4ms
- 动画回调(rAF 回调队列):渲染前使用 rAF
💞 Vue 进阶
1、Vue 如何实现双向数据绑定
- View => Data:视图变化更新数据,通过事件监听的方式实现
- Data => View:数据变化更新视图,数据劫持+发布订阅模式
- 监听器
Observer
:劫持和监听所有属性,属性发生变化,就通知订阅者- 遍历数据对象所有的属性,并使用
Object.defineProperty
把这些属性全部转为getter/setter
,在属性被访问和修改时通知变更
- 遍历数据对象所有的属性,并使用
- 订阅器
Dep
:收集订阅者、数据变化时通知订阅者 - 订阅者
Watcher
:收到属性的变化通知,执行相应的方法来更新视图- 在组件渲染的过程中触发
getter
进行依赖dep
(数据属性记录)收集,把 Watcher 实例存放到对应的 Dep 对象中去 - 依赖项
dep
的setter
触发时,通知watcher
使关联组件重新渲染
- 在组件渲染的过程中触发
- 解析器
Compiler
:解析模板指令,初始化视图;将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器
- 监听器
2、Proxy 与 Object.defineProperty 优劣对比
- Object.defineProperty
- 缺点
- 对于对象:只能对属性进行数据劫持,所以需要深度遍历整个对象
- 对于数组:不能监听到数据的变化,需要遍历数组
- 优点:兼容性好,支持 IE9
- 缺点
- Proxy
- 优点
- 对于对象:可以直接监听对象而非属性
- 对于数组:可以直接监听数组的变化
- 多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的
- 缺点:存在浏览器兼容性问题,而且无法用 polyfill 磨平
- 优点
3、NextTick 原理分析
- Vue.js 默认使用异步执行 DOM 更新
- Vue.nextTick(
vm.$nextTick
):在下次 DOM 更新循环结束之后执行延迟回调,用于获取更新后的 DOM。实现方式(优雅降级
):macrotask
的实现- setImmediate
MessageChannel:
创建一个消息通道,通过其两个 MessagePort 属性发送数据- setTimeout(fn,0)
microtask
的实现:promise,不支持的话直接指向 macro task 的实现
4、Vue 中的 key 有什么作用
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。更新组件时判断两个节点是否相同。相同就复用,不相同就删除旧的创建新的
- 准确: 如果不加 key,那么 vue 会选择复用节点(Vue 的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的 bug
- 快速: key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度仅仅为 O(1)