前端面试之吊打面试官 VUE篇

82 阅读30分钟

 MVVM的理解

MVVM是一种软件架构模式,MVVM 分为 Model、View、ViewModel:

  • Model代表数据模型,数据和业务逻辑都在Model层中定义;
  • View代表UI视图,负责数据的展示;
  • ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;

Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。

MVC

MVC的基本结构
MVC(Model-View-Controller)是桌面开发中常见的软件架构。标准的MVC架构图如下:

​编辑

Model(模型) :负责业务数据管理和处理,包括增删改查。Model必须提供外部可以操作模型数据的接口,同时在数据发生变化后能够通知外部
View(视图) :用户界面。View需要感知Model的变化,数据变化时,更新用户界面
Controller(控制器) :业务逻辑层。Controller需要感知View的事件,调用Model提供的接口对数据进行操作

MVC的工作流程
1、用户对界面进行操作,触发View的相关事件;

2、View感知这些事件,通知Controller进行处理;

3、Controller处理相关业务,并通过Model的接口对业务数据进行更新;

4、Model的业务数据改变触发相应事件,通知View业务数据已经发生改变;

5、View捕获到Model发出的数据改变事件,重新获取数据并更新用户界面。
MVC的优缺点
优点:
降低用户界面与业务逻辑之间的耦合性。大大降低了用户界面和业务逻辑的耦合性,可以隔离模块间的变化,有利于维护和扩展。
复用性高。多个视图可以共享一个模型。
MVC可以将一个项目分为三个子项目分别进行开发和管理,有利于软件工程化,也可实现独立部署。
因为业务逻辑和界面的分离,为业务模块的单元测试创造了条件。

缺点:
不适合中小规模的软件项目。MVC引入了更多概念,增加了项目结构和实现的复杂性,将其应用到中小规模项目通常得不偿失。
更多的内存消耗。Model中的内容都可以从界面获取到,因此,Model的引入增加了内存消耗。
降低了程序性能。因为实现的复杂性,势必会导致性能降低。

vue和react的区别,有什么相同

不同:

  • 模版语法不同,react采用JSX语法,vue使用基于HTML的模版语法
  • 数据绑定不同,vue 使用双向数据绑定,react 则需要手动控制组件的状态和属性。
  • 状态管理不同,vue使用vuex状态管理,react使用redux状态管理
  • 组件通信不同,vue使用props和事件的方式进行父子组件通信,react则通过props和回调函数的方式进行通信。
  • 生命周期不同,vue有8个生命周期钩子,react有10个
  • 响应式原理不同,vue使用双向绑定来实现数据更新,react则通过单向数据流来实现

相同

  • 组件化开发:Vue 和 React 都采用了组件化开发的方式,将用户界面划分为独立、可复用的组件,从而使代码更加模块化、可维护和可扩展。
  • 虚拟 DOM:Vue 和 React 都使用虚拟 DOM 技术,通过在 JavaScript 和真实 DOM 之间建立一个轻量级的虚拟 DOM 层,实现高效的 DOM 更新和渲染。
  • 响应式更新:Vue 和 React 都支持响应式更新,即当数据发生变化时,会自动更新相关的组件和视图,以保持用户界面的同步性。
  • 集成能力:Vue 和 React 都具有良好的集成能力,可以与其他库和框架进行整合,例如 Vue 可以与 Vuex、Vue Router 等配套使用,React 可以与 Redux、React Router 等配套使用。

Vue2和Vue3有哪些区别

  1. 性能优化
    性能优化是 Vue 3 的核心目标之一。通过编译器和虚拟 DOM 的优化升级,Vue 3 能在大规
    模应用中提供更优异的运行性能。例如,Vue 3 的编译工具链接更优化的模板渲染函数,从
    而使得模板编译结果更高效。
  2. Composition API
    Composition API 是 Vue 3 中的重要更新,它允许开发者以函数形式编写逻辑,从而可以将
    同样的逻辑进行更好的复用。在 Vue 2 中,我们使用 Options API,它通常会导致代码成块、
    难以维护,而 Composition API 则可以将逻辑聚合在一起,更加模块化。
  3. Tree-shaking 支持
    Tree-shaking 技术可以在打包时去掉未使用的代码,Vue 3 针对这一点做了优化,支持更好的
    tree-shaking。这意味着当你在项目中只使用 Vue 框架的一部分特性时,未使用的特性将不
    会被打包,从而减少了应用的最终体积。
  4. Fragments
    在 Vue 2 中,由于模板的限制,每个组件必须有一个唯一的根元素。然而在 Vue 3 中,引入
    了 Fragments,这表示组件可以返回多个根节点。这样开发者在设计模板时会更加灵活和方
    便,避免了不必要的 DOM 层级。
  5. Teleport
    Teleport 是 Vue 3 新引入的一个功能,它允许将组件的模板渲染到 DOM 树中的任意位置。
    比如说,模态框内容可以渲染到 body 标签下,而不会受到父组件的约束。这对于处理全局
    模态框、通知等 UI 组件非常有用。
  6. 新的响应式系统
    Vue 3 使用 Proxy 对象代替了 Vue 2 中的 Object.defineProperty 来实现响应式数据。这种方
    法不仅更加简洁,而且性能更高——尤其在处理嵌套对象和数组时效果更显著。这种 Proxy
    方法获得了广泛的支持,并且解决了 Vue 2 中一些边界问题。

SPA的理解,有什么优缺点

SPA(单页应用)是一种前端应用程序的架构模式,它通过在加载应用程序时只加载单个 HTML 页面,并通过使用 JavaScript 动态地更新页面内容,从而实现无刷新的用户体验。

vue样式隔离的原理

  1. 自动添加属性选择器 当你在 <style> 标签中添加 scoped 属性时,Vue 会在编译时自动为该组件的所有样式添加一个独特的属性选择器(如 data-v-xxxx)。这些选择器被附加到组件的根元素以及样式规则中,从而将样式仅限于当前组件。
  2. 动态添加唯一属性 Vue 使用一个唯一的哈希值(例如 data-v-12345678)作为属性选择器,将样式与特定的组件关联。这个哈希值基于组件的内容和位置生成,确保样式的唯一性和准确性。
  3. 样式冲突解决 在多组件样式的情况下,scoped 机制确保了样式不会发生冲突或被意外覆盖,因为它们的作用范围被限定在了当前组件中。

SPA和多页面有什么区别

区别

  • 页面加载方式:在多页面应用中,每个页面都是独立的 HTML 文件,每次导航时需要重新加载整个页面。而在 SPA 中,初始加载时只加载一个 HTML 页面,后续的导航通过 JavaScript 动态地更新页面内容,无需重新加载整个页面。
  • 用户体验:SPA 提供了流畅、快速的用户体验,因为页面切换时无需等待整个页面的重新加载,只有需要的数据和资源会被加载,减少了页面刷新的延迟。多页面应用则可能会有页面刷新的延迟,给用户带来较长的等待时间。
  • 代码复用:SPA 通常采用组件化开发的方式,可以在不同的页面中复用组件,提高代码的可维护性和可扩展性。多页面应用的每个页面都是独立的,组件复用的机会较少。
  • 路由管理:在多页面应用中,页面之间的导航和路由由服务器处理,每个页面对应一个不同的 URL。而在 SPA 中,前端负责管理页面的导航和路由,通过前端路由库(如 React Router 或 Vue Router)来管理不同路径对应的组件。
  • SEO(搜索引擎优化) :由于多页面应用的每个页面都是独立的 HTML 文件,搜索引擎可以直接索引和抓取每个页面的内容,有利于搜索引擎优化。相比之下,SPA 的内容是通过 JavaScript 动态生成的,搜索引擎的爬虫可能无法正确地获取和索引页面的内容,需要采取额外的优化措施。
  • 服务器负载:SPA 只需初始加载时获取 HTML、CSS 和 JavaScript 文件,后续的页面更新和数据获取通常通过 API 请求完成,减轻了服务器的负载。而多页面应用每次导航都需要从服务器获取整个页面的内容。

