2021 前端 VUE 面试题总汇

文章目录

        • 1、vue的生命周期
        • 2、Vue2.x 双向绑定原理
        • 3、Vue3.x 响应式原理
        • 4、v-for 为什么要加上 key
        • 5、Vdom的理解
        • 6、vuex 的结构,以及 actiion 和 mutation 的区别
        • 7、axios 的原理以及优点,如何终止 axios 请求
        • 8、vue 的缺点
        • 9、vue 和 react 区别
        • 10、computed 和 watch 区别,以及 computed 如何实现缓存和更新的
        • 11、hash 和 history 区别 ,分别是怎么实现的
        • 12、nextTick 作用和实现
        • 13、solt 插槽种类以及原理
        • 14、keep-alive使用和原理
        • 15、MVVM 和 MVC
        • 16、template 模板引擎的渲染过程
        • 17、SPA页面的理解
        • 18、vue 性能优化?
        • 19、父子组件加载顺序
        • 20、Vue.use是做什么的,原理是什么
        • 21、Proxy 与 Object.defineProperty 优劣对比
        • 22、v-model 的原理
        • 23、v-if 和 v-show 的区别
        • 24、v-for 和 v-if 优先级
        • 25、Vue.mixin 使用场景和原理
        • 26、Vue.set
        • 27、组件中写 name 选项有哪些好处
        • 28、vue 组件通讯有哪些方法
        • 29、vue 组件的data 为什么是一个函数
        • 30、data 里面数据量比较大如何优化
        • 31、子组件里面可以修改父组件的值吗
        • 32、生命周期钩子是如何实现的
        • 33、什么是 vue-loader
        • 34、vue 是怎么检测数组的变化的
        • 35、vue 是怎样依赖收集的?(dep 和 Watcher 是什么关系)
        • 36、vue路由的钩子函数(导航守卫)
        • 37、vue $route 和 $router 的区别
        • 38、vue 中的 diff 算法原理
        • 39、Vue. extend 能做什么
        • 40、vue 的 mixin 和 mixins 区别
        • 41、vue 组件渲染和更新的过程
        • 42、vue 为什么采用异步渲染
        • 43、vue 为什么要使用异步组件
        • 44、vue 如何快速定位那个组件出现性能问题的
        • 45、vue3.x 对比 vue2.x 变化
        • 46、watch 的 deep 如何实现的
        • 47、v-html 会导致那些问题
        • 48、v-el作用
        • 48、说说vue的动态组件
        • 49、怎么定义vue-router的动态路由?怎么获取传过来的值?
        • 50、自定义指令
        • 51、自定义过滤器
        • 52、vue 中 extend、mixins、extends的区别

1、vue的生命周期

beforeCreate:实例初始化之后;数据观测和事件配置之前调用,组件的选项对象还没有创建,el挂载和data都没有初始化,无法访问方法和数据。
created:实例创建完成之后调用;已经完成了数据观测,属性方法的运算,watch/event 事件回调,data数据初始化已经完成,el挂载还没有开始。
beforeMount:挂载之前调用;el初始化已经完成,vdom已经完成data和模板的结合生成html,但是还没有挂载到html页面里。
mounted:挂载完成之后调用;模板中的html渲染到html页面里。
beforeUpdate:数据更新之前调用;发生在vdom重新渲染和打补丁之前,可以进一步更改状态,不会触发重新渲染。
updated:数据更新,dom渲染之后调用;要避免在这里更改状态,防止更新导致的无线循环。
beforeDestory:实例销毁之前调用;还可以获取到this,一般用于清除定时器和监听事件等。
destoryed:实例销毁之后调用;所有监听事件被清除,子实例被销毁。
activated:(keep-alive的生命周期)页面第一次进入的时候,钩子触发的顺序是created->mounted->activated
deactivated:(keep-alive的生命周期)页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发activated

2、Vue2.x 双向绑定原理

vue 是通过 数据劫持 结合 发布者-订阅者 来实现双向绑定;数据劫持通过Object.defineProperty()方法的get和set方法来重新定义,set是设置属性值触发的函数,get是读取属性值触发的函数;

底层实现:

1、监听器observer,object.defineProperty 的set方法监听所有属性,有变动通知订阅者,在这里还需要创建一个消息订阅器Dep来收集订阅者
2、订阅者watcher,接收变化通知执行相应函数,更新view
3、解析器compile,扫描解析每一个节点的指令,初始化订阅器

详细过程参考

3、Vue3.x 响应式原理

Vue3.x改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。

Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?

判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。

监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?

我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。

4、v-for 为什么要加上 key

key 的主要功能是提高 vdom 的更新速度;因为 vue 在 patch (补丁)过程中的 diff 算法对新旧节点比对时是可以通过 key 精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使 patch 过程更高效。(源码位置:src/core/vdom/patch.js)
尽量避免使用index最为key值。

function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

5、Vdom的理解

