Skip to content
On this page

1、cookie 和 session 的区别

cookie 和 session 都是用于客户端和服务器端之间进行状态管理的技术,它们的区别主要有:

  1. 存储位置不同:

    cookie 存储在客户端浏览器中,而session存储在服务器端的内存或者数据库中

  2. 安全性不同:

    cookie 可以被客户端浏览器禁用或者删除,也容易被恶意攻击者窃取和篡改,因此存储敏感信息的安全性较低, 而session存储在服务器端,客户端无法直接访问,相对来说比较安全

  3. 存储容量不同:

    cookie 的存储容量比较小,一般不超过4kb;而session的存储容量比较大,可以存储更多的信息

  4. 过期时间不同:

    cookie的可以设置过期时间,可以长期保存在客户端浏览器中;而session的过期时间一般比较短,用户在关闭浏览器 或者一段时间不活动之后就会自动过期

  5. 用途不一样:

    cookie主要用于客户端浏览器与服务器端之间的状态管理,比如保存用户登录信息、购物车信息等; 而session一般用于服务器端的状态管理,比如保存用户的会话信息、缓存数据等。

总之,cookie和session都有各自的优缺点,具体使用哪种技术要看具体的需求和场景来决定。

2、slot 的使用

vue中的插槽有三种类型:匿名插槽、具名插槽、作用域插槽

  1. 匿名插槽:

    子组件通过 slot 标签来确定插槽渲染的位置,标签内可以放入Dom结构,当父组件在使用的时候没有往插槽传入内容, 标签内的dom结构就会显示在页面上;父组件在使用的时候,直接在子组件的标签内写入内容就可以了

  2. 具名插槽:

    子组件通过 name 属性来表示插槽的名字,不传就为默认插槽;父组件在使用的时候在默认插槽的基础上加上 slot 属性, 值就是子组件的插槽的 name 属性值

  3. 作用域插槽:

    子组件在作用域插槽上绑定属性将自身的信息传递给父组件来使用,这些属性会挂载到父组件的 v-slot 接收的对象上; 而 v-slot 可以简写为 #,父组件通过 # 加上插槽名字 来获取子组件的信息,在内容中使用。

总结一下,有一些需要注意的地方:

  • v-slot指令 只能在 template 标签上使用,但是当只有默认插槽的时候,可以在组件标签上使用

  • 默认插槽的名字为 default ,可以省略 default 直接写 v-slot

  • 当 v-slot 简写为 # 时,不能省略参数,写成 #default

  • 可以通过解构获取子组件传递的变量:v-slot={user},也可以重命名变量:v-slot="{user: aaa}",还可以设置默认值:v-slot="{user='默认1'}"

3、computed 是怎么做响应式的?

computed属性是vue中的计算属性,它是一个类似于过滤器的函数,主要用于对绑定到视图的数据进行处理

  • computed 属性可以像data 属性一样定义,然后可以直接在模板中使用,但是它是一个函数并且必须要有一个返回值

  • computed 属性也是响应式的,它会自动追踪其他的响应式属性,例如 data中数据或者父组件传递的 props 中的数据 当它依赖的这些响应式属性发生变化时,computed属性会自动重新计算,然后通知组件,更新页面视图

  • 计算属性的结果默认会走缓存,只有当它依赖的响应式属性发生变化才会重新计算,这可以提高性能,避免不必要的计算 (这跟method方法不同,method调用一次就重新计算一次,computed 调用多次如果值没变化就只会计算一次)

4、vue2 和 vue3 的区别

  1. 数据双向绑定 的原理不同:

在vue2中,数据双向绑定是利用 ES5 的一个 API 叫 Object.defineProperty 对数据劫持, 并且结合发布\订阅者模式的方式去实现的

而在vue3中,数据双向绑定是使用 es6 的 Proxy 方法对数据进行代理,

相比于 vue2 ,proxy 有几个好处:

  • Object.defineProperty 只能监听对象的某个属性,不能对整个对象进行监听,因此 vue2 需要递归遍历所有的属性 去对数据进行劫持,而 proxy 可以直接对整个对象代理进行监听,因此节省了递归遍历等操作,明显提高了性能

  • 可以监听数组,不用像vue2中那样对数组做特异性操作,vue3可以检测到数组内部数据的变化

  1. vue3 支持 碎片化

在vue2中,template 模板标签下必须并且只能有一个根节点

而在vue3中,可以支持多个根节点

  1. API 类型不同:

在 vue2 中使用的是选项式API的写法,根据不同的属性来分割代码:data、computed、methods等, 当代码量比较大的时候,需要不断地上下翻滚查找数据和方法,会比较麻烦

而 vue3 采用的是 组合式API的写法,同一个业务的数据和方法写在同一个地方,相比于 vue2 的写法, vue3 的代码会更加方便和整洁

  1. 定义数据变量和方法不同:

vue2 的数据变量放到 data 中管理,创建的方法放到 methods 中管理,

而 vue3 的数据和方法是写到 setup 函数里面,setup 函数会在组件初始化构造的时候触发

  1. 生命周期钩子函数不一样:

vue2 中的生命周期有:

组件创建前和后:beforeCreate、created

组件挂载前和后:beforeMount、mounted

数据更新前和后:beforeUpdate、Updated

以及组件销毁前后:beforeDestroy、destroyed

vue3 的生命周期把组件创建前后改成了 setup 函数

组件挂载前和后:onBeforeMount、onMounted

数据更新前和后:onBeforeUpdate、onUpdated