优点

  • 用户体验:SPA 提供了流畅、快速的用户体验,在页面加载后,只有需要的数据和资源会被加载,减少了页面刷新的延迟。
  • 响应式交互:由于 SPA 依赖于异步数据加载和前端路由,可以实现实时更新和动态加载内容,使用户可以快速地与应用程序交互。
  • 代码复用:SPA 通常采用组件化开发的方式,提高了代码的可维护性和可扩展性。
  • 服务器负载较低:由于只有初始页面加载时需要从服务器获取 HTML、CSS 和 JavaScript 文件,减轻了服务器的负载。

缺点

  • 首次加载时间:SPA 首次加载时需要下载较大的 JavaScript 文件,这可能导致初始加载时间较长。
  • SEO(搜索引擎优化)问题:由于 SPA 的内容是通过 JavaScript 动态生成的,搜索引擎的爬虫可能无法正确地获取和索引页面的内容。
  • 内存占用:SPA 在用户浏览应用程序时保持单个页面的状态,这可能导致较高的内存占用。
  • 安全性:由于 SPA 通常使用 API 进行数据获取,因此需要特别注意安全性。

Vue的性能优化有哪些

编码阶段

  • v-if和v-for不一起使用
  • v-for保证key的唯一性
  • 使用keep-alive缓存组件
  • v-if和v-show酌情使用
  • 路由懒加载、异步组件
  • 图片懒加载
  • 节流防抖
  • 第三方模块按需引入
  • 服务端与渲染

打包优化

  • 压缩代码
  • 使用CDN加载第三方模块
  • 抽离公共文件

用户体验

  • 骨架屏
  • 客户端缓存

SEO优化

  • 预渲染
  • 服务端渲染
  • 合理使用 meta 标签

Vue生命周期

创建前后:

  • beforeCreate(创建前): 数据观测和初始化事件还未开始,不能访问data、computed、watch、methods上的数据方法。
  • created(创建后):实例创建完成,可以访问data、computed、watch、methods上的数据方法,但此时渲染节点还未挂在到DOM上,所以不能访问。

挂载前后:

  • beforeMount(挂载前): Vue实例还未挂在到页面HTML上,此时可以发起服务器请求
  • mounted(挂载后):Vue实例已经挂在完毕,可以操作DOM

更新前后:

  • beforeUpdate(更新前): 数据更新之前调用,还未渲染页面
  • updated(更新后):DOM重新渲染,此时数据和界面都是新的。

销毁前后:

  • beforeDestorye(销毁前):实例销毁前调用,这时候能够获取到this
  • destoryed(销毁后):实例销毁后调用,实例完全被销毁。

编辑

常用的属性、指令有哪些

属性

  • data:用于定义组件的初始数据。
  • props:用于传递数据给子组件。
  • computed:用于定义计算属性。
  • methods:用于定义组件的方法。
  • watch:用于监听组件的数据变化。
  • components:用于注册子组件。可以通过 components 属性将其他组件注册为当前组件的子组件,从而在模板中使用这些子组件。

指令

  • v-if:条件渲染指令,根据表达式的真假来决定是否渲染元素。
  • v-show:条件显示指令,根据表达式的真假来决定元素的显示和隐藏。
  • v-for:列表渲染指令,用于根据数据源循环渲染元素列表。
  • v-bind:属性绑定指令,用于动态绑定元素属性到 Vue 实例的数据。
  • v-on:事件绑定指令,用于监听 DOM 事件,并执行对应的 Vue 方法。
  • v-model:双向数据绑定指令,用于在表单元素和 Vue 实例的数据之间建立双向绑定关系。
  • v-text:文本插值指令,用于将数据插入到元素的文本内容中。
  • v-html:HTML 插值指令,用于将数据作为 HTML 解析并插入到元素中。

Computed 和 Watch 的区别

computed计算属性,通过对已有的属性值进行计算得到一个新值。它需要依赖于其他的数据,当数据发生变化时,computed会自动计算更新。computed属性值会被缓存,只有当依赖数据发生变化时才会重新计算,这样可以避免重复计算提高性能。

  • 缓存机制:计算属性的核心特点是 缓存。Vue 会缓存计算属性的值,只有当它依赖的数据发生变化时,计算属性的值才会重新计算。这个缓存机制是通过一个 dirty flag(脏标志) 实现的。第一次访问计算属性时,它会执行计算,并将计算结果存储下来。如果依赖的数据没有变化,再次访问计算属性时,直接返回缓存值,而不重新计算。
  • 依赖追踪:在计算属性的 getter 函数执行期间,Vue 会追踪它所使用的响应式数据(即依赖)。Vue 会将这些依赖项记录下来,当依赖项发生变化时,计算属性会标记为脏,并在下一次访问时重新计算。

计算属性的实现大致如下:

  1. 初始化阶段:当组件创建时,Vue 会遍历 computed 属性,并为每个计算属性定义一个 getter 函数。
  2. 第一次访问计算属性时
    • 计算属性的 getter 会被调用,触发对依赖项的访问。
    • Vue 会将这些依赖项记录下来,并计算出计算属性的值。
    • 计算属性的值被缓存并返回。
  1. 依赖项更新时
    • 如果计算属性依赖的响应式数据发生变化,Vue 会标记计算属性为 "脏"(dirty)。
    • 下一次访问该计算属性时,Vue 会重新执行 getter 函数,重新计算属性的值,并更新缓存。
  1. 后续访问计算属性时
    • 如果计算属性的值没有脏标记,Vue 会直接返回缓存的值,而不重新计算。

watch用于监听数据的变化,并在变化时执行一些操作。它可以监听单个数据或者数组,当数据发生变化时会执行对应的回调函数,和computed不同的是watch不会有缓存。

Vue组件通信

父传子

  • props
  • $children
  • $refs

子传父

  • $emit
  • $parent

兄弟组件

  • provied
  • inject
  • eventBus
  • Vuex
  • 使用父组件作为中介。

常见的事件修饰符及其作用

  • .stop阻止冒泡
  • .prevent阻止默认事件
  • .capture :与事件冒泡的方向相反,事件捕获由外到内;
  • .self :只会触发自己范围内的事件,不包含子元素;
  • .once:只会触发一次。

v-if和v-show的区别

v-if元素不可见,直接删除DOM,有更高的切换消耗。 v-show通过设置元素display: none控制显示隐藏,更高的初始渲染消耗。

v-html 的原理

会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值。

v-model 是如何实现的,语法糖实际是什么?