vdom 就是用 js 对象来描述真实 DOM,虚拟DOM的实现就是普通对象包含tag、data、children等属性对真实节点的描述。(本质上就是在JS和DOM之间的一个缓存);由于直接操作 DOM 性能低,但是 js 层的操作效率高,可以将 DOM 操作转化成对象操作,最终通过diff算法比对差异进行更新DOM (减少了对真实DOM的操作)。虚拟DOM不依赖真实平台环境从而也可以实现跨平台。

优点:

1、保证性能下限:可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能;
2、无需手动操作dom:极大提高我们的开发效率;
3、跨平台:虚拟 DOM 本质上是 JavaScript 对象;

缺点:

无法进行极致优化: 在一些性能要求极高的应用中虚拟DOM无法进行针对性的极致优化

6、vuex 的结构,以及 actiion 和 mutation 的区别

结构:state(初始化数据)、action(异步处理数据)、mutation(唯一能够修改state的操作)、getter(从state中动态获取相关数据)、module(模块化)

区别:

1、action可以异步,mutation必须同步
2、mutation是唯一可以修改state的(commit)
3、action修改state需要经过mutation(dispatch)

7、axios 的原理以及优点,如何终止 axios 请求

原理:属于 XMLHttpRequest,是一个基于 Promise 的 http 请求库,可用于浏览器和 Node

优点:

1、Axios 是一个基于 promise 的 HTTP 库,支持 promise 所有的 API
2、它可以拦截请求和响应
3、对响应回来的内容自动转换成 JSON 类型的数据
4、安全性更高,客户端支持防御 XSRF
5、可以取消请求
6、在浏览器中发送请求会创建 XMLHttpRequests
7、在 node.js 发送请求会创建 http请求

中断请求:使用 CancelToken 函数
1)、使用 cancelToken.sourse 工厂方法创建 cancel token

const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
	cancelToken: source.token
}).catch(function(thrown) {
	if (axios.isCancel(thrown)) {
		console.log('Request canceled', thrown.message);
	}else {
		// 处理错误
	}
});
source.cancel('xxx');

2)、通过传递一个 executor 函数到CancelToken构造函数创建cancel token

const CancelToken = axios.CancelToken;
let cancel
axios.get('/user/12345', {
	cancelToken: new CancelToken(function executor(c){
		cancel = c
	})
});
cancel();

注意: executor 处理器函数接收两个函数 resolve 和 reject 作为参数,异步任务执行成功调用 resolve,否则调用reject。

8、vue 的缺点

1、不支持IE8.0及其以下版本;
2、入门快,但是高阶教程少;
3、生态环境不如其他框架;
4、社区不大;

9、vue 和 react 区别

相同:

1、都使用vdom;
2、都提供了响应式和组件化的视图组件;
3、核心都在UI层(比如路由,状态管理,数据请求交给其他库);
4、都是单向数据流;
5、轻量级

不同:

1、vue 基于 html 的模板语法,react 是 jsx 语法,可以 html 和 js 混写;
2、状态管理 vue 是 data,react 是 state 但是不能直接修改 state,需要 setState 方法;
3、vue 使用 solt 插槽进行嵌套传递,react 使用 props.children 传递;
4、vue 使用指令渲染,react 逻辑运算符;
5、父子组件传值,vue 使用 props 和 $emit,react 使用 props 和子组件触发父组件的方法,父组件通过setState修改;
6、路由,vue 路由组件都会渲染到 router-view 里面,react 是全局组件的方式,子组件作为 children 传递到父组件;
7、vue 实现双向绑定,react 没有;
8、vue 父组件更新子组件不会动,react 父更新子必更新,需要手动设置;

10、computed 和 watch 区别,以及 computed 如何实现缓存和更新的

区别

1、都是观察数据变化的(相同);
2、计算属性将会混入到 vue 的实例中,所以需要监听自定义变量;watch 监听 data 、props 里面数据的变化;
3、computed 有缓存,它依赖的值变了才会重新计算,watch 没有;
4、watch 支持异步,computed 不支持;
5、watch 是一对多(监听某一个值变化,执行对应操作);computed 是多对一(监听属性依赖于其他属性)
6、watch 监听函数接收两个参数,第一个是最新值,第二个是输入之前的值;
7、computed 属性是函数时,都有 get 和 set 方法,默认走 get 方法,get 必须有返回值(return)

watch的参数:
deep:深度监听
immediate :组件加载立即触发回调函数执行

computed缓存原理:
conputed本质是一个惰性的观察者;当计算数据存在于 data 或者 props里时会被警告;

vue初次运行会对 computed 属性做初始化处理(initComputed),初始化的时候会对每一个 computed 属性用 watcher 包装起来 ,这里面会生成一个 dirty 属性值为 true;然后执行 defineComputed 函数来计算,计算之后会将 dirty 值变为 false,这里会根据 dirty 值来判断是否需要重新计算;如果属性依赖的数据发生变化,computed 的 watcher 会把 dirty 变为 true,这样就会重新计算 computed 属性的值。

11、hash 和 history 区别 ,分别是怎么实现的