组件销毁前后改了名称,叫:onBeforeUnMount、onMounted

并且 vue3 中的生命周期在使用之前要先引入。
  1. 父子组件传参的方式不同

vue2 中的 父传子,子组件使用 props 接收,而 子传父使用 this.$emit 传入事件名称 来触发父组件的方法并且通过参数传值

而在 vue3 的 setup 语法糖里面,父传子使用的是 defineProps 来接收,子传父使用的是 defineEmits

  1. 插槽和指令不同
  • 插槽:

    vue2 中的插槽可以直接使用 slot 指令,而 vue3 中必须使用 v-slot 指令

  • 指令:

    vue2 中的 v-for 比 v-if 的优先级要高,并且不建议一起使用,而在 vue3 中,v-if 只是作为 v-for 的一个判断语句 不会相互冲突;

    并且在 vue3 中,移除了 keyCode 作为 v-on 的修饰符,以及移除了 v-on.native 修饰符, 还有一个过滤器 filter 也从 vue3 中移除了

  1. main.js 文件不同

在 vue2 的main.js中,引入的是 Vue 的构造函数,并且可以使用 prototype 操作原型的方式去添加全局对象或者方法

在 vue3 中则是引入了一个名叫 createApp 的工厂函数,通过模块化的方式去组织代码,比 vue2 更加简洁

并且 vue3 的 app.vue 组件可以没有根标签

5、解释一下 MVVM

MVVM 就是 model-view-viewModel,本质上是 MVC 的改进版

model 代表数据模型,数据和业务逻辑都在model层定义

view 是用户在屏幕上看到的UI视图

viewModel 负责监听 model 中数据的改变并且控制视图的更新,以及处理用户的交互逻辑

MVVM 优点是:

  • 低耦合,view视图可以独立于model的变化和修改,一个viewModel可以绑定到不同的view上,view和model无直接的关联,互不影响

  • 可复用性强,就是开发者可以把一些视图逻辑放在一个viewModel里面,让很多view可以复用这段逻辑

  • 独立开发,业务逻辑和视图表现是分离,开发者可以专注于业务逻辑,不用过多关注界面的细节

6、vue 的特点

vue 是一个轻量级的 JavaScript 框架,专注于视图层开发,用于构建单页面应用

vue 使用 MVVM 架构,将视图层、数据层和业务逻辑分离开来,通过一些简单的API实现响应式的数据绑定和可以组合的视图组件, 让开发者可以更方便地开发和维护复杂的前端应用

vue 的特点:

  1. 数据双向绑定机制:就是说当数据发生变化时,视图也会进行更新,不需要手动去操作 DOM

  2. 组件化开发:把代码分割成一个个独立的组件,方便我们开发和复用,同时也可以提高代码的可维护性和可扩展性

此外,vue 还有丰富的插件生态,比如路由管理的 vueRouter、状态管理的 vuex 等,提高开发效率

总的来说,vue 的优点在于它的灵活性和轻量级,它的语法、指令简单明了,学习成本低, 不管是小型应用还是大型单页面应用,都可以使用 vue 来实现

7、什么是单页面应用(SPA)

单页面应用,也叫 single-page application 简称 SPA,也就是加载单个页面并在交互时动态刷新该页面,所有的 活动都在一个页面中进行。

单页面应用在初始化时加载相应的HTML、CSS、JavaScript 文件,利用 js 动态的切换 HTML,从而实现UI与用户的交互效果

vue 框架为单页面应用开发提供了模板、路由和DOM操作以及异步访问数据api等功能,使得前后端分离开发成为了可能

SPA的优点是:

  1. 前后端分离开发,减轻了服务器的压力;

  2. 移动端和PC端等可以共用一套后端接口程序代码,节省了开发成本。

SPA的缺点是:

  1. SEO 难度比较高,首次加载时耗时比较长

  2. 页面前进、后退的管理比较困难

针对单页面应用的前进后退的管理问题,我们可以引入 vueRouter 实现单页面应用的路由配置和导航控制, 通过浏览过的一个历史记录,查看过往的页面

还有首次加载耗时长的问题,可以采用多种缓存措施,在需要的时候再进行加载模块,比如路由懒加载、数据懒加载等

最后是 SEO 优化问题,由于单页面应用的数据是动态刷新的,导致网页内容很难被搜索引擎抓取到。

一般有两种解决方案:一种是服务端渲染,还有一种是前端预渲染。

关于前端预渲染:

  1. 使用插件 prorender-spa-plugin,下载安装之后在 vue.config.js 中进行配置, 配置好这个默认预编译的路径、路由和渲染方式

  2. 然后在 main.js 中引入,并且要把 vueRouter 路由模式改成 history,然后进行打包

8、data为啥是函数

在 Vue 组件中,data 必须是一个函数,是因为组件可能会被多次实例化。对象是引用数据类型, 如果 data 是一个对象, 那么所有组件实例会共享同一个数据对象,引用的是同一个内存地址。这样一来,当一个组件实例修改了数据, 所有其他实例的数据也会被更改,这是我们不希望看到的

而如果 data 是一个函数,它会创建一个函数作用域作为自己的私有域,这样就使得每个组件实例中的 data 都是独立的数据,互不影响, 可以保证组件的一个正常工作。

9、数据双向绑定的原理

vue 中实现的这个数据双向绑定,首先要对数据进行劫持监听,用到了 Object.definedProperty() 的 get 和 set 方法, 然后还需要一个监听器 Observer,来监听所有属性。如果属性发生了变化,就告诉订阅者 Watcher 看是否需要更新。

