前端 vue 面试题
父子组件生命周期调用顺序(简单)
渲染顺序:先父后子,完成顺序:先子后父
更新顺序:父更新导致子更新,子更新完成后父
销毁顺序:先父后子,完成顺序:先子后父
diff 算法
时间复杂度: 个树的完全 diff
算法是一个时间复杂度为 O(n*3)
,vue 进行优化转化成 O(n)
。
理解:
最小量更新,
key
很重要。这个可以是这个节点的唯一标识,告诉diff
算法,在更改前后它们是同一个 DOM 节点扩展
v-for
为什么要有key
,没有key
会暴力复用,举例子的话随便说一个比如移动节点或者增加节点(修改 DOM),加key
只会移动减少操作 DOM。只有是同一个虚拟节点才会进行精细化比较,否则就是暴力删除旧的,插入新的。
只进行同层比较,不会进行跨层比较。
diff 算法的优化策略:四种命中查找,四个指针
旧前与新前(先比开头,后插入和删除节点的这种情况)
旧后与新后(比结尾,前插入或删除的情况)
旧前与新后(头与尾比,此种发生了,涉及移动节点,那么新前指向的节点,移动到旧后之后)
旧后与新前(尾与头比,此种发生了,涉及移动节点,那么新前指向的节点,移动到旧前之前)
v-show 与 v-if 有什么区别?
v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。
所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。
Vue 性能优化
编码优化:
事件代理
keep-alive
拆分组件
key
保证唯一性路由懒加载、异步组件
防抖节流
Vue 加载性能优化
第三方模块按需导入(
babel-plugin-component
)图片懒加载
用户体验
app-skeleton
骨架屏shellap
p 壳pwa
SEO 优化
预渲染
实际工作中,你总结的 vue 最佳实践有哪些
从编码风格、性能、安全等方面说几条:
编码风格方面:
命名组件时使用“多词”风格避免和
HTML
元素冲突使用“细节化”方式定义属性而不是只有一个属性名
属性名声明时使用“驼峰命名”,模板或
jsx
中使用“肉串命名”使用
v-for
时务必加上key
,且不要跟v-if
写在一起
性能方面:
路由懒加载减少应用尺寸
利用
SSR
减少首屏加载时间利用
v-once
渲染那些不需要更新的内容一些长列表可以利用虚拟滚动技术避免内存过度占用
对于深层嵌套对象的大数组可以使用
shallowRef
或shallowReactive
降低开销避免不必要的组件抽象
安全:
不使用不可信模板,例如使用用户输入拼接模板:
template: <div> + userProvidedString + </div>
避免使用
v-html
,:url
,:style
等,避免html
、url
、样式等注入
Vue-router 路由模式有几种
vue-router
有 3
种路由模式:hash
、history
、abstract
,对应的源码如下所示
其中,3 种路由模式的说明如下:
hash
: 使用URL hash
值来作路由,支持所有浏览器history
: 依赖HTML5 History API
和服务器配置abstract
: 支持所有JavaScript
运行环境,如Node.js
服务器端。如果发现没有浏览器的API
,路由会自动强制进入这个模式.
参考 前端进阶面试题详细解答
Vue-router 基本使用
mode
hash
history
跳转
编程式(js 跳转)
this.$router.push('/')
声明式(标签跳转)
<router-link to=""></router-link>
vue 路由传参数
使用
query
方法传入的参数使用this.$route.query
接受使用
params
方式传入的参数使用this.$route.params
接受
占位
Vue 中组件和插件有什么区别
1. 组件是什么
组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在 Vue 中每一个.vue 文件都可以视为一个组件
组件的优势
降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、时间、范围等组件作具体的实现
调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单
提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级
2. 插件是什么
插件通常用来为 Vue
添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
添加全局方法或者属性。如:
vue-custom-element
添加全局资源:指令/过滤器/过渡等。如
vue-touch
通过全局混入来添加一些组件选项。如
vue-router
添加
Vue
实例方法,通过把它们添加到Vue.prototype
上实现。一个库,提供自己的
API
,同时提供上面提到的一个或多个功能。如vue-router
3. 两者的区别
两者的区别主要表现在以下几个方面:
编写形式
注册形式
使用场景
3.1 编写形式
编写组件
编写一个组件,可以有很多方式,我们最常见的就是 vue 单文件的这种格式,每一个.vue
文件我们都可以看成是一个组件
vue 文件标准格式
我们还可以通过template
属性来编写一个组件,如果组件内容多,我们可以在外部定义template
组件内容,如果组件内容并不多,我们可直接写在template
属性上
编写插件
vue
插件的实现应该暴露一个 install
方法。这个方法的第一个参数是 Vue
构造器,第二个参数是一个可选的选项对象
3.2 注册形式
组件注册
vue 组件注册主要分为全局注册与局部注册
全局注册通过Vue.component
方法,第一个参数为组件的名称,第二个参数为传入的配置项
局部注册只需在用到的地方通过components
属性注册一个组件
插件注册
插件的注册通过Vue.use()
的方式进行注册(安装),第一个参数为插件的名字,第二个参数是可选择的配置项
注意的是:
注册插件的时候,需要在调用 new Vue()
启动应用之前完成
Vue.use
会自动阻止多次注册相同插件,只会注册一次
4. 使用场景
组件 (Component) 是用来构成你的 App 的业务模块,它的目标是
App.vue
插件 (Plugin) 是用来增强你的技术栈的功能模块,它的目标是 Vue 本身
简单来说,插件就是指对Vue
的功能的增强或补充
既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异
响应式数据变化,
Vue
确实可以在数据变化时,响应式系统可以立刻得知。但是如果给每个属性都添加watcher
用于更新的话,会产生大量的watcher
从而降低性能而且粒度过细也得导致更新不准确的问题,所以
vue
采用了组件级的watcher
配合diff
来检测差异
Vue.set 的实现原理
给对应和数组本身都增加了
dep
属性当给对象新增不存在的属性则触发对象依赖的
watcher
去更新当修改数组索引时,我们调用数组本身的
splice
去更新数组(数组的响应式原理就是重新了splice
等方法,调用splice
就会触发视图更新)
基本使用
以下方法调用会改变原始数组:
push()
,pop()
,shift()
,unshift()
,splice()
,sort()
,reverse()
,Vue.set( target, key, value )
调用方法:
Vue.set(target, key, value )
target
:要更改的数据源(可以是对象或者数组)key
:要更改的具体数据value
:重新赋的值
相关源码
我们阅读以上源码可知,vm.$set 的实现原理是:
如果目标是数组 ,直接使用数组的
splice
方法触发相应式;如果目标是对象 ,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用
defineReactive
方法进行响应式处理(defineReactive
方法就是Vue
在初始化对象时,给对象属性采用Object.defineProperty
动态添加getter
和setter
的功能所调用的方法)
Vue computed 实现
建立与其他属性(如:
data
、Store
)的联系;属性改变后,通知计算属性重新计算
实现时,主要如下
初始化
data
, 使用Object.defineProperty
把这些属性全部转为getter/setter
。初始化
computed
, 遍历computed
里的每个属性,每个computed
属性都是一个watch
实例。每个属性提供的函数作为属性的getter
,使用Object.defineProperty
转化。Object.defineProperty getter
依赖收集。用于依赖发生变化时,触发属性重新计算。若出现当前
computed
计算属性嵌套其他computed
计算属性时,先进行其他的依赖收集
vue-cli 工程常用的 npm 命令有哪些
下载
node_modules
资源包的命令:
启动
vue-cli
开发环境的 npm 命令:
vue-cli
生成 生产环境部署资源 的npm
命令:
用于查看
vue-cli
生产环境部署资源文件大小的npm
命令:
在浏览器上自动弹出一个 展示
vue-cli
工程打包后app.js
、manifest.js
、vendor.js
文件里面所包含代码的页面。可以具此优化vue-cli
生产环境部署的静态资源,提升 页面 的加载速度
Vue 3.0 中的 Vue Composition API?
在 Vue2 中,代码是 Options API 风格的,也就是通过填充 (option) data、methods、computed 等属性来完成一个 Vue 组件。这种风格使得 Vue 相对于 React 极为容易上手,同时也造成了几个问题:
由于 Options API 不够灵活的开发方式,使得 Vue 开发缺乏优雅的方法来在组件间共用代码。
Vue 组件过于依赖
this
上下文,Vue 背后的一些小技巧使得 Vue 组件的开发看起来与 JavaScript 的开发原则相悖,比如在methods
中的this
竟然指向组件实例来不指向methods
所在的对象。这也使得 TypeScript 在 Vue2 中很不好用。
于是在 Vue3 中,舍弃了 Options API,转而投向 Composition API。Composition API 本质上是将 Options API 背后的机制暴露给用户直接使用,这样用户就拥有了更多的灵活性,也使得 Vue3 更适合于 TypeScript 结合。
如下,是一个使用了 Vue Composition API 的 Vue3 组件:
显而易见,Vue Composition API 使得 Vue3 的开发风格更接近于原生 JavaScript,带给开发者更多地灵活性
如果让你从零开始写一个 vue 路由,说说你的思路
思路分析:
首先思考vue
路由要解决的问题:用户点击跳转链接内容切换,页面不刷新。
借助
hash
或者 history api
实现url
跳转页面不刷新同时监听
hashchange
事件或者popstate
事件处理跳转根据
hash
值或者state
值从routes
表中匹配对应component
并渲染
回答范例:
一个SPA
应用的路由需要解决的问题是 页面跳转内容改变同时不刷新 ,同时路由还需要以插件形式存在,所以:
首先我会定义一个
createRouter
函数,返回路由器实例,实例内部做几件事
保存用户传入的配置项
监听
hash
或者popstate
事件回调里根据
path
匹配对应路由
将
router
定义成一个Vue
插件,即实现install
方法,内部做两件事
实现两个全局组件:
router-link
和router-view
,分别实现页面跳转和内容显示定义两个全局变量:
$route
和$router
,组件内可以访问当前路由和路由器实例
Vue 项目中你是如何解决跨域的呢
一、跨域是什么
跨域本质是浏览器基于同源策略的一种安全手段
同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能
所谓同源(即指在同一个域)具有以下三个相同点
协议相同(protocol)
主机相同(host)
端口相同(port)
反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域
一定要注意跨域是浏览器的限制,你用抓包工具抓取接口数据,是可以看到接口已经把数据返回回来了,只是浏览器的限制,你获取不到数据。用 postman 请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。
Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题 ?你能说说如下代码的实现原理么?
1)Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题
Vue 使用了 Object.defineProperty 实现双向数据绑定
在初始化实例时对属性执行 getter/setter 转化
属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的(这也就造成了 Vue 无法检测到对象属性的添加或删除)
所以 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
2)接下来我们看看框架本身是如何实现的呢?
Vue 源码位置:vue/src/core/instance/index.js
我们阅读以上源码可知,vm.$set 的实现原理是:
如果目标是数组,直接使用数组的 splice 方法触发相应式;
如果目标是对象,会先判读属性是否存在、对象是否是响应式,
最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理
defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法
Proxy 与 Object.defineProperty 优劣对比
Proxy 的优势如下:
Proxy 可以直接监听对象而非属性;
Proxy 可以直接监听数组的变化;
Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;
Object.defineProperty 的优势如下:
兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。
v-model 的原理?
我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
text 和 textarea 元素使用 value 属性和 input 事件;
checkbox 和 radio 使用 checked 属性和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。
以 input 表单元素为例:
如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:
Vue 中组件生命周期调用顺序说一下
组件的调用顺序都是先父后子
,渲染完成的顺序是先子后父
。
组件的销毁操作是先父后子
,销毁完成的顺序是先子后父
。
加载渲染过程
子组件更新过程
父组件更新过程
销毁过程
v-if 和 v-show 的区别
v-if 在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点。
v-show 会被编译成指令,条件不满足时控制样式将对应节点隐藏 (display:none)
评论