区别:

1、 url 展示上,hash 有“#”,history 没有
2、hash 可以支持低版本浏览器和 IE8 ,history 只兼容到 IE10
3、history 模式需要后端配合将所有访问都指向 index.html,否则用户刷新页面,会导致404错误

原理:
hash:通过 onhashchange 事件监听 hash 变化,然后根据 hash 的变化更新页面部分内容(hash变化不会触发浏览器请求,但是会触发 hashchange 事件)。
history:主要是 H5 新增的两个 API(pushState、replaceState);他们可以改变url,但是不会发送请求,这样就可以通过 onpopstate 监听 url 变化来实现页面部分内容更新。

补充:pushState、replaceState 这两个方法应用于浏览器的历史记录栈,在当前已有的back、forward、go的基础上,他们提供了对当前浏览器进行修改的功能,只是当它们被修改时,虽然浏览器的URL发生

12、nextTick 作用和实现

作用:vue 更新 dom 是异步更新的,数据变化,dom 的更新不会马上完成;nextTick 的回调是在下次 DOM 更新循环结束之后执行的延迟回调。

实现:(源码:src/core/util/next-tick.js)
原理是使用异步方法处理任务,vue 会根据当前环境优先使用 promise.then、MutationObserver 、setImmediate,如果都不支持就使用 setTimeout 把函数延迟到 DOM 更新之后再使用。(原因是宏任务消耗大于微任务,优先使用微任务,最后使用消耗最大的宏任务)

promise:可以将函数延迟到当前函数调用栈最末端;
MutationObserver :是h5新加的一个功能,其功能是监听dom节点的变动,在所有dom变动完成后,执行回调函数;
setImmediate:用于中断长时间运行的操作,并在浏览器完成其他操作(如事件和显示更新)后立即运行回调函数;

13、solt 插槽种类以及原理

种类:
1)、匿名插槽:只能有一个
2)、实名插槽:可以有多个,在使用时必须使用name属性来标识

原理:

1、父组件先解析,把插槽当作子组件的子元素处理;
2、子组件解析,solt作为一个占位符,会被解析成一个函数;
3、函数传入参数执行,拿到第一步解析得到的插槽节点,并返回;

3)、作用域插槽:父组件获取子组 件solt 中携带的数据
为了让 user 在父级的插槽内容中可用,我们可以将 user 作为 元素的一个 attribute 绑定上去:

//子组件
<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  slot>
span>

绑定在 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:

//父组件
<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  template>
current-user>

我们选择将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。

原理:

1、父组件先解析,把插槽包装成一个函数保存给节点;
2、子组件解析,solt作为一个占位符,会被解析成一个函数;
3、函数传入参数执行,跟普通插槽一样。但是会多出来插槽传递出来的数据。

本题参考

14、keep-alive使用和原理

keep-alive 主要是组件缓存,采用的是LRU算法。
常用的两个属性 include(要缓存的)/ exclude(不要缓存的),允许组件有条件的进行缓存;都可以用逗号分隔字符串、正则表达式或一个数组来表示
因为keep-alive会将组件保存在内存中,并不会销毁以及重新创建,所以不会重新调用组件的created等方法,所以它自己有两个生命周期activated / deactivated,用来得知当前组件是否处于活跃状态。

缓存实现过程:

1、在 vue 实例创建的时候,created 钩子会创建一个 cache 对象,用来作为缓存容器,保存 vdom 节点。
2、destroyed钩子则在组件被销毁的时候清除cache缓存中的所有组件实例。
3、在mounted钩子的 render 函数中会先获取 keep-alive 里面组件的名称,然后根据 include 和 exclude 来匹配看看是否要缓存,如果不匹配直接直接返回 vdom ;否则会根据 key 在 this.cache 缓存器里面查找,如果存在则说明之前已经缓存了,直接返回缓存的 vdom 覆盖当前的 vdom ;否则就会将当前的 vdom 存储在 cache 中。
4、最后将该组件实例的 keepAlive 设置为 true。

15、MVVM 和 MVC

1)、MVVM 即 Model-View-ViewModel 的简写。即模型-视图-视图模型。模型(Model)指的是后端传递的数据。视图(View)指的是所看到的页面。视图模型(ViewModel)是 mvvm 模式的核心,它是连接 view 和 model 的桥梁。它有两个方向:一是将模型(Model)转化成视图(View),即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将视图(View)转化成模型(Model),即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。

2)、MVC 是 Model-View- Controller 的简写。即模型-视图-控制器。M 和 V 指的意思和 MVVM 中的 M 和 V 意思一样。C 即 Controller 指的是页面业务逻辑。使用 MVC 的目的就是将 M 和 V 的代码分离。MVC 是单向通信。也就是 View 跟 Model,必须通过 Controller 来承上启下。MVC和MVVM的区别并不是VM完全取代了C,只是在MVC的基础上增加了一层VM,只不过是弱化了C的概念,ViewModel 存在目的在于抽离 Controller 中展示的业务逻辑,而不是替代 Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用,使开发更高效,结构更清晰,增加代码的复用性。