因为 订阅者 是有很多个的,所以需要有一个消息订阅器 Dep 来专门收集这些订阅者, 然后在监听器 Observer 和 订阅者 Watcher 之间进行一个统一管理。

接着,我们还需要有一个指令解析器 Compile,对每个元素节点进行扫描和解析, 将相关指令对应初始化成一个订阅者 Watcher,并且替换模板数据或者绑定相应的函数,

此时当订阅者 Watcher 接收到相应属性的变化时,就会触发执行对应的更新函数, 这一点是在 js 文档碎片中完成的,从而更新了视图。

以上就是我所了解的数据双向绑定的原理~

10、computed、methods、watch

computed 是 vue 中的计算属性,它可以用来解决模板过重的问题。计算属性必须要 return 返回一个结果, 并且具有缓存功能,可以提高性能。

computed 注重这个计算结果,不支持异步操作。

methods 选项是用来存放方法的,方法可以用来处理事件绑定和逻辑计算,跟 computed 不同, 方法可以不用 return 一个结果,并且没有缓存功能。

watch 可以用来监听某一个值的变化,在值发生变化的时候可以做一些操作。watch 也可以不用 return 返回值, 也没有缓存。但是它同时支持异步或者同步操作。

总之,computed、methods以及 watch 都是用于处理数据的选项,可以根据具体的业务需求和场景来使用。

11、v-if 和 v-show 的区别

v-if 和 v-show 这两个指令都可以用来控制 DOM 元素的显示和隐藏,不同的是:

v-if 是真正的条件渲染,它会在元素显示隐藏的切换过程中销毁和重建条件块内的事件监听器和子组件; 而v-show只是简单的基于CSS样式来进行切换。

v-if 在首次渲染时,如果条件为真,就会创建和渲染条件块的元素,如果条件为false,那它什么也不会做;

而 v-show 在首次渲染时,不管条件为真还是假,都会创建和渲染元素

总结一下,v-if 是切换的开销高,v-show 的初次渲染的开销才比较高,

因此,如果是需要频繁进行切换的场景,使用 v-show 性能会更好,如果条件很少改变,使用v-if可能会比较好。

但还是需要结合具体的业务需求和场景来选择使用。

12、为什么 v-for 中必须要有 key

key 是 虚拟DOM 中用于识别节点的唯一标识符,它可以帮助 vue 高效地更新虚拟DOM,避免元素错乱等问题。 例如,在使用 v-for 渲染列表时,如果没有给每个元素加上一个唯一的key,就会导致元素发生错乱。

vue 的DOM操作默认采用“就地复用”策略,也就是说,当数据项的顺序发生变化时, vue会复用已有的元素来提高性能,而不是重新创建DOM元素。

最后需要注意的是,key 属性的类型只能是 number 或者 string 类型。

以上就是我了解的为什么要有 key 的原因

追问:key 的作用原理

13、vue 中的指令

指令就是针对 DOM 和数据操作而定义的一组具有特殊功能的命令

指令分为 系统指令 和 自定义指令

  1. 系统指令主要有:v-if、v-show、v-for、v-model、v-text、v-html 等

  2. 自定义指令可以分为:全局自定义指令 和 局部自定义指令

  • 全局自定义指令:

    在全局文件main.js中通过 directive 属性挂载 Vue 构造函数上

  • 局部自定义指令:

    在组件的钩子函数 directives 中进行声明

  1. 应用场景:

    用于数据类的,格式化一些内容、校验等,例如:微博的评论,有个显示评论时间的, 我们可以通过自定义指令,把时间小于2分钟的改成 刚刚、时间超过两分钟就显示 2分钟前、3分钟前...

    还有可以用于控制元素的状态比如是否可用、如按钮的权限控制等等

追问:vue 中自定义指令怎么实现的?

  1. 全局自定义指令:
// vue2 写法,在 Vue 构造函数中挂载
Vue.directive()

// vue3 写法,在实例对象 app 上挂载
app.directive('指令名称', (el, binding) => {
    // el 是绑定的元素, binding 是绑定指令的对象,binding.value 可以获取绑定属性值
    //可选生命周期:created、beforeMount、Mounted、beforeUpdate、updated、beforeUnMount、UnMounted
})
  1. 局部自定义指令:在组件中注册使用,在组件的钩子函数 directives 中进行声明
// 选项式写法:
directives: {
    'xxx': {
        mounted(el, binding) {xxx}
        .....
    }
}

// setup 语法糖
const vColor = {
    mounted(el, binding) {xxx}
    .....
}

14、vue的生命周期、 mounted 和 created 的区别

vue2 完整的生命周期,主要包括以下四个阶段:创建、挂载、数据更新、组件销毁

  • 组件创建前的生命周期是:beforeCreate 创建后:created

  • 挂载到DOM前的生命周期是:beforeMount 挂载后:mounted

  • 组件数据更新前:beforeUpdate 更新后:updated

  • 组件销毁之前: beforeDestroy 销毁后:destroyed

还有当组件初次加载时,会执行前面4个生命周期,就是:beforeCreate、created、beforeMount、mounted

如果使用了 keep-alive 就会新增两个生命周期,分别是:activated 和 deactivated。