Vue 中数据双向绑定是一个指令v-model,可以绑定一个响应式数据到视图,同时视图的变化能改变该值。

  • 当作用在表单上:通过v-bind:value绑定数据,v-on:input来监听数据变化并修改value
  • 当作用在组件上:本质上是一个父子通信语法糖,通过props和$emit实现。

data为什么是一个函数而不是对象

因为对象是一个引用类型,如果data是一个对象的情况下会造成多个组件共用一个data,data为一个函数,每个组件都会有自己的私有数据空间,不会干扰其他组件的运行。

mixin 和 mixins 区别

  • mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。
  • mixins 应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。

路由的hash和history模式的区别

hash模式 开发中默认的模式,地址栏URL后携带#,后面为路由。 原理是通过onhashchange()事件监听hash值变化,在页面hash值发生变化后,window就可以监听到事件改变,并按照规则加载相应的代码。hash值变化对应的URL都会被记录下来,这样就能实现浏览器历史页面前进后退。

history模式history模式中URL没有#,这样相对hash模式更好看,但是需要后台配置支持。

history原理是使用HTML5 history提供的pushState、replaceState两个API,用于浏览器记录历史浏览栈,并且在修改URL时不会触发页面刷新和后台数据请求。

router和route的区别

  • $route 是路由信息,包括path、params、query、name等路由信息参数
  • $router 是路由实例,包含了路由跳转方法、钩子函数等

如何设置动态路由

  • params传参
    • 路由配置: /index/:id
    • 路由跳转:this.$router.push({name: 'index', params: {id: "zs"}});
    • 路由参数获取:$route.params.id
    • 最后形成的路由:/index/zs
  • query传参
    • 路由配置:/index正常的路由配置
    • 路由跳转:this.$rouetr.push({path: 'index', query:{id: "zs"}});
    • 路由参数获取:$route.query.id
    • 最后形成的路由:/index?id=zs

区别

  • 获取参数方式不一样,一个通过route.params,一个通过route.params,一个通过 route.query
  • 参数的生命周期不一样,query参数在URL地址栏中显示不容易丢失,params参数不会在地址栏显示,刷新后会消失

路由守卫

  • 全局前置钩子:beforeEach、beforeResolve、afterEach
  • 路由独享守卫:beforeEnter
  • 组件内钩子:beforeRouterEnter、beforeRouterUpdate、beforeRouterLeave

Vue中key的作用

key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,减少DOM操作量,提高性能。

为什么不建议用index作为key?

如果将数组下标作为key值,那么当列表发生变化时,可能会导致key值发生改变,从而引发不必要的组件重新渲染,甚至会导致性能问题。例如,当删除列表中某个元素时,其后面的所有元素的下标都会发生改变,导致Vue重新渲染整个列表。

为什么v-for和v-if不能一起使用

v-for比v-if优先级更高,一起使用的话每次渲染列表时都要执行一次条件判断,造成不必要的计算,影响性能。

原理知识

双向数据绑定的原理

采用数据劫持结合发布者-订阅者模式的方式,data数据在初始化的时候,会实例化一个Observe类,在它会将data数据进行递归遍历,并通过Object.defineProperty方法,给每个值添加上一个getter和一个setter。在数据读取的时候会触发getter进行依赖(Watcher)收集,当数据改变时,会触发setter,对刚刚收集的依赖进行触发,并且更新watcher通知视图进行渲染。

编辑

原理解释

在Vue.js中,双向绑定是一种核心的特性,它允许视图(View)和数据模型(Model)之间的自动同步。这种双向绑定机制使得当数据发生变化时,视图会自动更新;反之,当视图中的输入发生变化时,数据模型也会相应更新。

发布者-订阅者模式在Vue.js的实现中起着重要作用,帮助实现了双向绑定的功能。下面结合Vue.js的双向绑定机制来分析发布者-订阅者模式的应用:

  1. 数据驱动视图
    • 在Vue.js中,数据模型(Model)充当了发布者的角色,负责存储应用的状态数据。当数据发生变化时,Vue.js会自动通知相关的订阅者(视图),从而更新视图的显示。
  1. 订阅者更新视图
    • Vue组件中的模板(Template)和数据模型之间建立了订阅关系。当数据模型发生变化时,与之相关联的视图会自动更新,这就是订阅者(视图)接收到发布者(数据模型)通知后执行的更新操作。
  1. 视图修改数据
    • 同样地,当视图中的输入发生变化时(比如input框内的文本变化),Vue.js会自动更新数据模型,实现了从视图到数据模型的反向更新。
  1. 事件总线
    • 在Vue.js内部,采用了类似发布者-订阅者模式的事件系统来进行数据的变化通知和处理。当数据发生变化时,Vue实例会通过事件总线来通知相关的订阅者(观察者),从而更新视图。

总的来说,Vue.js的双向绑定机制借鉴了发布者-订阅者模式的思想,通过数据模型作为发布者,视图作为订阅者,以及内部的事件总线机制,实现了数据和视图之间的自动同步。这种设计架构使得开发者无需手动管理数据和视图的更新,极大地简化了前端开发的复杂度,提高了开发效率。

使用 Object.defineProperty() 来进行数据劫持有什么缺点?

该方法只能监听到数据的修改,监听不到数据的新增和删除,从而不能触发组件更新渲染。vue2中会对数组的新增删除方法push、pop、shift、unshift、splice、sort、reserve通过重写的形式,在拦截里面进行手动收集触发依赖更新。

和Vue3相比有什么区别?

Vue3采用了Proxy代理的方式,Proxy是ES6引入的一个新特性,它提供了一个用于创建代理对象的构造函数。它是对整个对象的监听和拦截,可以对对象所有操作进行处理。而Object.defineProperty只能监听单个属性的读写,无法监听新增、删除等操作。

Vue是如何收集依赖的?

依赖收集发生在defineReactive()方法中,在方法内new Dep()实例化一个Dep()实例,然后在getter中通过dep.depend()方法对数据依赖进行收集,然后在settter中通过dep.notify()通知更新。整个Dep其实就是一个观察者,吧收集的依赖存储起来,在需要的时候进行调用。在收集数据依赖的时候,会为数据创建一个Watcher,当数据发生改变通知每个Watcher,由Wathcer进行更新渲染。

slot是什么?有什么作用?原理是什么?

slot插槽,一般在封装组件的时候使用,在组件内不知道以那种形式来展示内容时,可以用slot来占据位置,最终展示形式由父组件以内容形式传递过来,主要分为三种:

  • 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
  • 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
  • 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。

实现原理:当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.slot中,默认插槽为vm.slot中,默认插槽为vm.slot.default,具名插槽为vm.slot.xxxxxx为插槽名,当组件执行渲染函数时候,遇到slot标签,使用slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到slot标签,使用slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

对keep-alive的理解,它是如何实现的,具体缓存的是什么?

keep-alive是Vue.js的一个内置组件。它能够将不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。

  • include 字符串或正则表达式,只有名称匹配的组件会被匹配;
  • exclude 字符串或正则表达式,任何名称匹配的组件都不会被缓存;
  • max 数字,最多可以缓存多少组件实例。