可以参考阮一峰的文章很详细

16、template 模板引擎的渲染过程

template 的作用是模板占位符,可帮助我们包裹元素,但在循环过程当中,template不会被渲染到页面上

在 vue 实例初始化 $mount 的时候,先调用 render 函数如果 render 函数不存在,则调用 template 进行编译得到 render 函数。如果没有 template 则会调用 el 来获取 template。
 
渲染过程:

1、获取模板,并将模板通过 compile 编译器(通过正则等方式解析 template里面的指令,class、style等)编译成 AST 语法树;
2、把得到的 AST 通过 generate 转化成 render 渲染函数,render 函数的返回值是 vdom;
3、vue 构造函数直接使用 render 渲染函数渲染 dom;

17、SPA页面的理解

SPA (single-page application:单页面应用程序)在页面初始化的时候加载相应的 HTML、js、css,一旦加载完成,页面不会因为用户的操作进行页面的重新加载或者跳转,而是使用路由机制实现HTML内容转换,避免页面重新加载。

优点:

1、 用户体验好,内容修改不需要重新渲染页面,避免不必要的跳转和渲染;
2、SPA 对服务器的压力比较小;
3、前后端分离,架构清晰,前端负责页面,后端负责数据;

缺点:

1、首屏加载慢;
2、所有内容在同一个页面动态展示,不利于 SEO 优化;

18、vue 性能优化?

1)、去掉编译文件中的 map 文件
map文件主要是帮我们线上调试代码、查看样式的,通常线上不需要这些文件;找到 config/index.js 文件,将 productionSourceMap 设置为 false。

2)、开启 gzip 打包压缩 && 后台配合 gzip 访问
首先安装依赖 compression-webpack-plugin ,推荐使用 1.1.11 版本;找到 config/index.js 文件,将 productionGzip 设置为 true;

npm install --save-dev [email protected]

在nginx.conf 里面配置

http {  //在 http中配置如下代码,
   gzip on;
   gzip_disable "msie6"; 
   gzip_vary on; 
   gzip_proxied any;
   gzip_comp_level 8; #压缩级别
   gzip_buffers 16 8k;
   #gzip_http_version 1.1;
   gzip_min_length 100; #不压缩临界值
   gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript 		 application/x-httpd-php image/jpeg image/gif image/png;
 }

3)、路由懒加载
组件的延迟加载,可以把页面资源划分为多份,用到的时候才会按需加载,这样减少第一次加载的消耗。

4)、使用 CDN 引入第三方库减少服务器压力
首先、在index.html 文件中添加 CDN 相关的代码,以vue、vue-router、vuex 为例

  <body>
    <div id="app">div>
    <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js">script>
    <script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js">script>
    <script src="https://cdn.bootcss.com/vue-router/3.0.4/vue-router.min.js">script>
  body>

在 vue.config.js(vue-cli3) 文件 ,build/webpack.base.conf.js(vue-cli2)文件,增加 externals,将引入的外部模块导入

module.exports = {
entry: {
app: './src/main.js'
},
externals:{
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex':'Vuex'
}

最后、去掉 inport 以及 Vue.use(xxx)

//import Vue from 'vue'
//import Router from 'vue-router'
//import Vuex from 'vuex'
//Vue.use(Router)
///Vue.use(Vuex)

好处:加快打包速度,减轻服务器访问压力。

5)、尽量使用轻量级的工具库
moment是处理时间的标杆,但是它过于庞大,我们可以使用day.js替代

6)、对于短时间的大量操作(缩放、滚动)
使用防抖、节流函数

7)、代码精简,去除 console ,可复用的方法、组件提取出来

8)、不要写行内样式,避免dom重绘

9)、按需引入
比如我们只使用 elementUI 里面的部分组件,我们只引入需要用到的组件

10)、SPA页面采用keep-alive缓存组件

11)、key保证唯一性

12)、v-if 当值为false时内部指令不会执行,具有阻断功能,很多情况下使用v-if替代v-show

13、服务端渲染ssr

19、父子组件加载顺序

1)、加载过程:父beforeCreate->父cerated->父beforeMount->子beforeCreate->子cerated->子beforeMount>子mounted->父mounted
2)、子组件更新:父beforeUpdate->子beforeUpdate->子updated->父updated
3)、父组件更新:父beforeUpdate->父updated
4)、销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

20、Vue.use是做什么的,原理是什么

作用:用来注册使用插件或者组件的方法。
原理:

1、检测组件是否注册,避免重复注册;
2、处理入参,将第一个参数之后的参数归集,并在首部插入 this 上下文;
3、第一个参数是对象就执行对象里面的install方法,是方法则直接执行这个方法,然后缓存;
4、返回;

21、Proxy 与 Object.defineProperty 优劣对比