关于生命周期钩子函数,它们在组件的不同阶段运行,完成一些特定的操作:

  1. beforeCreate:这个生命周期是组件创建之前调用,此时数据观测和事件初始化还没开始,data的响应式追踪、 event\watcher 都还没被设置,也就是说这时候不能访问到data、methods、computed、watcher 上面的方法和数据

  2. created: 这时候组件实例创建好了,并且实例上面配置了:data、methods、computed、watcher等选项里面的 数据和方法,但是此时渲染节点还没开始挂载到DOM上,因此访问不到 $el 属性

  3. beforeMount:是在组件实例挂载之前调用,此时实例完成了模板编译,并将 data 里面的数据生成虚拟DOM

  4. mounted:这时是在 el 被新创建的 vm.$el 替换,并且挂载到实例上之后调用。

  5. beforeUpdate:数据更新时调用,发生在虚拟DOM重新渲染之前

  6. updated:由于数据更改导致虚拟 DOM 重新渲染,然后就会调用这个生命周期,此时 页面上的 DOM 已经更新了

  7. beforeDestroy:实例销毁之前调用。在这一步,组件实例还是完全可用的

  8. destroyed:组件实例销毁之后调用。调用后,vue实例会解除所有的数据绑定,包括所有的事件监听器, 子组件实例等都会被销毁解除

还有一个特殊的 errorCaptured:会在捕获到一个来自子孙组件的错误时被调用

vue3 的生命周期跟 vue2 的大同小异,但是用法和名字不太一样了

vue3 中 组件创建前后beforeCreate和created 改为使用 setup 函数, 组件的数据和方法在 setup 函数中定义。

然后 组件挂载到页面渲染前后的生命周期、还有数据更新前后的生命周期,在 vue2 的基础上,多加了 on 在名字前面,

例如:onBeforeMount、onMounted、onBeforeUpdate、onUpdated

最后组件销毁前后的生命周期的名字改为了 unMounted:

销毁前:onBeforeUnMount、销毁后:onUnMounted

create 和 mounted 的区别:

created 是在组件渲染之前调用的,能拿到数据但是拿不到模板的

mounted 是在组件数据挂载并且渲染页面之后调用的,因此模板和数据都可以拿到

15、组件通信

  1. 父传子:

    父组件通过在子组件标签上用 v-bind:属性名='data',绑定属性名,值为父组件中的data数据变量, 而子组件使用 props:['data'],用数组或者对象的形式声明属性值来接收父组件传递过来的数据

  2. 子传父:

    子组件通过 this.$emit() 向父组件发送事件,在父组件中定义这个事件和对应回调方法,数据会以参数形式 传递到父组件中

  3. 兄弟组件通信:

    通过新建一个 bus.js 文件,里面 new 一个 vue 组件实例并且导出这个实例,然后假设有 A 、B 两个兄弟组件 在 A 组件中导入这个 bus.js,使用 bus.$emit() 传递事件和参数数据,然后 B 组件中也引入 bus.js , 使用 bus.$on() 来接收事件和定义回调函数来处理传递过来的参数

还可以使用 vuex 或者 pinia 的方式来通信:vuex的理解

16、vuex 的理解

vuex 是 vue.js应用程序 的状态管理库,就像一个 store 仓库,保存着程序中的大部分状态,它的核心概念有:

state: 是数据源,用来保存所有组件的公共数据

getter:getters 的返回值会被缓存下来,只有当它的依赖值发生变化才会重新计算,类似于计算属性

mutations:定义方法用来更新 state 中的数据,必须是同步的,在组件中通过 store.commit 来调用

actions:actions 可以使用异步方法,在组件中通过 store.dispatch 来调用并提交到 mutations 来更改数据

最后一个是 module:当应用程序比价大的时候,通过 module 可以包含其他的 vuex 模块

但是 vuex 存储的数据在刷新页面就会丢失,因此需要做持久化存储:

  1. 可以使用 localStorage 做持久化存储,

  2. 也可以使用第三方插件:比如 vuex-persistedstate ,安装之后在 store/index.js 中 引入插件,然后在 new vuex.store 中配置一下这个 plugins: [createPersistedState()],这样 vuex 的数据就会持久化到 localStorage 中

这个插件本质上也是利用 localStorage 进行持久化存储的

追问:在什么场景下应用 vuex

如果项目中有需要在多个组件之间管理和共享状态的数据,就可以使用 vuex 来解决,例如 我之前做的一个项目里有一些商品需要收藏 或者类似小说网站那种书签、小说收藏、书架数据、搜索框历史记录等功能,就可以使用 vuex 来管理。

例如收藏夹功能就可以在 vuex 中 state 记录商品数据信息列表,当本地有就直接用本地的数据,如果没有,就从接口请求回来。

(如果数据比较重要,就发送到后端进行保存,如果数据不重要,就本地存储就好了)

17、get 和 post 的区别

get 和 post 是 http 协议中两种不同的请求方法

get 请求一般用来查询数据,通过 url 传递参数,就是拼接到 url 后面,而且参数有长度限制,只能用 url 来编码, 还有一点是 get 请求可以被缓存下来,也可以添加到浏览器书签上。

post 请求一般用来提交数据的,例如提交表单数据或者文件文件上传等,post 请求的参数放到请求体中,参数的长度没有限制 也可以使用多种编码方式,跟get请求不同,post不能被缓存。

18、常见的跨域方式