2 个生命周期 activated , deactivated

  • activated:当缓存的组件被激活时,该钩子函数被调用。可以在该钩子函数中进行一些状态恢复、数据更新等操作。
  • deactivated:当缓存的组件被停用时,该钩子函数被调用。可以在该钩子函数中进行一些状态保存、数据清理等操作。

keep-alive内部其实是一个函数式组件,没有template标签。在render中通过获取组件的name和include、exclude进行匹配。匹配不成功,则不需要进行缓存,直接返回该组件的vnode。

匹配成功就进行缓存,获取组件的key在cache中进行查找,如果存在,则将他原来位置上的 key 给移除,同时将这个组件的 key 放到数组最后面(LRU)也就实现了max功能。

不存在的话,就需要对组件进行缓存。将当前组件push(key)添加到尾部,然后再判断当前缓存的max是否超出指定个数,如果超出直接将第一个组件销毁(缓存淘汰策略LRU)。

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

$nextTick 原理及作用

在Vue.js中,this.$nextTick()方法是用来在DOM更新之后执行延迟回调函数的一种方式。它的原理涉及到Vue.js的更新队列和事件循环机制。

下面是this.$nextTick()方法的原理:

  1. 更新队列:
    • 当数据发生变化时,Vue.js并不会立即更新DOM,而是将需要更新的操作放入一个队列中。
  1. 异步更新:
    • Vue.js利用JavaScript的事件循环机制,在当前事件循环结束时(即当前任务执行完毕后),才会去执行更新队列中的操作。
  1. $nextTick()方法:
    • nextTick()方法就是用来在下次DOM更新循环结束之后执行回调函数的工具。通过调用this.nextTick()方法就是用来在下次DOM更新循环结束之后执行回调函数的工具。通过调用this.nextTick(callback),可以确保在DOM更新完成后执行特定的逻辑。
  1. 实现原理:
    • 当调用this.$nextTick(callback)时,Vue.js会将该回调函数推入微任务队列(microtask queue)中,等待当前任务执行完毕后立即执行。这样可以保证回调函数在DOM更新后被触发,从而获取到最新的DOM状态。

总的来说,this.$nextTick()方法的原理是利用JavaScript的事件循环机制和微任务队列,在当前事件循环结束时执行回调函数,确保回调函数在DOM更新完成后被触发。这样可以有效地处理DOM更新后的逻辑或操作,确保操作基于最新的DOM状态。

Vue模版编译原理

模版编译主要过程:template ---> ast ---> render,分别对象三个方法

  • parse 函数解析 template
  • optimize 函数优化静态内容
  • generate 函数创建 render 函数字符串

调用parse方法,将template转化为AST(抽象语法树),AST定义了三种类型,一种html标签,一种文本,一种插值表达式,并且通过 children 这个字段层层嵌套形成了树状的结构。

optimize方法对AST树进行静态内容优化,分析出哪些是静态节点,给其打一个标记,为后续更新渲染可以直接跳过静态节点做优化。

generate将AST抽象语法树编译成 render字符串,最后通过new Function(render)生成可执行的render函数

Vuex

Vuex 的原理

Vuex是专门为Vue设计的状态管理,当Vue从store中读取数据后,数据发生改变,组件中的数据也会发生变化。

​编辑

  1. State:存储共享的状态数据。
  2. Getters:从 state 派生出状态,或者计算得到的值。
  3. Mutations:同步的函数,用于改变 state
  4. Actions:处理异步操作,通过 commit 提交 mutations 来间接改变 state
  5. Modules:将 Vuex store 分成多个模块,用于管理大型应用中的状态。

Vue组件接收交互行为,调用dispatch方法触发action相关处理,若页面状态需要改变,则调用commit方法提交mutation修改state,通过getters获取到state新值,重新渲染Vue Components,界面随之更新。

Vuex中action和mutation的区别

  • mutation更专注于修改state,必须是同步执行。
  • action提交的是mutation,而不是直接更新数据,可以是异步的,如业务代码,异步请求。
  • action可以包含多个mutation

Vuex 和 localStorage 的区别

  • Vuex存储在内存中,页面关闭刷新就会消失。而localstorage存储在本地,读取内存比读取硬盘速度要快
  • Vuex应用于组件之间的传值,localstorage主要用于不同页面之间的传递
  • Vuex是响应式的,localstorage需要刷新

虚拟DOM

对虚拟DOM的理解

虚拟DOM就是用JS对象来表述DOM节点,是对真实DOM的一层抽象。可以通过一些列操作使这个棵树映射到真实DOM上。

如在Vue中,会把代码转换为虚拟DOM,在最终渲染到页面,在每次数据发生变化前,都会缓存一份虚拟DOM,通过diff算法来对比新旧虚拟DOM记录到一个对象中按需更新,最后创建真实DOM,从而提升页面渲染性能。

虚拟DOM就一定比真实DOM更快吗

虚拟DOM不一定比真实DOM更快,而是在特定情况下可以提供更好的性能。

在复杂情况下,虚拟DOM可以比真实DOM操作更快,因为它是在内存中维护一个虚拟的DOM树,将真实DOM操作转换为对虚拟DOM的操作,然后通过diff算法找出需要更新的部分,最后只变更这部分到真实DOM就可以。在频繁变更下,它可以批量处理这些变化从而减少对真实DOM的访问和操作,减少浏览器的回流重绘,提高页面渲染性能。

而在一下简单场景下,直接操作真实DOM可能会更快,当更新操作很少或者只是局部改变时,直接操作真实DOM比操作虚拟DOM更高效,省去了虚拟DOM的计算、对比开销。

虚拟DOM的解析过程

  • 首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含 TagName、props 和 Children 这些属性。然后将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。
  • 当页面的状态发生改变,需要对页面的 DOM 的结构进行调整的时候,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。
  • 最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。

DIFF算法原理

diff的目的是找出差异,最小化的更新视图。 diff算法发生在视图更新阶段,当数据发生变化的时候,diff会对新旧虚拟DOM进行对比,只渲染有变化的部分。

  1. 对比是不是同类型标签,不是同类型直接替换
  2. 如果是同类型标签,执行patchVnode方法,判断新旧vnode是否相等。如果相等,直接返回。
  3. 新旧vnode不相等,需要比对新旧节点,比对原则是以新节点为主,主要分为以下几种。
    1. newVnode 和 oldVnode都有文本节点,用新节点替换旧节点。
    2. newVnode有子节点,oldVnode没有,新增newVnode的子节点。
    3. newVnode没有子节点,oldVnode有子节点,删除oldVnode中的子节点。
    4. newVnode和oldVnode都有子节点,通过updateChildren对比子节点。

双端diff

updateChildren方法用来对比子节点是否相同,将新旧节点同级进行比对,减少比对次数。会创建4个指针,分别指向新旧两个节点的首尾,首和尾指针向中间移动。

每次对比下两个头指针指向的节点、两个尾指针指向的节点,头和尾指向的节点,是不是 key是一样的,也就是可复用的。如果是重复的,直接patch更新一下,如果是头尾节点,需要进行移动位置,结果以新节点的为主。

如果都没有可以复用的节点,就从旧的vnode中查找,然后进行移动,没有找到就插入一个新节点。

当比对结束后,此时新节点还有剩余,就批量增加,如果旧节点有剩余就批量删除。​编辑