Proxy 的优势如下:
1)Proxy 可以直接监听对象而非属性;
2)Proxy 可以直接监听数组的变化;
3)Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
4)Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
5)Proxy 作为新标准将受到浏览器厂商重点持续的性能优化;

Object.defineProperty 的优势如下:
兼容性好,支持 IE9,而Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。

22、v-model 的原理

v-model本质就是一个语法糖,可以看成是value + input方法的语法糖,是 vue 的双向绑定指令。一方面 model 层通过 defineProperty 来劫持每个属性,一旦监听到变化通过相关的页面元素更新。另一方面通过编译模板文件,为控件的 v-model 绑定 input 事件,从而页面输入能实时更新相关 data 属性值。

可以通过model属性的prop和event属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性。

v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
1)text 和 textarea 元素使用 value 属性和 input 事件;
2)checkbox 和 radio 使用 checked 属性和 change 事件;
3)select 字段将 value 作为 prop 并将 change 作为事件。

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

1、v-if 是真正的条件渲染,直到条件第一次变为真时,才会开始渲染。
2、v-show 不管初始条件是什么都会渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。
3、v-show 则适用于需要非常频繁切换条件的场景;不频繁切换使用 v-if 更合理。

24、v-for 和 v-if 优先级

1、v-for 优先于 v-if 被解析
2、如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能
3、要避免出现这种情况,则在外层嵌套template,在这一层进行v-if判断,然后在内部进行v-for循环
4、如果条件出现在循环内部,可通过计算属性提前过滤掉那些不需要显示的项

25、Vue.mixin 使用场景和原理

Vue.mixin的作用就是抽离公共的业务逻辑,原理类似“对象的继承”,在使用 mixin 的组件中引入后,mixin 中的方法和属性也就并入到该组件中,可以直接使用;当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并,如果混入的数据和本身组件中的数据冲突,会采用“就近原则”以组件的数据为准。

//全局引用 main.js
import mixin from './mixin'
Vue.mixin(mixin)

//在vue文件中引用
import '../mixin'; // 引入mixin文件
export default {
   mixins: [mixin]
}

同名选项合并:

1、 数据对象(data)在内部会进行递归合并,在和组件的数据发生冲突时以组件数据优先。
2、同名钩子函数(created,mounted…)将混合为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
3、值为对象的选项(methods, components 和 directives)将被混合为同一个对象。两个对象键名冲突时,取组件对象的键值对。

26、Vue.set

起因:因为 ES5 的限制,vue 无法检测到对象属性的添加和删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。

用法:Vue.set(target, key, value)

向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性。(对象不可以是 Vue 实例)

执行流程:

1、判断在非生产环境,传入的 target 如果是undefined、null 或是原始类型(值是否有效),则直接抛出错误。
2、target 如果是个数组,并且 key 是索引的话,那么就取当前数组长度与 key 这两者的最大值作为数组的新长度,然后使用数组的 splice 方法将传入的索引 key 对应的 val 值添加进数组,进行重写数组。
3、如果传入的 target 不是数组,那就当作是对象来处理,vue实例直接退出,不是响应式数据则直接赋值然后返回;是响应式数据则将属性定义成响应式的 defineReactive(ob.value, key, val)
4、使用 ob.dep.notify() 触发更新视图

Vue.set( ) 是将 set 函数绑定在 Vue 构造函数上,this.$set() 是将 set 函数绑定在 Vue原型上。

27、组件中写 name 选项有哪些好处

1、可以通过名字找到对应的组件( 递归组件:组件自身调用自身 )
2、可以通过 name 属性实现缓存功能 (keep-alive)
3、可以通过 name 来识别组件(跨级组件通信时非常重要)
4、使用 vue-devtools 调试工具里显示的组见名称是由 vue 中组件 name 决定的

28、vue 组件通讯有哪些方法

1)、props / $emit 适用 父子组件通信(常规操作)

父组件通过 props 传值给子组件,子组件 $emit 把自己的消息发送给父组件

2)、ref 与 $parent / $children 适用 父子组件通信

ref:在 dom 元素上绑定 ref 属性,然后通过 $refs 获取该属性的所有 dom 元素和组件实例
$parent / $children:访问父 / 子实例(可以取值,不可修改)

3)、EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信

通过一个空的Vue实例作为中央事件总线,用它来触发事件和监听事件

4)、$ attrs/$listeners 适用于 隔代组件通信

$ attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=“$attrs” 传入内部组件。通常配合 interitAttrs 选项一起使用。
$ listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=“$listeners” 传入内部组件

简单来说:$ attrs与$ listeners 是两个对象,$ attrs 里存放的是父组件上绑定的非 Props 属性,$ listeners里存放的是父组件中绑定的非原生事件。

5)、provide / inject 适用于 隔代组件通信

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

6)、Vuex 适用于 父子、隔代、兄弟组件通信

29、vue 组件的data 为什么是一个函数

组件是可复用的,一个组件被创建好之后,就可能被用在各个地方,而组件不管被复用了多少次,组件中的 data 数据都应该是相互隔离,互不影响的,基于这一理念,组件每复用一次,data 数据就应该被复制一次,之后,当某一处复用的地方组件内 data 数据被改变时,其他复用地方组件的 data 数据不受影响.