跨域是由于浏览器的同源策略导致的,在浏览器中,如果当前页面请求一个不同源的url时,就会触发这个跨域的问题,常见的解决方法有:

  1. jsonp:利用script标签不受同源策略影响的特性,动态创建 script 标签,src 属性值就为请求的url,来获取其他域下的数据; 同时将回调函数作为参数传递给服务器,服务器在响应时将数据作为回调函数的参数来返回,响应完成时,客户端通过执行回调函数来获取数据。

  2. CORS:跨域资源共享,它允许服务器端通过设置响应头比如 Access-Control-Allow-Origin 来告诉浏览器允许这个跨域请求, 前端在发送请求时在请求头加上 Origin 字段就可以了。这个也是官方推荐的一种跨域解决方案。

  3. 使用代理服务器跨域:通过代理服务器将跨域请求转发到目标服务器,有好几种实现方法,可以使用 Nginx 进行反向代理, 也可以在 vue 项目中的一个配置文件中设置 proxy 属性来开启代理服务

  4. postMessage:html5 的 window.postMessage 方法,第一个参数为发送的消息,第二个参数为指定域地址, 然后通过 window.addEventListener('message',()=>{}) 监听 window 的 message 事件来接收传递的信息

  5. 使用 WebSockets: WebSockets 可以实现跨域通信,因为它们不受同源策略限制。通过建立一个 WebSocket 连接, 客户端和服务器可以进行双向通信,实现跨域数据交换。(new 一个 WebSockets 实例,然后监听实例的 open 事件,在回调里 发送数据 .send(), 监听实例的 message 事件,可以接收到服务器发来的信息,还有 close 事件)

19、axios 二次封装

axios 是一个基于 promise 的网络异步请求库,我们可以对它进行二次封装方便我们使用:

新建 utils/request.js ,导入 axios,可以设置 baseURL、timeOut超时时间等一些基础配置项,

然后可以给请求、响应各封装一层拦截器:

  1. 请求拦截器 interceptors.request.use,可以在这里设置每个请求携带是否 token 等操作

  2. 响应拦截器 interceptors.response.use,收到响应之后的处理,可以在这里处理不同状态码的操作

然后我们可以根据需要,去封装 get请求、post请求等不同请求方法,进一步简化请求的调用方式,这样在 使用的时候,直接填入 url 和 参数就可以了。

但是如果想要更好维护的话,还可以再封装一层 api 的解耦,根据不同的模块定义每一个请求的接口函数, 这样可以根据需要去调用,也可以使用 ts 类型接口去约束请求需要的参数类型,方便我们使用

例如 获取个人信息的接口、查询字典的接口 可以封装成一个个api函数,例如我之前的项目中 有这个用户管理页它需要查询字典这个接口去查询该页面的用户是否有这个 启用 的功能,然后还有角色管理同样 也需要这个 查询字典的 接口查询角色是否可以启用,因此这两个页面就可以直接引入api接口函数来使用就可以了 比较方便嘛

20、async 和 await

async 用来定义异步函数,该函数不会阻塞后面的代码。async 语句会把这个函数包装成promise对象,可以使用 .then 获取返回值

async 函数内部原理是:

如果这个 async 函数有返回值,就会使用 resolve() 把它转化成promise对象来返回

如果这个 async 函数内部报错了,调用 reject() 返回 promise 对象,可以用 catch 来捕获错误

然后 await 语句,是等待的意思,它必须要在 async 函数内部配合使用,await后面一般跟着promise对象, 它会把async函数挂起,直到promise对象返回结果才继续往下执行

async 和 await 的优势是:

它可以像写同步代码一样来写异步代码,比 promise 写法更直观一点,提高效率(promise 解决了回调地狱问题)

21、接口安全

前后端在进行数据传输的时候,为了防止请求被抓包、伪造、偷窥等情况,一般需要对数据进行加密, 我们前端常用的有 md5,crypto、jsencrypt 等…

说一下我们之前常用的加密解密方式:

第一种是 crypto,属于 AES 算法,首先通过 npm 下载,然后在 utils/cryptoAES.js 文件, 导入 crypto,定义一个加密函数和一个解密函数

其中就会用到密钥和偏移量,这个密钥跟偏移量是前后端约定好的。使用的时候在组件组件中导入这个文件就可以了, 然后使用加密函数对数据进行加密,发送到后端。后端返回的数据如果是加密的,就可以使用解密函数来解密得到正确的信息

第二种是 jsencrypt,它是属于 RSA 加密,RSA 是一种非对称加密算法,可以双向加密解密,用到一对公钥和私钥, 使用 公钥加密的数据需要用对应的私钥来解密,使用私钥加密就需要使用对应的公钥来解密,前后端各自保存着对应的公钥私钥, 也就是说,就算有人在前端的拿到了公钥或者私钥也没办法去解密,因为对应的私钥或公钥在后端,因此加密安全级别很高。

使用方法也很简单,通过 npm 下载,导入并且定义一个加密函数一个解密函数,然后再使用就可以了

22、 对单向数据流的理解

在vue中,单向数据流的一个体现是在 父子组件通信上:

父组件通过 props 向子组件传递的数据,子组件不能直接修改,修改就会报错,而如果父组件的数据更新了, 子组件的也会跟着变化,这就是单向数据流,这种方式可以确保数据只能从父组件流向子组件,这样可以很好追踪数据源的变化, 降低程序出错可能性。

如果子组件想要修改父组件的传递的数据:

  1. 可以在子组件 data 中新建一个变量赋值 props 中的值,然后再去修改这个变量就可以了

  2. 可以通过 this.$emit 发出一个事件,父组件收到事件去做相应的一个修改逻辑

23、对路由的理解