​​

 MVVM的理解

MVVM是一种软件架构模式,MVVM 分为 Model、View、ViewModel:

  • Model代表数据模型,数据和业务逻辑都在Model层中定义;
  • View代表UI视图,负责数据的展示;
  • ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作;

Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。

MVC

MVC的基本结构
MVC(Model-View-Controller)是桌面开发中常见的软件架构。标准的MVC架构图如下:

​编辑

Model(模型) :负责业务数据管理和处理,包括增删改查。Model必须提供外部可以操作模型数据的接口,同时在数据发生变化后能够通知外部
View(视图) :用户界面。View需要感知Model的变化,数据变化时,更新用户界面
Controller(控制器) :业务逻辑层。Controller需要感知View的事件,调用Model提供的接口对数据进行操作

MVC的工作流程
1、用户对界面进行操作,触发View的相关事件;

2、View感知这些事件,通知Controller进行处理;

3、Controller处理相关业务,并通过Model的接口对业务数据进行更新;

4、Model的业务数据改变触发相应事件,通知View业务数据已经发生改变;

5、View捕获到Model发出的数据改变事件,重新获取数据并更新用户界面。
MVC的优缺点
优点:
降低用户界面与业务逻辑之间的耦合性。大大降低了用户界面和业务逻辑的耦合性,可以隔离模块间的变化,有利于维护和扩展。
复用性高。多个视图可以共享一个模型。
MVC可以将一个项目分为三个子项目分别进行开发和管理,有利于软件工程化,也可实现独立部署。
因为业务逻辑和界面的分离,为业务模块的单元测试创造了条件。

缺点:
不适合中小规模的软件项目。MVC引入了更多概念,增加了项目结构和实现的复杂性,将其应用到中小规模项目通常得不偿失。
更多的内存消耗。Model中的内容都可以从界面获取到,因此,Model的引入增加了内存消耗。
降低了程序性能。因为实现的复杂性,势必会导致性能降低。

vue和react的区别,有什么相同

不同:

  • 模版语法不同,react采用JSX语法,vue使用基于HTML的模版语法
  • 数据绑定不同,vue 使用双向数据绑定,react 则需要手动控制组件的状态和属性。
  • 状态管理不同,vue使用vuex状态管理,react使用redux状态管理
  • 组件通信不同,vue使用props和事件的方式进行父子组件通信,react则通过props和回调函数的方式进行通信。
  • 生命周期不同,vue有8个生命周期钩子,react有10个
  • 响应式原理不同,vue使用双向绑定来实现数据更新,react则通过单向数据流来实现

相同

  • 组件化开发:Vue 和 React 都采用了组件化开发的方式,将用户界面划分为独立、可复用的组件,从而使代码更加模块化、可维护和可扩展。
  • 虚拟 DOM:Vue 和 React 都使用虚拟 DOM 技术,通过在 JavaScript 和真实 DOM 之间建立一个轻量级的虚拟 DOM 层,实现高效的 DOM 更新和渲染。
  • 响应式更新:Vue 和 React 都支持响应式更新,即当数据发生变化时,会自动更新相关的组件和视图,以保持用户界面的同步性。
  • 集成能力:Vue 和 React 都具有良好的集成能力,可以与其他库和框架进行整合,例如 Vue 可以与 Vuex、Vue Router 等配套使用,React 可以与 Redux、React Router 等配套使用。

Vue2和Vue3有哪些区别

  1. 性能优化
    性能优化是 Vue 3 的核心目标之一。通过编译器和虚拟 DOM 的优化升级,Vue 3 能在大规
    模应用中提供更优异的运行性能。例如,Vue 3 的编译工具链接更优化的模板渲染函数,从
    而使得模板编译结果更高效。
  2. Composition API
    Composition API 是 Vue 3 中的重要更新,它允许开发者以函数形式编写逻辑,从而可以将
    同样的逻辑进行更好的复用。在 Vue 2 中,我们使用 Options API,它通常会导致代码成块、
    难以维护,而 Composition API 则可以将逻辑聚合在一起,更加模块化。
  3. Tree-shaking 支持
    Tree-shaking 技术可以在打包时去掉未使用的代码,Vue 3 针对这一点做了优化,支持更好的
    tree-shaking。这意味着当你在项目中只使用 Vue 框架的一部分特性时,未使用的特性将不
    会被打包,从而减少了应用的最终体积。
  4. Fragments
    在 Vue 2 中,由于模板的限制,每个组件必须有一个唯一的根元素。然而在 Vue 3 中,引入
    了 Fragments,这表示组件可以返回多个根节点。这样开发者在设计模板时会更加灵活和方
    便,避免了不必要的 DOM 层级。
  5. Teleport
    Teleport 是 Vue 3 新引入的一个功能,它允许将组件的模板渲染到 DOM 树中的任意位置。
    比如说,模态框内容可以渲染到 body 标签下,而不会受到父组件的约束。这对于处理全局
    模态框、通知等 UI 组件非常有用。
  6. 新的响应式系统
    Vue 3 使用 Proxy 对象代替了 Vue 2 中的 Object.defineProperty 来实现响应式数据。这种方
    法不仅更加简洁,而且性能更高——尤其在处理嵌套对象和数组时效果更显著。这种 Proxy
    方法获得了广泛的支持,并且解决了 Vue 2 中一些边界问题。

SPA的理解,有什么优缺点

SPA(单页应用)是一种前端应用程序的架构模式,它通过在加载应用程序时只加载单个 HTML 页面,并通过使用 JavaScript 动态地更新页面内容,从而实现无刷新的用户体验。

vue样式隔离的原理

  1. 自动添加属性选择器 当你在 <style> 标签中添加 scoped 属性时,Vue 会在编译时自动为该组件的所有样式添加一个独特的属性选择器(如 data-v-xxxx)。这些选择器被附加到组件的根元素以及样式规则中,从而将样式仅限于当前组件。
  2. 动态添加唯一属性 Vue 使用一个唯一的哈希值(例如 data-v-12345678)作为属性选择器,将样式与特定的组件关联。这个哈希值基于组件的内容和位置生成,确保样式的唯一性和准确性。
  3. 样式冲突解决 在多组件样式的情况下,scoped 机制确保了样式不会发生冲突或被意外覆盖,因为它们的作用范围被限定在了当前组件中。

SPA和多页面有什么区别

区别

  • 页面加载方式:在多页面应用中,每个页面都是独立的 HTML 文件,每次导航时需要重新加载整个页面。而在 SPA 中,初始加载时只加载一个 HTML 页面,后续的导航通过 JavaScript 动态地更新页面内容,无需重新加载整个页面。
  • 用户体验:SPA 提供了流畅、快速的用户体验,因为页面切换时无需等待整个页面的重新加载,只有需要的数据和资源会被加载,减少了页面刷新的延迟。多页面应用则可能会有页面刷新的延迟,给用户带来较长的等待时间。
  • 代码复用:SPA 通常采用组件化开发的方式,可以在不同的页面中复用组件,提高代码的可维护性和可扩展性。多页面应用的每个页面都是独立的,组件复用的机会较少。
  • 路由管理:在多页面应用中,页面之间的导航和路由由服务器处理,每个页面对应一个不同的 URL。而在 SPA 中,前端负责管理页面的导航和路由,通过前端路由库(如 React Router 或 Vue Router)来管理不同路径对应的组件。
  • SEO(搜索引擎优化) :由于多页面应用的每个页面都是独立的 HTML 文件,搜索引擎可以直接索引和抓取每个页面的内容,有利于搜索引擎优化。相比之下,SPA 的内容是通过 JavaScript 动态生成的,搜索引擎的爬虫可能无法正确地获取和索引页面的内容,需要采取额外的优化措施。
  • 服务器负载:SPA 只需初始加载时获取 HTML、CSS 和 JavaScript 文件,后续的页面更新和数据获取通常通过 API 请求完成,减轻了服务器的负载。而多页面应用每次导航都需要从服务器获取整个页面的内容。