组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果。(对象为引用类型,当重用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题)

30、data 里面数据量比较大如何优化

vue 把 data、props、store 等数据做成响应式,也就是会对这些响应式数据做深度监听,给每一个object类型的key(包括嵌套object)添加observer(vue3使用proxy)。所以如果我们不需要数据是响应式的,可以在.vue 文件头部直接使用 let、const 定义变量,在组件销毁的时候将该这些变量设为null。

31、子组件里面可以修改父组件的值吗

答案是传递的是对象和数组可以修改,如果是基础数据类型也可以修改,但是控制台会报错;对象和数组修改之后父组件是可以监听到这个值的变化的。那么为什么呢?

对象和数组都是引用类型,父组件传递过来的是一个地址,子组件修改的是地址里面的内容,地址本身并没有变,所以不会报错,但是基础数据类型就不同了,他是直接修改了传递的值,但是 vue 不允许在子组件里面直接修改prop值,所以就会报错。

那么基础类型要怎么修改呢?
1)、可以使用.sync 修饰符来修改。参考:vue 父子组件双向绑定传值的实现
2)、子组件用 data 或者 computed 重新定义一个变量,然后修改新定义的变量。

32、生命周期钩子是如何实现的

Vue的生命周期钩子就是回调函数而已,当创建组件实例的过程中会调用对应的钩子方法。

内部主要是使用callHook方法来调用对应的方法。核心是一个发布订阅模式,将钩子订阅好(内部采用数组的方式存储),在对应的阶段进行发布。

33、什么是 vue-loader

作用:解析和转换.vue文件。提取出其中的逻辑代码 script,样式代码style,以及HTML 模板template,再分别把他们交给对应的loader去处理

用途:js可以写es6,style样式可以写scss或less

特性:

1、允许为 Vue 组件的每个部分使用其它的 webpack loader,例如在

css-loader:加载由vue-loader提取出的CSS代码
vue-template-compiler:把vue-loader提取出的HTML模板编译成可执行的javascript代码

34、vue 是怎么检测数组的变化的

1、使用函数劫持的方式,重写了数组的方法(push、pop、shift、unshift、sort、reverse、splice)
2、Vue 将 data 中的数组,进行了原型链重写。指向了自己定义的数组原型方法,这样当调用数组api时,可以通知依赖更新.如果数组中包含着引用类型。会对数组中的引用类型再次进行监控。

流程:

1、初始化传入 data 数据执行 initData
2、将数据进行观测 new Observer
3、将数组原型方法指向重写的原型
4、深度观察数组中的引用类型

35、vue 是怎样依赖收集的?(dep 和 Watcher 是什么关系)

1、 每一个属性都有自己的 dep 属性,来存放依赖的 Watcher,属性发生变化后会通知 Watcher 去更新。
2、在用户获取(getter) 数据时 Vue 给每一个属性都添加了 dep 属性来收集 Watcher。在用户 setting 设置属性值时 dep.notify() 通知 收集的Watcher 重新渲染。
3、Dep依赖收集类和 Watcher类 是多对多双向存储的关系。
4、每一个属性都可以有多个 Watcher 类,因为属性可能在不同的组件中被使用。
5、同时一个 Watcher 类 也可以对应多个属性。

注意:Dep 是一个用来负责收集 Watcher 的类,Watcher 是一个封装了渲染视图逻辑的类,用于派发更新的。Watcher 是不能直接更新视图的还需要结合 vdom 经过 patch() 中的 diff 算法才可以生成真正的 DOM

36、vue路由的钩子函数(导航守卫)

全局的路由钩子函数:beforeEach、afterEach(一般用于全局进行权限跳转)
单个的路由钩子函数:beforeEnter、beforeLeave(路由内部钩子,一般在路由表里)
组件内的路由钩子函数:beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate

beforeEach:每一次路由该变的之后页面加载之前执行,三个参数(to 将要进入的路由对象、from 即将离开的路由对象、next 跳转方法),next 必须调用
afterEach:每一次路由该变的之后页面加载之后执行;两个参数(to 将要进入的路由对象、from 即将离开的路由对象)
beforeEnter:进入指定路由跳转时需要执行的逻辑
beforeLeave:离开指定路由跳转时需要执行的逻辑
beforeRouteEnter、beforeRouteLeave、 beforeRouteUpdate都是写在组件里面,也有三个参数(to、from、next)

37、vue $route 和 $router 的区别

1、$route 是“路由信息对象”,包括 path , params , hash , query ,fullPath , matched , name 等路由信息参数。
2、⽽ $router 是“路由实例”对象包括了路由的跳转⽅法,钩⼦函数等

38、vue 中的 diff 算法原理

diff 算法的原理是递归加上遍历节点来比较节点复用性。diff 算法通过对比虚拟树的差异,将差异通过 patch (补丁)一边比较一边打补丁方式更新到真实 dom 节点上。patch 函数是 diff 流程的入口函数。在采取diff算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。