在 vue 中,路由是可以根据不同的 url 来切换显示不同内容的控制器、通常使用 vue-router 来实现路由功能

路由有两种模式:hash 和 history,默认使用hash模式,在 router/index.js 中配置

hash 路由模式会在 url 后面带有一个 # 号,当 url 的hash发生变化时,浏览器不会重新加载整个页面,而是会触发 hashchange事件,就可以通过js来监听这个事件实现数据的加载和页面渲染;

而 history 模式会刷新页面,会向后台服务器发送请求,如果后台没有定义这个路径的话,会返回404,因此需要后台配置重定向

路由的使用方法:首先需要通过 npm 下载 vue-router,需要在 src/router/index.js 中引入 vue-router, 需要配置路由表,包括 path、name、component、children 等属性

然后还可以分为静态路由和动态路由:

  1. 静态路由:就是路由的path固定不变的

  2. 动态路由:路由的path/:参数名称,比如说:path:"/users/:id",:号跟着一个参数id,在组件中使用的时候,通过 this.$router.push(url, 参数) 进行带参数跳转,可以使用 query 和 params 来传参

然后还有 $route 和 $router 的一个区别:

  1. $route:表示当前的路由信息,包含了当前页面的url路径、参数、query对象等内容

  2. $router 对象是全局的路由实例,包含一系列路由方法:

    • 比如 router-link 标签,可以声明一个路由链接

    • this.$router.push() 可以向 history 栈中添加一个新的记录

    • this.$router. replace() 替换,这个不会向 history 栈中添加新记录

    • this.$router.go(n) 方法,可以传递数字参数n,可以是正整数也可以负整数,表示向前或者向后跳转 n 个页面

还有路由的懒加载,可以按需加载路由文件,减少首页加载时间,避免首页出现白屏

最后是路由守卫:

  1. 全局路由守卫:放在 main.js 的 router 实例上,定义 beforeEach((to:即将进入, from:当前离开, next:下一步) => {}),afterEach((to, from)=>{})

  2. 局部路由守卫:在每个组件中定义的,beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

  3. 路由独享守卫:只在当前的路由中生效,有两个守卫方法 前置守卫beforeEach、后置守卫afterEach, 在 /router/index.js 中配置的一个个路由信息下定义,只在当前路由生效

追问:hash模式 和 history模式 的原理和区别

hash 路由模式:会在 url 后面带有一个 # 号,当 url 的hash发生变化时, 浏览器不会重新加载整个页面,而是会触发hashchange事件,就可以通过js来监听这个事件 实现数据的加载和页面渲染

而 history 模式:会刷新页面,会向后台服务器发送请求,如果后台没有定义这个路径的话, 会返回404,因此需要后台配置重定向

hash 模式的优缺点是:

兼容性好,所有浏览器都支持运行使用,缺点是 url 中带有 # 号,可能不太美观

history 模式的优缺点是:

url美观嘛,用户体验好,但是兼容性不太好,需要跟后端来配合使用

24、vue中如何动态添加属性(首先应该想到: 动态 class\style 切换,)

在 vue 中,可以使用 v-bind 指令实现动态绑定属性,v-bind 指令可以将data中的变量绑定到指定的 html 属性上,缩写为: ':'

可以定义 class类名、style样式,通过 v-bind 绑定class和style属性,改变变量值,可以进行动态切换的效果

25、vue 中如何做样式穿透

在vue中,可以在想要穿透的样式选择器前面加上 >>> 符号 或者 /deep/ 或者 ::v-deep 就可以了

26、keep-alive 组件缓存,保持状态怎么实现刷新

keep-alive 是 vue 中内置组件,可以保存组件的状态,避免组件重复渲染

它的使用方法很简单,只需要用 keep-alive 标签包裹着要缓存的组件就可以了,这样在切换组件时,组件就不会被销毁而是保存起来

可以使用 include 属性去选择哪些组件需要进行缓存,可以动态绑定一个数组,数组里面放的就是需要缓存的组件的name属性值

有两个独有的生命周期:activated:激活 和 deactivated:停用,可以写业务逻辑,如果要保存上一个页面的状态,则需要 使用局部路由守卫,在 beforeRouteLeave 函数中把当前页面路径以变量形式存放,在 activated 生命周期中,把跳转到变 量所保存的路径,既可以实现状态保存了

使用 keep-alive 组件在第一次加载时,会触发正常组件的生命周期基础上再多一个 activated。 如 beforeCreate、created、beforeMount、mounted

如果第二次或者之后,就只会触发 activated 这个生命周期

总之,使用 keep-alive 的优势是:在组件切换过程中并不会销毁而是缓存到内存中, 可以防止重复渲染DOM,减少加载时间,提高性能以及用户的体验

27、token 保存在哪里?

token可以保存的地方有很多,例如 localStorage、cookie、vuex、sessionStorage等地方。

比较推荐的是 localStorage、cookie ,可以很方便地实现免密登录、记住密码等功能

28、自己封装过组件吗?介绍一下常用的组件(待补充)

在封装组件的时候,不要随便过度的去拆分封装组件,是要遵守一些原则的:

比如一个组件只负责一个特定的功能或者行为,还有数据是否是动态,就是从后台接口拿到数据进行渲染, 还有就是是否多个页面都可以使用到的,可复用性比较高,满足这些条件的都应该封装成组件,这样的代码 写出来它的结构会比较清晰,容易维护