优点

  • 用户体验:SPA 提供了流畅、快速的用户体验,在页面加载后,只有需要的数据和资源会被加载,减少了页面刷新的延迟。
  • 响应式交互:由于 SPA 依赖于异步数据加载和前端路由,可以实现实时更新和动态加载内容,使用户可以快速地与应用程序交互。
  • 代码复用:SPA 通常采用组件化开发的方式,提高了代码的可维护性和可扩展性。
  • 服务器负载较低:由于只有初始页面加载时需要从服务器获取 HTML、CSS 和 JavaScript 文件,减轻了服务器的负载。

缺点

  • 首次加载时间:SPA 首次加载时需要下载较大的 JavaScript 文件,这可能导致初始加载时间较长。
  • SEO(搜索引擎优化)问题:由于 SPA 的内容是通过 JavaScript 动态生成的,搜索引擎的爬虫可能无法正确地获取和索引页面的内容。
  • 内存占用:SPA 在用户浏览应用程序时保持单个页面的状态,这可能导致较高的内存占用。
  • 安全性:由于 SPA 通常使用 API 进行数据获取,因此需要特别注意安全性。

Vue的性能优化有哪些

编码阶段

  • v-if和v-for不一起使用
  • v-for保证key的唯一性
  • 使用keep-alive缓存组件
  • v-if和v-show酌情使用
  • 路由懒加载、异步组件
  • 图片懒加载
  • 节流防抖
  • 第三方模块按需引入
  • 服务端与渲染

打包优化

  • 压缩代码
  • 使用CDN加载第三方模块
  • 抽离公共文件

用户体验

  • 骨架屏
  • 客户端缓存

SEO优化

  • 预渲染
  • 服务端渲染
  • 合理使用 meta 标签

Vue生命周期

创建前后:

  • beforeCreate(创建前): 数据观测和初始化事件还未开始,不能访问data、computed、watch、methods上的数据方法。
  • created(创建后):实例创建完成,可以访问data、computed、watch、methods上的数据方法,但此时渲染节点还未挂在到DOM上,所以不能访问。

挂载前后:

  • beforeMount(挂载前): Vue实例还未挂在到页面HTML上,此时可以发起服务器请求
  • mounted(挂载后):Vue实例已经挂在完毕,可以操作DOM

更新前后:

  • beforeUpdate(更新前): 数据更新之前调用,还未渲染页面
  • updated(更新后):DOM重新渲染,此时数据和界面都是新的。

销毁前后:

  • beforeDestorye(销毁前):实例销毁前调用,这时候能够获取到this
  • destoryed(销毁后):实例销毁后调用,实例完全被销毁。

编辑

常用的属性、指令有哪些

属性

  • data:用于定义组件的初始数据。
  • props:用于传递数据给子组件。
  • computed:用于定义计算属性。
  • methods:用于定义组件的方法。
  • watch:用于监听组件的数据变化。
  • components:用于注册子组件。可以通过 components 属性将其他组件注册为当前组件的子组件,从而在模板中使用这些子组件。

指令

  • v-if:条件渲染指令,根据表达式的真假来决定是否渲染元素。
  • v-show:条件显示指令,根据表达式的真假来决定元素的显示和隐藏。
  • v-for:列表渲染指令,用于根据数据源循环渲染元素列表。
  • v-bind:属性绑定指令,用于动态绑定元素属性到 Vue 实例的数据。
  • v-on:事件绑定指令,用于监听 DOM 事件,并执行对应的 Vue 方法。
  • v-model:双向数据绑定指令,用于在表单元素和 Vue 实例的数据之间建立双向绑定关系。
  • v-text:文本插值指令,用于将数据插入到元素的文本内容中。
  • v-html:HTML 插值指令,用于将数据作为 HTML 解析并插入到元素中。

Computed 和 Watch 的区别

computed计算属性,通过对已有的属性值进行计算得到一个新值。它需要依赖于其他的数据,当数据发生变化时,computed会自动计算更新。computed属性值会被缓存,只有当依赖数据发生变化时才会重新计算,这样可以避免重复计算提高性能。

  • 缓存机制:计算属性的核心特点是 缓存。Vue 会缓存计算属性的值,只有当它依赖的数据发生变化时,计算属性的值才会重新计算。这个缓存机制是通过一个 dirty flag(脏标志) 实现的。第一次访问计算属性时,它会执行计算,并将计算结果存储下来。如果依赖的数据没有变化,再次访问计算属性时,直接返回缓存值,而不重新计算。
  • 依赖追踪:在计算属性的 getter 函数执行期间,Vue 会追踪它所使用的响应式数据(即依赖)。Vue 会将这些依赖项记录下来,当依赖项发生变化时,计算属性会标记为脏,并在下一次访问时重新计算。

计算属性的实现大致如下:

  1. 初始化阶段:当组件创建时,Vue 会遍历 computed 属性,并为每个计算属性定义一个 getter 函数。
  2. 第一次访问计算属性时
    • 计算属性的 getter 会被调用,触发对依赖项的访问。
    • Vue 会将这些依赖项记录下来,并计算出计算属性的值。
    • 计算属性的值被缓存并返回。
  1. 依赖项更新时
    • 如果计算属性依赖的响应式数据发生变化,Vue 会标记计算属性为 "脏"(dirty)。
    • 下一次访问该计算属性时,Vue 会重新执行 getter 函数,重新计算属性的值,并更新缓存。
  1. 后续访问计算属性时
    • 如果计算属性的值没有脏标记,Vue 会直接返回缓存的值,而不重新计算。

watch用于监听数据的变化,并在变化时执行一些操作。它可以监听单个数据或者数组,当数据发生变化时会执行对应的回调函数,和computed不同的是watch不会有缓存。

Vue组件通信

父传子

  • props
  • $children
  • $refs

子传父

  • $emit
  • $parent

兄弟组件

  • provied
  • inject
  • eventBus
  • Vuex
  • 使用父组件作为中介。

常见的事件修饰符及其作用

  • .stop阻止冒泡
  • .prevent阻止默认事件
  • .capture :与事件冒泡的方向相反,事件捕获由外到内;
  • .self :只会触发自己范围内的事件,不包含子元素;
  • .once:只会触发一次。

v-if和v-show的区别

v-if元素不可见,直接删除DOM,有更高的切换消耗。 v-show通过设置元素display: none控制显示隐藏,更高的初始渲染消耗。

v-html 的原理

会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值。

v-model 是如何实现的,语法糖实际是什么?