在数据发生变化的时候,set 方法就会调用 dep.notify 通知所有订阅者,订阅者会调用 patch 给真实的 dom 打补丁,更新相应的视图。

1、patch 函数接收两个参数 oldVnode 和 Vnode 分别代表旧节点和新节点,对比两个节点是否相同,不相同则创建新节点插入到旧节点之前,同时删除旧节点;相同则对节点打补丁。
2、判断节点相同是根据 sameVnode 方法,当 tag key inputType 完全相同时,我们认定节点可复用。
3、打补丁调用了 patchVnode 函数,先拿到真实 dom;判断 oldVnode 和 Vnode 是否指向同一个对象;是则直接 return,否则如果都有文本节点并且不相等,则把新节点的文本更新给真实 dom。
4、如果 oldVnode 有子节点而 Vnode 没有,则删除真实 dom的子节点。
5、如果 oldVnode 没有子节点而 Vnode 有,则将 Vnode 的子节点真实化之后添加到真实 dom。
6、 如果两者都有子节点,则执行 updateChildren 函数比较子节点。
7、首先将 oldVnode 和 Vnode 的子节点提取出来;
8、这两个子节点都有两个变量 StartIdx 和 EndIdx,通过这两个变量相互比较(开始与开始、结束与结束、开始与结束、结束与开始)相同则复用,同时移动指(节点)。
9、不同则遍历老节点,找到与 newStartIdx 对应元素的 key 一样的节点;把当前位置设置为 undefined,然后把找到的元素移到老节点对应位置,所有移动完成之后会将超出新节点长度的元素删除掉。

更详细内容参考

39、Vue. extend 能做什么

作⽤是扩展组件⽣成⼀个构造器,通常会与 $mount ⼀起使⽤。

<div id="mount-point">div>
// 创建构造器
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')

这样div里面就有了下面内容

<p>Walter White aka Heisenbergp>

40、vue 的 mixin 和 mixins 区别

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

41、vue 组件渲染和更新的过程

渲染:

1、把模板解析为render函数
2、触发响应式,监听data属性getter setter
3、执行render函数,生成vnode,patch(elem,vnode)

更新:

1、修改data,触发setter(此前在getter中已被监听)
2、重新执行render函数,生成newVnode
3、patch(vnode,newVnode)

42、vue 为什么采用异步渲染

因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染;所以为了性能考虑,Vue会在本轮数据更新后,再去异步更新视图。

原理:

1、调用 notify() 方法,通知watcher 进行更新操作
2、依次调用watcher 的 update 方法
3、对watcher 进行去重操作(通过id),放到队列里
4、执行完后异步清空这个队列, nextTick(flushSchedulerQueue) 进行批量更新操作

43、vue 为什么要使用异步组件

组件功能多打包出的结果会变大,我可以采用异步的方式来加载组件。
异步组件的核心是把组件定义成一个函数,主要依赖import()这个语法,可以实现文件的分割加载。

原理:在createComponent方法中,会有相应的异步组件处理

1、首先定义一个asyncFactory变量,然后进行判断,如果组件是一个函数,然后会去调resolveAsyncComponent方法;
2、然后将赋值在asyncFactory上的函数传进去,会让asyncFactory马上执行,执行的时候并不会马上返回结果,因为他是异步的,返回的是一个promise,这时候这个值就是undefined,然后就会先渲染一个异步组件的占位,空虚拟节点。
3、如果加载完之后会调factory函数传入resolve和reject两个参数,执行后返回一个成功的回调和失败的回调,promise成功了就会调resolve;
4、resolve中就会调取forceRender方法强制更新视图重新渲染,forceRender中调取的就是$forceUpdate,同时把结果放factory.resolved上;
5、如果强制刷新的时候就会再次走resolveAsyncComponent方法,这时候有个判断,如果有成功的结果就把结果直接放回去,这时候resolveAsyncComponent返回的就不是undefined了,就会接的创建组件,初始化组件,渲染组件。

44、vue 如何快速定位那个组件出现性能问题的

⽤ timeline ⼯具。 通过 timeline 来查看每个函数的调⽤时常,定位出哪个函数的问题,从⽽能判断哪个组件出了问题

45、vue3.x 对比 vue2.x 变化

1、源码组织方式变化:使用 TS 重写
2、支持 Composition API:基于函数的API,更加灵活组织组件逻辑(vue2用的是options api)
3、响应式系统提升:Vue3中响应式数据原理改成proxy,可监听动态新增删除属性,以及数组变化
4、编译优化:vue2通过标记静态根节点优化diff,Vue3 标记和提升所有静态根节点,diff的时候只需要对比动态节点内容
5、打包体积优化:移除了一些不常用的api(inline-template、filter)
6、生命周期的变化:使用setup代替了之前的beforeCreate和created
7、Vue3的template模板支持多个根标签
8、Vuex状态管理:创建实例的方式改变,Vue2为new Store , Vue3为createStore
9、Route获取页面实例与路由信息:vue2通过this获取router实例,vue3通过使用 getCurrentInstance/ userRoute和userRouter方法获取当前组件实例
10、支柱Props的使用变化:vue2通过this获取props里面的内容,vue3直接通过 props
11、父子组件传值:vue3在向父组件传回数据时,如使用的自定义名称,如backData,则需要在emits中定义一下