例如,我之前的做过的一个项目,是 xx 管理平台,它的首页里面有一个搜索框,商品轮播图,因为这部分数据 是可以在后台管理平台里面进行修改,因此把这个轮播图就封装成一个组件,后面如果要修改一些内容或者样式 也会比较方便,父组件请求回来的数据,通过 props 传递给子组件使用,还有设置了一些方法,比如是否自动播放、 播放间隔时间是多少等内容,都可以通过父组件传值来决定

组件封装的时候要尽量考虑的全面一点,可以使用插槽预留一些位置,方便使用者动态地去插入内容,让这个 组件更加灵活,满足更多自定义的场景需求。

  1. 组件的作用

  2. 封装的步骤

  3. 如果有传参

29、打包压缩时,dist 文件过大怎么处理

可以对项目做一下优化处理:

  1. dist打包生成的文件中有 .map 文件,可以在 vue.config.js 文件中,配置 productionSourceMap:false,表示不生成 map

  2. 多使用组件和路由的懒加载,按需引入

  3. 第三方包通过 script 标签,CDN 的方式引入

  4. 对于文件和图片进行压缩,可以安装压缩插件:compression-webpack-plugin 下载之后在 vue.config.js 中进行配置:组件不打包、分割代码、超过一定大小的文件进行压缩等选项

30、vue 源码

在 node_modules/vue/src 目录中存放这vue的源码,其中有个 core 目录,里面包含了 vue 的核心代码,包括内置组件、全局 API 封装、Vue实例化、观察者、虚拟DOM 以及工具函数等,

31、vue 是怎么渲染模板的?

在vue源码的 vue/src/core/instance/init.js 文件,是模板渲染的起点,主要逻辑是:

  1. 先检查是否有 el 属性,有就调用 vm.$mount 方法挂载 vm,挂载的目标就是把模板渲染成最终的 DOM,无论怎么渲染, 最终都会生成 Render 函数,在 instance/lifecycle.js,通过 Watcher 的绑定,每当数据发生变化时, 会执行 _update 方法,此时会执行 vm._render()方法,在这个方法里,会执行我们的 Render 函数, 得到一个 VNode 对象,而这个 VNode 就是 vue2.x 中的虚拟(Virtual) DOM

  2. Virtual DOM 的基本思路分为两步:

  • 使用 JS 模拟DOM树: VNode 的数据结构中还有 VNodeData、VNodeDirective、VNodeComponentOptions,这些数据结构 都是对DOM节点的一些描述

  • DOM模型树通过 diff 算法查找差异,将差异转为真正的DOM节点:这里是通过在源码src/core/vdom/patch.js 文件中的 一个 patch(oldVode, newVode, hydrating) 方法来完成,patch 方法接收三个参数, 旧的VNode、新的VNode以及一个 hydrating 表示是否使用 服务端渲染的DOM元素

总结一下:

  1. 先 new Vue,执行初始化

  2. 挂载 $mount 方法,通过自定义 Render 方法、template文件、el 等生成 Render 函数

  3. 通过 Watcher 监听数据的变化,当数据发生变化时,Render 函数执行生成 VNode 对象

  4. 通过 patch 方法,对比新旧的VNode,然后运行diff算法,来进行增删改真正的DOM元素,使得页面发生变化

32、diff 算法

在我们前端程序中,操作数据要比直接操作DOM结构要快,所以把DOM结构转换为一个数据对象,也就是虚拟DOM, 对它进行增删改等操作,然后再统一转换为真实DOM,提高DOM操作的效率,因此需要对比新旧 DOM 变化,就会用到这个diff算法:

diff算法的核心主要是:

  1. h() 函数,作用是把DOM结构转化为虚拟DOM,有三个参数:标签名、对象、数据(字符串或者数组) h()函数可以根据不同的数据类型执行不同的操作,如果是基本数据类型,h()函数会把它放到节点对象的text属性中,表示文本内容 如果是数组类型对象,h()函数会遍历数组,将其中的每个元素当成子节点放到节点对象 children 数组中

  2. 还有一个 patch 函数,它会将旧的真实DOM元素转换成对应的虚拟DOM节点,然后将新旧虚拟DOM进行对比, 通过一系列替换节点、移动、插入或者删除以及更新属性等操作,将新的虚拟DOM转换成真实DOM,页面就会更新了

总结一下diff算法的主要逻辑是:

diff算法是用来对比新的和旧的虚拟DOM树,它会维护两个指针,一个指向旧的虚拟DOM、一个指向新的虚拟DOM,在对比时:

  1. 首先会对比 节点本身,如果不是同一个节点,就直接删除旧的节点创建新的节点进行替换

  2. 如果是相同的节点,就递归对子节点比较,然后再比较子节点时,diff算法会尽可能重复利用已经存在的节点,减少DOM操作次数

  3. 如果新虚拟DOM中某个节点在旧的DOM中也存在,diff算法会把它移动到正确的位置,而不是进行删除和插入新节点

  4. 如果旧的虚拟DOM中某个节点在新的中不存在,就会删掉它

然后在这个过程中,DOM节点上绑定的 key 起到一个很大的作用,它可以让diff算法进行对比时更准确、更快地完成遍历

更准确是指在复用节点时可以避免使用了错误的旧节点,更快是利用key的唯一性生成map对象, 直接获取对应节点,而不用等遍历一遍才拿到对应节点

(渡一) 当组件创建和更新时,vue均会执行内部的update函数,该函数使用render函数生成的虚拟dom树,将新旧两树进行对比, 找到差异点,最终更新到真实dom

