Vue
1. 如何理解vue的响应式原理?Proxy和Object.defineproperty的优缺点
- vue的响应式原理主要是是利用了Object.defineproperty的getter和setter方法的观察者模式。每个组件初始化的时候都会注册一个data属性并绑定getter和setter方法然后再new一个watcher对象,此时watcher对象会立即调用组件的render函数生成一个虚拟DOM。在调用这个函数的时候会使用到data里面的属性,就会触发getter方法,然后会将当前的watcher函数注册到sub里面,当data的属性发生改变后就会遍历sub里面所有的watcher对象,通知他们去重新渲染。
- proxy的优势:
- 可以直接监听对象和数组,而非属性
- 有13种拦截方法不限于 apply. ownKeys. deleteProperty. has 等等
- proxy的返回值是个新对象,可以直接操作
- Object.definedproperty:
- 兼容性好,支持IE9以下
2. 解释单向数据流和双向数据绑定
- 对于vue来说组件之间的数据传递具有单向数据流这样的特性称之为单向数据流,单向数据流(Unidirectional data flow)方式使用一个上传数据流和一个下传数据流进行双向数据通信,两个数据流之间相互独立,单向数据流指只能从一个方向来修改状态。
- 双向数据绑定即为数据发生变化时,视图也跟着变化,当视图发生变化的时候,数据也会跟着同步变化,两个数据流之间互为影响。
3. JQuery 和 Vue 的区别
- jQuery专注视图层,通过操作DOM去实现页面逻辑的渲染
- Vue专注于数据层,通过数据的双向绑定通过修改数据最终表现在DOM上面,减少了对dom的操作。Vue 使用了组件化思想,使得项目子集职责清晰,提高了开发效率,方便重复利用,便于协同开发
4. 组件通信的方式都有哪些
- 父传子:选项式:父组件在使用子组件的时候通过自定义属性向子组件传参,子组件通过props接收;组合式:在子组件中引入
defineProps
,用法和选项式props
一样 - 子传父:选项式:子组件在某一个时机向父组件发送通知,触发一个事件,在父组件使用子组件时,给子组件绑定一个自定义事件,当子组件触发事件通知父组件时,执行这个自定义事件会触发一个函数,这个函数的参数就是子组件传递的数据。组合式:在子组件中引用
defineEmits
,并用这个方法的调用结果给父组件传递自定义事件,与选项式的this.$emit
相同 - 祖孙级:选项式:在祖先级组件中使用provide将数据暴露给所有后代,可以是data中的数据,也可以是methods中的方法,在需要使用的子孙级组件中使用inject接收到祖先暴露的数据即可使用组合式:在父组件和子组件引入对应的方法,其他与选项式相同
- 属性透传:在祖先组件通过父传子方式传递数据时,在子组件中如果需要直接通过props使用,如果不需要,在当前子组件的子组件(孙子级组件的调用)上添加一个
v-bind="$attrs"
,将当前组件接收到的属性和方法传递给孙子组件。 - 兄弟通信:Vue 3 中移除了 eventBus,但可以借助第三方工具来完成。Vue 官方推荐使用
mitt
或tiny-emitter
。由于在组合式中没有this,所以使用vuex需要引入useStore
,并const store = useStore()
,store就相当于this.$store
5. 导航守卫的流程
- 导航被触发
- 在失活的组件里面调用
beforeRouteLeave
守卫 - 调用全局前置守卫(
beforeRouteEnter
) - 在重用的组件里面调用
beforeRouteUpdate
守卫 - 在路由配置里调用
beforeEnter
- 解析异步路由组件
- 在被激活的组件里面调用
beforeRouteLeave
守卫 - 调用全局的
beforeResolve
守卫 - 导航被确认
- 调用全局的
afterEach
钩子 - 触发DOM更新
- 调用
beforeRouteEnter
守卫传给next
的回调函数,创建好的组件实例会作为参数传入
6. vue等单页面应用的优缺点
- 优点:
- 单页面内容的改变不需要重新加载整个页面,web应用更具有响应性
- 切换不会出现白屏,也不会出现假死并有“闪烁”现象
- 服务器压力小,服务器只用出数据;不用管展示逻辑和页面合成,吞吐能力提升
- 良好的前后端分离。后端不再负责模板渲染,输出页面工作,后端API通用化,,即同一套后端代码,不用修改就可以用于web页面,手机平板等客户端
- 缺点:
- 首次加载耗时较多
- SEO问题,不利于百度,360等搜索引擎收录
- 容易造成css命名冲突
- 前进、后退、地址栏等都需要程序进行管理,页面复杂度高,需要一定的技能水平和开发成本
7. vue如何实现单页面应用
- 通常的url地址由以下组成:端口号,域名,协议,路径,参数,哈希值,当哈希值改变时,页面不会进行跳转,单页面应用就是利用了这点给window注册
onhashchange
事件,当哈希值改变时通过location.hash
就能获得对应的哈希值,然后跳转到相应的页面。- hash通过监听浏览器的
onhashchange
事件,查找对应的路由规则 - history原理:利用h5新增的
history
中的两个APIpushStade()
和replaceState()
和一个事件onpopState
监听URL的变化
- hash通过监听浏览器的
8. data为什么是个函数
- 在Vue组件中data必须是个函数而不能是个对象。因为Vue组件可以同时存在多个实例如果直接使用对象形式的data选项,那么多个实例将会共用一个选项,造成数据干扰。因此将data选项设置为函数可以让每个实例都有自己独立的data对象
9. ref和reactive的简单理解
- ref和reactive都是Vue3中监听数据的方法,本质是proxy
- ref基本数据类型和引用数据类型都可以监听(一般用于监听基本),reactive只能监听对象
- ref底层还是reactive,ref是对reactive的二次包装,ref定义的数据访问的时候要多加一个
.value
10. 谈谈对Vue3的了解
- 新增亮点:
- 性能比Vue2快了1.2~2倍
- 支持tree-shaking,按需编译,体积比Vue2更小
- 支持组合式API
- 更好的支持TS
- 更先进的组件
- 如何实现性能比Vue2更好
- diff算法更快
- Vue2是需要全局去比较每个节点,若发现变化,就去更新节点
- Vue3是在创建的虚拟DOM中,根据DOM的内容会不会发生改变,添加静态标记,谁有flag比较谁
- 静态提升
- vue2中的数据无论是否需要更新,每次都会重新创建后再渲染;vue3中对不参与更新的元素会做静态提升,只被创建一次,渲染的时候直接复用即可
- 事件侦听缓存
- 默认情况下,onclick为动态绑定所以每次都会侦听他的变化,但因为是同一函数,没必要追踪变化,所以直接复用即可
- 在之前会添加静态标记,把点击事件当作动态属性,会进行diff算法比较,但在事件监听缓存之后就没有静态标记了,可以直接复用
- diff算法更快
- 为什么Vue3比Vue2体积更小
- 在vue3中创建项目除了vue-cli,webpack外还有一种方法是Viti,Viti是一款作者开发的有意取代webpack的工具,原理是利用ES6的import会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去webpack的打包事件
11. 你做过的Vue性能优化
- 首屏加载优化
- 路由懒加载
- 开启服务器Gzip
-
开启Gzip就是一种压缩技术,需要前端提供压缩包,然后再服务器开启压缩,文件在服务器压缩后传递给浏览器,浏览器解压后再进行解析。首先安装webpack提供
compression-webpack-pluging
进行压缩,然后咋vue.config.js:const CompressionWebpackPlugin = require('compression-webpack-plugin') const productionGzipExtensions = ['js', 'css']......plugins: [ new CompressionWebpackPlugin({ algorithm: 'gzip', test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')\$'), threshold: 10240, minRatio: 0.8 }) ]....
-
- 启动CDN加速
- 继续采用cdn的方式引入第三方资源,就可以缓解我们服务器的压力,原理是将我们的服务器压力分给其他服务器点
- 代码层面的优化
- computed和watch区分使用场景
- computed: 是计算属性,依赖其他属性值,并且有缓存,只有他依赖的属性的值发生改变,下一次获取computed的值才会重新计算;当我们需要数值计算,并且依赖于其他数据时,应该使用computed,因为可以利用computed的缓存性,避免每次获取值时都重新计算
- watch:类似于某些数据的监听回调,每当监听的数据发生变化时都会执行回调进行后续操作;当我们需要在数据变化时执行异步或者开销较大的代码时,应该使用watch,使用watch选项允许我们执行异步操作,限制我们执行改操作的频率,并且在我们得到最终结果前,设置中间状态,这些都是计算属性无法实现的。
- v-if和v-show区分使用场景:v-if适用于很少改变条件的情况,v-show适用于需要频繁切换的情况。主要的优化点在于减少页面中的dom总数,我比较倾向使用v-if,因为减少了页面dom总数
- v-for遍历必须为item添加key,且避免同时使用v-if和v-for:循环调用子组件时添加key,key可以唯一标识一个循环体,一般情况下推荐使用id作为key,避免同时使用v-if,v-for优先级比v-if高,如果每一次都要遍历整个数组,会影响速度。
- computed和watch区分使用场景
- webpack对图片进行压缩
- 避免内存泄漏
- 减少ES6转为ES5的冗余代码
- 减少闭包(避免内存浪费)
12. 如何自定义指令
- 通过directive来自定义指令:自定义指令分为全局指令和局部指令。自定义指令也有几个狗子函数,常用的有bind和update,当 bind 和 update 时触发相同行为,而不关心其它的钩子时可以简写。
Vue.directive("focus", {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus();
},
});
Vue.directive("color-swatch", function (el, binding) {
el.style.backgroundColor = binding.value;
});
13. Vue路由实现的底层原理
- 在Vue中利用数据劫持(defineProperty)在原型(prototype)上初始化一些getter
- 分别是router代表当前Router的实例
- route代表当前Router的信息
- 在install中也全局注册了
router-view,router-link
,其中的Vue.util.defineReactive,这是Vue观察者劫持数据的方法,劫持_route,当_route触发setter方法的时候,则会通知依赖的组件 - 接下来在init中,会挂载判断是路由的模式,是
history
或者hash
,点击行为按钮,调用hashchange或者popstate
的同时更新_route,_route的更新会触发router-view的重新渲染。
14. 用过插槽吗?
- 都用过。插槽相当于预留了一个位置,可以将我们书写在组件内的内容放入,写一个插槽就替换一次,两个就替换两次。为了自定义插槽的位置我们可以给插槽取名,它会根据插槽名来插入内容,一一对应。
15. 组合式侦听器和选项式的区别
- 选项式:只能监听一个数据,如果需要监听多个,可以书写一个对象计算属性,把要监听的数据放到里面;并且组合式使用侦听器的形式是对象,可以直接使用
- 组合式:可以监听多个数据,使用前先从 vue 里面引入 watch 方法,第一个参数为侦听的数据,如果侦听多个可以传递一个数组,第二个参数为回调函数,里面书写需要执行的代码,第三个参数为配置项
16. 什么是组件化开发
- 组件化就是把可以复用的,独立的,基础的,功能专一的代码封装到方法或者代码片段里,在未来需要的时候引入使用。用极少的代码实现与之前相同的功能,避免了相同功能代码的复写,提高了开发效率
17. vue的生命周期
- 组合式
- setup:创建实例化对象之前/之后执行
- onBeforeMount: 页面挂载成功之前执行
- onMounted: 页面挂载成功之后执行
- onBeforeUpdate: 组件更新之前执行
- onUpeaded: 组件更新之后执行
- onBeforeUnmount: 组件卸载之前执行
- onUnmounted: 组件卸载之后执行
- 选项式
- beforeCreate: 创建实例化对象之前执行
- created: 创建实例化对象之后执行
- beforeMounte: 页面挂载成功之前执行
- mounted: 页面挂载成功之后执行
- beforeUpdate: 组件更新之前执行
- upeaded: 组件更新之后执行
- beforeUnmount: 组件卸载之前执行
- unmounted: 组件卸载之后执行
18. vue-router的使用流程
- 先安装 vue-router 安装包,在src路径下创建router文件夹和index.js文件,在文件里面从 vue-router 引入
createRouter,createWebHashHistory
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes
})
- routes是一个数组,里面书写我们需要用到的路由,并把router导出,在main.js引入并挂载全局
- 第二种方法是在创建项目的时候,直接选择router选项
19. vue中对key属性的理解
- 在vue中使用v-for渲染数据的时候,通常会给标签加上key属性,属性的值是唯一值,通常使用id或者下标
- key属性会存在虚拟DOM中,是虚拟DOM对象的标识,当数据发生变化时,vue会根据用户对应的新数据修改来生成新的虚拟DOM。当旧虚拟DOM中找到了与新虚拟DOM相同的key时,若虚拟DOM中内容没变, 直接使用之前的真实DOM!若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。当旧虚拟DOM中未找到与新虚拟DOM相同的key时,创建新的真实DOM,随后渲染到到页面。
- 总结:key属性可以减少虚拟DOM的生成,减少渲染内容,优化代码。
20. 如何理解虚拟DOM
- 虚拟DOM是一个虚拟的、内存中的DOM节点树,通过js对象来模拟真实的DOM结构,而不是直接操作真实DOM。虚拟DOM通常会在每次页面渲染时被创建,通过对虚拟DOM的修改来实现对页面的更新。虚拟DOM的存在减少了对DOM的操作次数。
21. 说说怎么封装功能组件
- 先将功能组件的基本结构和样式搭建出来,并且定义一些变量,根据变量的值判断一部分标签是否展示,功能组件也会接收或者传递一些参数进行组件通信,尽可能的少给功能组件添加事件和功能,因为会限制功能组件的泛用性。
22. git的常用指令
- git clone url LocalPath ------ url为git地址,LocalPath是代码要存在本机的位置
- git add . ------ 上传文件
- git commit -m "注释" ------ 给代码添加注释
- git pull ------ 更新本地代码
- git push ------ 提交代码
- git checkout 分支 ------ 切换到某分支
- git mereg 分支 ------ 合并到当前分支
23. vuex的5个模块
- state:唯一数据源,用来书写vuex里面的数据
- getter:计算属性,可以理解为页面中的计算属性,用法也一致
- mutation:内部书写着用于更改vuex的数据的方法,方法的第一个参数为state,用它获取vuex的数据
- action:类似于mutation,只不过mutation内书写同步代码,action书写异步代码,不能直接修改state
- module:当state内的数据比较多,应用变得比较复杂时,用于引入分割后的state模块
24. vue3中创建响应式数据
- ref---基本数据类型和引用数据类型都可以,ref响应后会返回一个对象,响应后的数据在对象的value属性里
- reactive---只能响应引用数据类型,响应的返回值就是数据本身
- shallowRef/shallowReactive---只能响应第一层数据,深层数据不能响应式
25. 如何理解vuex
- vuex是专门为vue.js应用程序开发的状态管理模式和库。有利于我们的开发。在使用的时候我们会先下载vuex,然后再src目录下创建stores文件夹并创建index.js文件,从vuex中引入
createStore
,通过这个方法创建一个store实例,然后再main.js中引入和挂载,最后再需要的组件使用即可
26. 如何理解路由
- 前端路由,就是一个前端不同页面的状态管理器,即前端页面的路径和页面的匹配规则,可以不向后台发送请求而直接通过前端技术实现多个页面的效果。
27. 为什么使用$nextTick()
$nextTick()
是vue.js框架中的一个方法,主要用于DOM操作。当我们修改vue中的数据时,vue会自动更新视图,并异步执行$nextTick()中的回调函数,这个过程可以确保DOM已经更新,可以操作最新的DOM
28. vue里获取标签的方法
- 组合式: 给需要获取的标签添加ref属性,并创建对应属性值的响应式数据变量,变量名.value就是获取到的标签
- 给需要获取的标签添加ref属性,this.$refs.ref的属性值 就是获取的标签
29. 如何理解数据驱动视图
- 数据驱动视图就是通过对数据的操作来达到页面的改变,减少了对DOM的操作,简化了开发。
30. vue开发和源生开发的区别
- 在vue开发中,利用了响应式数据减少了对DOM的操作,同时也提供了方便的内置指令;还有vue中利用了组件化开发,将功能拆分成组件,提高了代码的利用率,减小了内存大小。
31. vue的常用指令
- v-text: 用于文本替换,会替换标签内原本所有的文本
- v-html: 也是用于文本替换, 能够认识文本中的 html 和 css,但是不推荐使用
- v-show: 通过 css 控制标签展示还是隐藏
- v-if/v-else/v-else-if: 通过 是否加载标签, 来控制标签展示还是隐藏
- v-for: 遍历一组数据, 循环渲染一些标签
- v-on/@: 绑定一些事件
- v-bind: 绑定一些属性
- v-model: 双向绑定数据
- v-solt: 插槽内需要使用的指令
- v-pre: 跳过 vue 的编译
- v-once: 只会被编译一次, 后续的编译与当前标签没有关系
- v-memo: 只有绑定的依赖发生变化的时候, 才会重新渲染标签
- v-cloak: 用于隐藏尚未完成编译的 DOM 模板。
32. 侦听器和计算属性的区别
- 侦听器
- 页面打开默认不执行,除非添加配置项
- 没有缓存
- 内部可以书写异步代码
- 不需要书写 return
- 可以一个值导致多个值的变化(一对多)
- 计算属性
- 页面打开默认执行一次
- 有缓存
- 内部不能书写异步代码
- 必须书写 return
- 可以多个值导致一个值的变化(多对一)
33. 如何理解 框架,库,插件
34. 项目上的难点
35. 组合式开发和选项式开发的区别
JS面试题
1. map和foreach的区别
- foreach:只是遍历数组,在回调函数内部书写需要执行的代码,没有返回值
- map:遍历数组,并对数组进行加工,必须写return,返回值是加工后的数组
2. 改变原数组的方法
- pop,push,shift,unshift,reverse,splice,sotr
3. 什么是Promise,then过多怎么办,try...catch解决报错缺点
- Promise是一种解决异步编程的方案,同时也可以解决回调地狱
- 在日常开发中如果.then过多可以使用async和await解决
- async和await的缺点是只能接收Promise的成功状态,通常的解决方式是只用成功状态返回,在内部约定一个内容判断是否成功,另一种是通过try...catch,正常执行try内的代码,成功则不执行catch里面,- 如果报错, 不会爆出错误, 不会终止程序, 而是执行 catch 的代码
4. 原型链和作用域链的区别
- 原型链是对象内属性的查找规则,作用域链是变量的查找规则
5. const a = {name:'zhangsan'};a.name可以修改吗
- 可以,a存储的是地址,修改a.name不会改变地址
6. function fn () {console.log(a)} fn() 结果
- 会发生报错,根据变量的查找规则,首先在当前作用域查找,如果没有到上层作用域查找,最上层也没有就会报错
7.构造函数,实例化对象,原型对象的关系
- 构造函数的原型空间和实例化对象的原型对象指向的是地址相同的同一个对象
8. 构造函数注意事项
- 实例化构造函数必须书写new关键字
- 构造函数内的this指向默认创建的对象
- 构造函数内部书写return
9. ts和js的区别
- ts 引入了类型的概念,对 js 进行了拓展,并增加了一些新的特性
- ts 代码需要通过编译器编译成 js 代码然后交由 js解析器进行执行
- ts 完全兼容 js ,换言之,任何的js代码都可以当作 ts 执行