https://blog.csdn.net/r657225738/article/details/115551368

46、watch 的 deep 如何实现的

watch 在监听对象的时候,如果 deep 值是 true,就会执行 traverse 这个方法,这个方法里就是做了个数组递归,如果是数组的话,会根据数组的每一项索引取值,进行递归追加依赖,如果是对象会拿 key 进行遍历取值,进行递归追加依赖,traverse 就是deep:true实现的核心。这样就会把数组或者对象的每一个属性都追加依赖进行监听,只要依赖发生变化就会通知视图更新。

47、v-html 会导致那些问题

1、可能会导致xss攻击,一定要保证你的内容是可以依赖的
2、v-html会替换掉标签内部的子元素
3、单页面文件里,scoped 样式不会作用在 v-html 内部,因为这部分内容没有被 模板编译器处理
4、v-html更新的是元素的 innerHTML 。内容按普通 HTML 插入, 不会作为 Vue 模板进行编译
5、包含的 js 不会执行,因为浏览器渲染的时候并不会渲染 js,这时要在$nextTick中动态创建script标签并插入

48、v-el作用

提供一个在页面上已存在的 DOM元素作为 Vue实例的挂载目标.可以是 CSS 选择器,也可以是一个 HTMLElement 实例,

48、说说vue的动态组件

动态组件就是几个组件放在一个挂载点下,然后根据父组件的某个变量来决定显示哪个,或者都不显示。
在挂载点使用 component 标签,然后使用 is =“组件名”,它会自动去找匹配的组件名,如果有,则显示;

<div id="app">
    <component is="one">component>
div>

new Vue({
    el: '#app',
    components: {
        one: {template: '<div>我是线路一div>'},
        two: {template: '<div>我是线路二div>'},
        thr: {template: '<div>我是线路三div>'}
    }
})

49、怎么定义vue-router的动态路由?怎么获取传过来的值?

可以有两种方式传递参数:params、query
1)、params:在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 router 对象的 params.id 获取。

//index.js
path:'/user/:id',
compunent: user
// 方法1:
<router-link :to="{ name: 'users', params: { id: 123}}">按钮router-link>
// 方法2:
this.$router.push({name:'users',params:{id:123}})
// 方法3:
this.$router.push('/user/' + 123)

2)、query:不需要配置路由格式,使用 router 对象的 query.id 获取。

// 方法1:
<router-link :to="{ name: 'users', query: { id: 123}}">按钮router-link>
// 方法2:
this.$router.push({ name: 'users', query:{ id:123}})
// 方法3:
<router-link :to="{ path: '/user', query: {  id:123 }}">按钮router-link>
// 方法4:
this.$router.push({ path: '/user', query:{  id:123 }})
// 方法5:
this.$router.push('/user?uname=' + 123)

50、自定义指令

自定义指令的生命周期,有5个事件钩子,可以设置指令在某一个事件发生时的具体行为:

bind: 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新(详细的钩子函数参数见下)。
componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
unbind: 只调用一次, 指令与元素解绑时调用。

钩子函数的参数 (包括 el,binding,vnode,oldVnode)

el: 指令所绑定的元素,可以用来直接操作 DOM 。
binding: 一个对象,包含以下属性:name: 指令名、value: 指令的绑定值、oldValue: 指令绑定的前一个值、expression: 绑定值的字符串形式、arg: 传给指令的参数、modifiers: 一个包含修饰符的对象。
vnode: Vue 编译生成的虚拟节点。
oldVnode: 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

51、自定义过滤器

参考:vue 全局注册过滤器 filter

52、vue 中 extend、mixins、extends的区别

1)、extend:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件对象。

2)、mixins:可以混入多个,当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

var vm = new Vue({
  created: function () { console.log(3) },
  mixins: [mixinA ,mixinB ]
})

3)、extends:声明扩展另一个组件(可以是一个简单的选项对象或构造函数)无需使用 Vue.extend。这主要是为了便于扩展单文件组件。和 mixins 类似。但extends只能继承一个。

var otherComp= { ... }
// 继承 otherComp
var comp = {
  extends: otherComp,
  ...
}

总结:

1、都是为了拓展组件的功能,优先级extend>extends>mixins
2、extend用于创建vue实例,是扩展组件的构造器
3、mixins可以混入多个mixin,extends只能继承一个

以上是我总结的 VUE 相关的知识点,还会继续更新,如有错误或者遗漏的地方欢迎指正!!!

你可能感兴趣的:(vue,vue,面试题,2021,响应式,原理)