Vue 中数据双向绑定是一个指令v-model,可以绑定一个响应式数据到视图,同时视图的变化能改变该值。

  • 当作用在表单上:通过v-bind:value绑定数据,v-on:input来监听数据变化并修改value
  • 当作用在组件上:本质上是一个父子通信语法糖,通过props和$emit实现。

data为什么是一个函数而不是对象

因为对象是一个引用类型,如果data是一个对象的情况下会造成多个组件共用一个data,data为一个函数,每个组件都会有自己的私有数据空间,不会干扰其他组件的运行。

mixin 和 mixins 区别

  • mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。
  • mixins 应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。

路由的hash和history模式的区别

hash模式 开发中默认的模式,地址栏URL后携带#,后面为路由。 原理是通过onhashchange()事件监听hash值变化,在页面hash值发生变化后,window就可以监听到事件改变,并按照规则加载相应的代码。hash值变化对应的URL都会被记录下来,这样就能实现浏览器历史页面前进后退。

history模式history模式中URL没有#,这样相对hash模式更好看,但是需要后台配置支持。

history原理是使用HTML5 history提供的pushState、replaceState两个API,用于浏览器记录历史浏览栈,并且在修改URL时不会触发页面刷新和后台数据请求。

router和route的区别

  • $route 是路由信息,包括path、params、query、name等路由信息参数
  • $router 是路由实例,包含了路由跳转方法、钩子函数等

如何设置动态路由

  • params传参
    • 路由配置: /index/:id
    • 路由跳转:this.$router.push({name: 'index', params: {id: "zs"}});
    • 路由参数获取:$route.params.id
    • 最后形成的路由:/index/zs
  • query传参
    • 路由配置:/index正常的路由配置
    • 路由跳转:this.$rouetr.push({path: 'index', query:{id: "zs"}});
    • 路由参数获取:$route.query.id
    • 最后形成的路由:/index?id=zs

区别

  • 获取参数方式不一样,一个通过route.params,一个通过route.params,一个通过 route.query
  • 参数的生命周期不一样,query参数在URL地址栏中显示不容易丢失,params参数不会在地址栏显示,刷新后会消失

路由守卫

  • 全局前置钩子:beforeEach、beforeResolve、afterEach
  • 路由独享守卫:beforeEnter
  • 组件内钩子:beforeRouterEnter、beforeRouterUpdate、beforeRouterLeave

Vue中key的作用

key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,减少DOM操作量,提高性能。

为什么不建议用index作为key?

如果将数组下标作为key值,那么当列表发生变化时,可能会导致key值发生改变,从而引发不必要的组件重新渲染,甚至会导致性能问题。例如,当删除列表中某个元素时,其后面的所有元素的下标都会发生改变,导致Vue重新渲染整个列表。

为什么v-for和v-if不能一起使用

v-for比v-if优先级更高,一起使用的话每次渲染列表时都要执行一次条件判断,造成不必要的计算,影响性能。

原理知识

双向数据绑定的原理

采用数据劫持结合发布者-订阅者模式的方式,data数据在初始化的时候,会实例化一个Observe类,在它会将data数据进行递归遍历,并通过Object.defineProperty方法,给每个值添加上一个getter和一个setter。在数据读取的时候会触发getter进行依赖(Watcher)收集,当数据改变时,会触发setter,对刚刚收集的依赖进行触发,并且更新watcher通知视图进行渲染。

编辑

原理解释

在Vue.js中,双向绑定是一种核心的特性,它允许视图(View)和数据模型(Model)之间的自动同步。这种双向绑定机制使得当数据发生变化时,视图会自动更新;反之,当视图中的输入发生变化时,数据模型也会相应更新。

发布者-订阅者模式在Vue.js的实现中起着重要作用,帮助实现了双向绑定的功能。下面结合Vue.js的双向绑定机制来分析发布者-订阅者模式的应用:

  1. 数据驱动视图
    • 在Vue.js中,数据模型(Model)充当了发布者的角色,负责存储应用的状态数据。当数据发生变化时,Vue.js会自动通知相关的订阅者(视图),从而更新视图的显示。
  1. 订阅者更新视图
    • Vue组件中的模板(Template)和数据模型之间建立了订阅关系。当数据模型发生变化时,与之相关联的视图会自动更新,这就是订阅者(视图)接收到发布者(数据模型)通知后执行的更新操作。
  1. 视图修改数据
    • 同样地,当视图中的输入发生变化时(比如input框内的文本变化),Vue.js会自动更新数据模型,实现了从视图到数据模型的反向更新。
  1. 事件总线
    • 在Vue.js内部,采用了类似发布者-订阅者模式的事件系统来进行数据的变化通知和处理。当数据发生变化时,Vue实例会通过事件总线来通知相关的订阅者(观察者),从而更新视图。

总的来说,Vue.js的双向绑定机制借鉴了发布者-订阅者模式的思想,通过数据模型作为发布者,视图作为订阅者,以及内部的事件总线机制,实现了数据和视图之间的自动同步。这种设计架构使得开发者无需手动管理数据和视图的更新,极大地简化了前端开发的复杂度,提高了开发效率。

使用 Object.defineProperty() 来进行数据劫持有什么缺点?

该方法只能监听到数据的修改,监听不到数据的新增和删除,从而不能触发组件更新渲染。vue2中会对数组的新增删除方法push、pop、shift、unshift、splice、sort、reserve通过重写的形式,在拦截里面进行手动收集触发依赖更新。

和Vue3相比有什么区别?

Vue3采用了Proxy代理的方式,Proxy是ES6引入的一个新特性,它提供了一个用于创建代理对象的构造函数。它是对整个对象的监听和拦截,可以对对象所有操作进行处理。而Object.defineProperty只能监听单个属性的读写,无法监听新增、删除等操作。

Vue是如何收集依赖的?

依赖收集发生在defineReactive()方法中,在方法内new Dep()实例化一个Dep()实例,然后在getter中通过dep.depend()方法对数据依赖进行收集,然后在settter中通过dep.notify()通知更新。整个Dep其实就是一个观察者,吧收集的依赖存储起来,在需要的时候进行调用。在收集数据依赖的时候,会为数据创建一个Watcher,当数据发生改变通知每个Watcher,由Wathcer进行更新渲染。

slot是什么?有什么作用?原理是什么?

slot插槽,一般在封装组件的时候使用,在组件内不知道以那种形式来展示内容时,可以用slot来占据位置,最终展示形式由父组件以内容形式传递过来,主要分为三种:

  • 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
  • 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
  • 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。

实现原理:当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.slot中,默认插槽为vm.slot中,默认插槽为vm.slot.default,具名插槽为vm.slot.xxxxxx为插槽名,当组件执行渲染函数时候,遇到slot标签,使用slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到slot标签,使用slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

对keep-alive的理解,它是如何实现的,具体缓存的是什么?

keep-alive是Vue.js的一个内置组件。它能够将不活动的组件实例保存在内存中,而不是直接将其销毁,它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中。

  • include 字符串或正则表达式,只有名称匹配的组件会被匹配;
  • exclude 字符串或正则表达式,任何名称匹配的组件都不会被缓存;
  • max 数字,最多可以缓存多少组件实例。