对比差异的过程叫diff,vue在内部通过一个叫patch的函数完成该过程

在对比时,vue采用深度优先、同层比较的方式进行比对在判断两个节点是否相同时,vue是通过虚拟节点的key和tag来进行判断的

具体来说,首先对根节点进行对比,如果相同则将旧节点关联的真实dom的引用挂到新节点上,然后根据需要更新属性到真实dom, 然后再对比其子节点数组;如果不相同,则按照新节点的信息递归创建所有真实dom,同时挂到对应虚拟节点上,然后移除掉旧的dom。

在对比其子节点数组时,vue对每个子节点数组使用了两个指针,分别指向头尾,然后不断向中间靠拢来进行对比, 这样做的目的是尽量复用真实dom,尽量少的销毁和创建真实dom。如果发现相同,则进入和根节点一样的对比流程, 如果发现不同,则移动真实dom到合适的位置。这样一直递归的遍历下去,直到整棵树完成对比。

diff的时机:

33、scoped 原理

vue中的scoped属性可以隔离组件样式,它主要是通过 PostCss 这个库实现的,PostCss 在vue项目编译的时候, 给组件中所有的DOM添加一个唯一的动态属性,也就是类似 data-v-xxx 的这样一个随机字符串,然后再给所有的css选择器 加上这个对应的属性选择器,来选择组件中的DOM,这样就可以让组件的样式只作用在对应组件的内部DOM元素

34、Echarts 常用配置

echarts是一个著名的图表库,我们下载引入之后,要进行配置来使用,首先要创建一个 DOM 元素作为容器

获取 DOM 元素来初始化 echarts 实例,然后在一个 option 对象里面进行配置,可以配置很多东西, 比如 title 标题组件,可以配置 show、text 是否显示,显示文本等

toolbox 工具栏,可以配置导出图片、数据视图、切换、缩放、是否显示等等

然后还有 图表的类型 type,颜色color、位置、边框、大小、图表纵坐标和横坐标的数据等

最后使用 myEcharts.setOption(option) 方法传入配置的options对象,来绘制整个图表

35、百度地图

  1. 首先要去申请百度地图的密钥,引入地图数据

  2. 在项目中引入地图数据脚本

  3. 在webpack中配置地图数据脚本的基本信息,封装为对象结构---地图对象

  4. 在项目中引入这个地图对象,并进行配置:

    • 导入地图,CDN导入即可,

    • 设置地图的容器div,设置缩放比例,是否开启拖拽等

    • 还有设置坐标点,添加覆盖物(行政区,景点介绍)

    • 限定地图显示范围(需要四个坐标点)

36、前端工程化

web业务日益复杂化和多元化,前端开发从 WebPage 模式为主转变为 WebApp 模式为主,前端工程化是使用软件工程的技术和方法, 对前端的开发流程、技术、工具、经验等进行规范化、标准化,其主要目的为了提高效率、降低成本

主要应该从模块化、组件化、规范化、自动化四个方面思考。

  1. 模块化:将一个大文件拆分成相互依赖的小文件,再进行统一的拼装和加载,具体有:

    • js模块化:模块化加载方案,如 CommonJS、AMD、CMD

    • css模块化:sass、less、stylus等实现了css的文件拆分,选择器全局污染问题没解决 从工具层面,创造出Shadow DOMCSS in JSCSS ModulesShadow DOM解决全局污染问题,目前很多浏览器不兼容, CSS in JS是彻底抛弃css,使用js或json来写样式,处理伪类等问题比较困难 CSS Modules仍然使用css,只是让js来管理依赖,目前来看是最好的解决方案

    • 资源模块化:

  2. 组件化:每个组件会包含模板(HTML)+样式(CSS)+逻辑(JS),是一个功能完备的结构单元

  3. 规范化:目录结构的命名规范,比如 js/css/img 等文件规范,还有接口规范、文档规范等

  4. 自动化:对各种资源的自动合并,比如图标合并、持续集成、自动化构建、部署、测试等

37、支付功能怎么做

支付功能是通过调用微信、支付宝等支付平台的API接口完成的,要去对应的平台注册账号,得到授权才能调用api接口

  1. 支付宝支付的话,我们前端一般就是有一个商品订单页面,然后调用后台的支付接口,返回订单号、支付金额等信息

然后我们前端就把订单号、金额等参数展示到用户面前,用户确定支付,调用预支付接口,然后跳到支付宝支付页面

  1. 还有微信支付的话,后台一般会返回一个二维码地址,我们前端就需要安装插件 npm install qrcodejs2 ,调用这个库

地址渲染成二维码放到页面上,用户扫描二维码进行支付,后台就会收到支付成功的结果,我们前端呢就需要去做一个轮询,

不断地请求接口来查询是否支付完成,可以在生命周期里面来做轮询,收到支付成功结果之后跳出页面,销毁这些事件

38、vue3的新特性

首先是这个双向绑定原理的优化,由 ES5 的 Object.defineProperty 改成了 ES6 的 Proxy 代理,速度更快了,性能会更好

还有 Vdom(虚拟DOM) 的重写、对模板编译进行了优化,提高了性能和可维护性

支持这个碎片化,template模板下不再限定一个根标签了,对 ts 语言提升支持,提供更好的类型检查

还有支持这个 tree-shaking ,去除无用代码和模块,使得代码体积更小,

最后还有一个,实现用 DOM 方式进行 WebGL 编程

Released under the MIT License.