2 个生命周期 activated , deactivated

  • activated:当缓存的组件被激活时,该钩子函数被调用。可以在该钩子函数中进行一些状态恢复、数据更新等操作。
  • deactivated:当缓存的组件被停用时,该钩子函数被调用。可以在该钩子函数中进行一些状态保存、数据清理等操作。

keep-alive内部其实是一个函数式组件,没有template标签。在render中通过获取组件的name和include、exclude进行匹配。匹配不成功,则不需要进行缓存,直接返回该组件的vnode。

匹配成功就进行缓存,获取组件的key在cache中进行查找,如果存在,则将他原来位置上的 key 给移除,同时将这个组件的 key 放到数组最后面(LRU)也就实现了max功能。

不存在的话,就需要对组件进行缓存。将当前组件push(key)添加到尾部,然后再判断当前缓存的max是否超出指定个数,如果超出直接将第一个组件销毁(缓存淘汰策略LRU)。

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

$nextTick 原理及作用

在Vue.js中,this.$nextTick()方法是用来在DOM更新之后执行延迟回调函数的一种方式。它的原理涉及到Vue.js的更新队列和事件循环机制。

下面是this.$nextTick()方法的原理:

  1. 更新队列:
    • 当数据发生变化时,Vue.js并不会立即更新DOM,而是将需要更新的操作放入一个队列中。
  1. 异步更新:
    • Vue.js利用JavaScript的事件循环机制,在当前事件循环结束时(即当前任务执行完毕后),才会去执行更新队列中的操作。
  1. $nextTick()方法:
    • nextTick()方法就是用来在下次DOM更新循环结束之后执行回调函数的工具。通过调用this.nextTick()方法就是用来在下次DOM更新循环结束之后执行回调函数的工具。通过调用this.nextTick(callback),可以确保在DOM更新完成后执行特定的逻辑。
  1. 实现原理:
    • 当调用this.$nextTick(callback)时,Vue.js会将该回调函数推入微任务队列(microtask queue)中,等待当前任务执行完毕后立即执行。这样可以保证回调函数在DOM更新后被触发,从而获取到最新的DOM状态。

总的来说,this.$nextTick()方法的原理是利用JavaScript的事件循环机制和微任务队列,在当前事件循环结束时执行回调函数,确保回调函数在DOM更新完成后被触发。这样可以有效地处理DOM更新后的逻辑或操作,确保操作基于最新的DOM状态。

Vue模版编译原理

模版编译主要过程:template ---> ast ---> render,分别对象三个方法

  • parse 函数解析 template
  • optimize 函数优化静态内容
  • generate 函数创建 render 函数字符串

调用parse方法,将template转化为AST(抽象语法树),AST定义了三种类型,一种html标签,一种文本,一种插值表达式,并且通过 children 这个字段层层嵌套形成了树状的结构。

optimize方法对AST树进行静态内容优化,分析出哪些是静态节点,给其打一个标记,为后续更新渲染可以直接跳过静态节点做优化。

generate将AST抽象语法树编译成 render字符串,最后通过new Function(render)生成可执行的render函数

Vuex

Vuex 的原理

Vuex是专门为Vue设计的状态管理,当Vue从store中读取数据后,数据发生改变,组件中的数据也会发生变化。

​编辑

  1. State:存储共享的状态数据。
  2. Getters:从 state 派生出状态,或者计算得到的值。
  3. Mutations:同步的函数,用于改变 state
  4. Actions:处理异步操作,通过 commit 提交 mutations 来间接改变 state
  5. Modules:将 Vuex store 分成多个模块,用于管理大型应用中的状态。

Vue组件接收交互行为,调用dispatch方法触发action相关处理,若页面状态需要改变,则调用commit方法提交mutation修改state,通过getters获取到state新值,重新渲染Vue Components,界面随之更新。

Vuex中action和mutation的区别

  • mutation更专注于修改state,必须是同步执行。
  • action提交的是mutation,而不是直接更新数据,可以是异步的,如业务代码,异步请求。
  • action可以包含多个mutation

Vuex 和 localStorage 的区别

  • Vuex存储在内存中,页面关闭刷新就会消失。而localstorage存储在本地,读取内存比读取硬盘速度要快
  • Vuex应用于组件之间的传值,localstorage主要用于不同页面之间的传递
  • Vuex是响应式的,localstorage需要刷新

虚拟DOM

对虚拟DOM的理解

虚拟DOM就是用JS对象来表述DOM节点,是对真实DOM的一层抽象。可以通过一些列操作使这个棵树映射到真实DOM上。

如在Vue中,会把代码转换为虚拟DOM,在最终渲染到页面,在每次数据发生变化前,都会缓存一份虚拟DOM,通过diff算法来对比新旧虚拟DOM记录到一个对象中按需更新,最后创建真实DOM,从而提升页面渲染性能。

虚拟DOM就一定比真实DOM更快吗

虚拟DOM不一定比真实DOM更快,而是在特定情况下可以提供更好的性能。

在复杂情况下,虚拟DOM可以比真实DOM操作更快,因为它是在内存中维护一个虚拟的DOM树,将真实DOM操作转换为对虚拟DOM的操作,然后通过diff算法找出需要更新的部分,最后只变更这部分到真实DOM就可以。在频繁变更下,它可以批量处理这些变化从而减少对真实DOM的访问和操作,减少浏览器的回流重绘,提高页面渲染性能。

而在一下简单场景下,直接操作真实DOM可能会更快,当更新操作很少或者只是局部改变时,直接操作真实DOM比操作虚拟DOM更高效,省去了虚拟DOM的计算、对比开销。

虚拟DOM的解析过程

  • 首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含 TagName、props 和 Children 这些属性。然后将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。
  • 当页面的状态发生改变,需要对页面的 DOM 的结构进行调整的时候,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。
  • 最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。

DIFF算法原理

diff的目的是找出差异,最小化的更新视图。 diff算法发生在视图更新阶段,当数据发生变化的时候,diff会对新旧虚拟DOM进行对比,只渲染有变化的部分。

  1. 对比是不是同类型标签,不是同类型直接替换
  2. 如果是同类型标签,执行patchVnode方法,判断新旧vnode是否相等。如果相等,直接返回。
  3. 新旧vnode不相等,需要比对新旧节点,比对原则是以新节点为主,主要分为以下几种。
    1. newVnode 和 oldVnode都有文本节点,用新节点替换旧节点。
    2. newVnode有子节点,oldVnode没有,新增newVnode的子节点。
    3. newVnode没有子节点,oldVnode有子节点,删除oldVnode中的子节点。
    4. newVnode和oldVnode都有子节点,通过updateChildren对比子节点。

双端diff

updateChildren方法用来对比子节点是否相同,将新旧节点同级进行比对,减少比对次数。会创建4个指针,分别指向新旧两个节点的首尾,首和尾指针向中间移动。

每次对比下两个头指针指向的节点、两个尾指针指向的节点,头和尾指向的节点,是不是 key是一样的,也就是可复用的。如果是重复的,直接patch更新一下,如果是头尾节点,需要进行移动位置,结果以新节点的为主。

如果都没有可以复用的节点,就从旧的vnode中查找,然后进行移动,没有找到就插入一个新节点。

当比对结束后,此时新节点还有剩余,就批量增加,如果旧节点有剩余就批量删除。​编辑