阿里前端常考vue面试题汇总_阿里高级vue面试题

改变

![](https://img-blog.csdnimg.cn/img_convert/b736620bcd29f08f3685022ab5583d8b.webp?x-oss-process=image/format,png)


你会发现, **只有改变的栏目才闪烁,也就是进行重绘** ,数据没有改变的栏目还是保持原样,这样就大大节省了浏览器重新渲染的开销



> 
> vue中使用`h函数`生成虚拟`DOM`返回
> 
> 
> 



const vm = new Vue({
el: ‘#app’,
data: {
user: {name:‘poetry’}
},
render(h){
// h()
// h(App)
// h(‘div’,[])
let vnode = h(‘div’,{},‘hello world’);
return vnode
}
});


#### 怎么缓存当前的组件?缓存后怎么更新


缓存组件使用`keep-alive`组件,这是一个非常常见且有用的优化手段,`vue3`中`keep-alive`有比较大的更新,能说的点比较多


**思路**


* 缓存用`keep-alive`,它的作用与用法
* 使用细节,例如缓存指定/排除、结合`router`和`transition`
* 组件缓存后更新可以利用`activated`或者`beforeRouteEnter`
* 原理阐述


**回答范例**


1. 开发中缓存组件使用`keep-alive`组件,`keep-alive`是`vue`内置组件,`keep-alive`包裹动态组件`component`时,会缓存不活动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,防止重复渲染`DOM`




1. 结合属性`include`和`exclude`可以明确指定缓存哪些组件或排除缓存指定组件。`vue3`中结合`vue-router`时变化较大,之前是`keep-alive`包裹`router-view`,现在需要反过来用`router-view`包裹`keep-alive`




1. 缓存后如果要获取数据,解决方案可以有以下两种


* `beforeRouteEnter`:在有`vue-router的`项目,每次进入路由的时候,都会执行`beforeRouteEnter`



beforeRouteEnter(to, from, next){
next(vm=>{
console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据
})
},


* `actived`:在`keep-alive`缓存的组件被激活的时候,都会执行`actived`钩子



activated(){
this.getData() // 获取数据
},


1. `keep-alive`是一个通用组件,它内部定义了一个`map`,缓存创建过的组件实例,它返回的渲染函数内部会查找内嵌的`component`组件对应组件的`vnode`,如果该组件在`map`中存在就直接返回它。由于`component`的`is`属性是个响应式数据,因此只要它变化,`keep-alive`的`render`函数就会重新执行


#### vue-router 动态路由是什么



> 
> 我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 `User` 组件,对于所有 `ID` 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 `vue-router` 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果
> 
> 
> 



const User = {
template: “

User
”,
};

const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: “/user/:id”, component: User },
],
});


问题: `vue-router` 组件复用导致路由参数失效怎么办?


解决方法:


1. 通过 `watch` 监听路由参数再发请求



watch: { //通过watch来监听路由变化
"KaTeX parse error: Expected '}', got 'EOF' at end of input: …s.getData(this.route.params.xxx);
}
}


1. 用 `:key` 来阻止“复用”




回答范例


1. 很多时候,我们需要将给定匹配模式的路由映射到同一个组件,这种情况就需要定义动态路由
2. 例如,我们可能有一个 `User` 组件,它应该对所有用户进行渲染,但用户 `ID` 不同。在 `Vue Router`中,我们可以在路径中使用一个动态字段来实现,例如:`{ path: '/users/:id', component: User }`,其中`:id`就是路径参数
3. 路径参数 用冒号 `:` 表示。当一个路由被匹配时,它的 `params` 的值将在每个组件中以 `this.$route.params` 的形式暴露出来。
4. 参数还可以有多个,例如/`users/:username/posts/:postId`;除了 `$route.params` 之外,`$route` 对象还公开了其他有用的信息,如 `$route.query`、`$route.hash` 等


 


#### watch 原理


`watch` 本质上是为每个监听属性 `setter` 创建了一个 `watcher`,当被监听的属性更新时,调用传入的回调函数。常见的配置选项有 `deep` 和 `immediate`,对应原理如下


* `deep`:深度监听对象,为对象的每一个属性创建一个 `watcher`,从而确保对象的每一个属性更新时都会触发传入的回调函数。主要原因在于对象属于引用类型,单个属性的更新并不会触发对象 `setter`,因此引入 `deep` 能够很好地解决监听对象的问题。同时也会引入判断机制,确保在多个属性更新时回调函数仅触发一次,避免性能浪费。
* `immediate`:在初始化时直接调用回调函数,可以通过在 `created` 阶段手动调用回调函数实现相同的效果


#### 组件通信


组件通信的方式如下:


#### (1) props / $emit


父组件通过`props`向子组件传递数据,子组件通过`$emit`和父组件通信


###### 1. 父组件向子组件传值


* `props`只能是父组件向子组件进行传值,`props`使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。
* `props` 可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。
* `props`属性名规则:若在`props`中使用驼峰形式,模板中需要使用短横线的形式



// 父组件



// 子组件

{{ msg }}


###### 2. 子组件向父组件传值


* `$emit`绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过`v-on`监听并接收参数。



// 父组件



//子组件


#### (2)eventBus事件总线(`$emit / $on`)


`eventBus`事件总线适用于**父子组件**、**非父子组件**等之间的通信,使用步骤如下: **(1)创建事件中心管理组件之间的通信**



// event-bus.js

import Vue from ‘vue’
export const EventBus = new Vue()


**(2)发送事件** 假设有两个兄弟组件`firstCom`和`secondCom`:




在`firstCom`组件中发送事件:




**(3)接收事件** 在`secondCom`组件中发送事件:



求和: {{ count }}

在上述代码中,这就相当于将`num`值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不用组件通过它来通信。


虽然看起来比较简单,但是这种方法也有不变之处,如果项目过大,使用这种方式进行通信,后期维护起来会很困难。


#### (3)依赖注入(provide / inject)


这种方式就是Vue中的**依赖注入**,该方法用于**父子组件之间的通信**。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。


`provide / inject`是Vue提供的两个钩子,和`data`、`methods`是同级的。并且`provide`的书写形式和`data`一样。


* `provide` 钩子用来发送数据或方法
* `inject`钩子用来接收数据或方法


在父组件中:



provide() {
return {
num: this.num
};
}


在子组件中:



inject: [‘num’]


还可以这样写,这样写就可以访问父组件中的所有属性:



provide() {
return {
app: this
};
}
data() {
return {
num: 1
};
}

inject: [‘app’]
console.log(this.app.num)


**注意:** 依赖注入所提供的属性是**非响应式**的。


#### (3)ref / $refs


这种方式也是实现**父子组件**之间的通信。


`ref`: 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。


在子组件中:



export default {
data () {
return {
name: ‘JavaScript’
}
},
methods: {
sayHello () {
console.log(‘hello’)
}
}
}


在父组件中:




#### (4)`$parent / $children`


* 使用`$parent`可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)
* 使用`$children`可以让组件访问子组件的实例,但是,`$children`并不能保证顺序,并且访问的数据也不是响应式的。


在子组件中:



{{ message }}

获取父组件的值为: {{ parentVal }}


在父组件中:



// 父组件中

{{ msg }}

在上面的代码中,子组件获取到了父组件的`parentVal`值,父组件改变了子组件中`message`的值。 **需要注意:**


* 通过`$parent`访问到的是上一级父组件的实例,可以使用`$root`来访问根组件的实例
* 在组件中使用`$children`拿到的是所有的子组件的实例,它是一个数组,并且是无序的
* 在根组件`#app`上拿`$parent`得到的是`new Vue()`的实例,在这实例上再拿`$parent`得到的是`undefined`,而在最底层的子组件拿`$children`是个空数组
* `$children` 的值是**数组**,而`$parent`是个**对象**


#### (5)`$attrs / $listeners`


考虑一种场景,如果A是B组件的父组件,B是C组件的父组件。如果想要组件A给组件C传递数据,这种隔代的数据,该使用哪种方式呢?


如果是用`props/$emit`来一级一级的传递,确实可以完成,但是比较复杂;如果使用事件总线,在多人开发或者项目较大的时候,维护起来很麻烦;如果使用Vuex,的确也可以,但是如果仅仅是传递数据,那可能就有点浪费了。


针对上述情况,Vue引入了`$attrs / $listeners`,实现组件之间的跨代通信。


先来看一下`inheritAttrs`,它的默认值true,继承所有的父组件属性除`props`之外的所有属性;`inheritAttrs:false` 只继承class属性 。


* `$attrs`:继承所有的父组件属性(除了prop传递的属性、class 和 style ),一般用在子组件的子元素上
* `$listeners`:该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合 `v-on="$listeners"` 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)


A组件(`APP.vue`):



//此处监听了两个事件,可以在B组件或者C组件中直接触发

B组件(`Child1.vue`):



props: {{ pChild1 }}

$attrs: {{ $attrs }}


C 组件 (`Child2.vue`):



props: {{ pChild2 }}

$attrs: {{ $attrs }}


在上述代码中:


* C组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了`$listeners` 属性
* 在B组件中通过v-bind 绑定`$attrs`属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的)


#### (6)总结


**(1)父子组件间通信**


* 子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过 emit 触发事件来向父组件发送数据。
* 通过 ref 属性给子组件设置一个名字。父组件通过 `$refs` 组件名来获得子组件,子组件通过 `$parent` 获得父组件,这样也可以实现通信。
* 使用 provide/inject,在父组件中通过 provide提供变量,在子组件中通过 inject 来将变量注入到组件中。不论子组件有多深,只要调用了 inject 那么就可以注入 provide中的数据。


**(2)兄弟组件间通信**


* 使用 eventBus 的方法,它的本质是通过创建一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递。
* 通过 `$parent/$refs` 来获取到兄弟组件,也可以进行通信。


**(3)任意组件之间**


* 使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。


如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候采用上面这一些方法可能不利于项目的维护。这个时候可以使用 vuex ,vuex 的思想就是将这一些公共的数据抽离出来,将它作为一个全局的变量来管理,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。


#### 组件中写name属性的好处



> 
> 可以标识组件的具体名称方便调试和查找对应属性
> 
> 
> 



// 源码位置 src/core/global-api/extend.js

// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub // 记录自己 在组件中递归自己 -> jsx
}


#### vue3.0 特性你有什么了解的吗?


Vue 3.0 正走在发布的路上,Vue 3.0 的目标是让 Vue 核心变得更小、更快、更强大,因此 Vue 3.0 增加以下这些新特性:


**(1)监测机制的改变**


3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。这消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:


* 只能监测属性,不能监测对象
* 检测属性的添加和删除;
* 检测数组索引和长度的变更;
* 支持 Map、Set、WeakMap 和 WeakSet。


新的 observer 还提供了以下特性:


* 用于创建 observable 的公开 API。这为中小规模场景提供了简单轻量级的跨组件状态管理解决方案。
* 默认采用惰性观察。在 2.x 中,不管反应式数据有多大,都会在启动时被观察到。如果你的数据集很大,这可能会在应用启动时带来明显的开销。在 3.x 中,只观察用于渲染应用程序最初可见部分的数据。
* 更精确的变更通知。在 2.x 中,通过 Vue.set 强制添加新属性将导致依赖于该对象的 watcher 收到变更通知。在 3.x 中,只有依赖于特定属性的 watcher 才会收到通知。
* 不可变的 observable:我们可以创建值的“不可变”版本(即使是嵌套属性),除非系统在内部暂时将其“解禁”。这个机制可用于冻结 prop 传递或 Vuex 状态树以外的变化。
* 更好的调试功能:我们可以使用新的 renderTracked 和 renderTriggered 钩子精确地跟踪组件在什么时候以及为什么重新渲染。


**(2)模板**


模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。


同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。


**(3)对象式的组件声明方式**


vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易。


此外,vue 的源码也改用了 TypeScript 来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理。现在 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外暴露的 api 更容易结合 TypeScript。静态类型系统对于复杂代码的维护确实很有必要。


**(4)其它方面的更改**


vue3.0 的改变是全面的,上面只涉及到主要的 3 个方面,还有一些其他的更改:


* 支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。
* 支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。
* 基于 treeshaking 优化,提供了更多的内置功能。


#### v-show 与 v-if 有什么区别?


**v-if** 是**真正**的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是**惰性的**:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。


**v-show** 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。


所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。


### 什么是作用域插槽


**插槽**


* 创建组件虚拟节点时,会将组件儿子的虚拟节点保存起来。当初始化组件时,通过插槽属性将儿子进行分类`{a:[vnode],b[vnode]}`
* 渲染组件时会拿对应的 `slot` 属性的节点进行替换操作。(插槽的作用域为父组件)



xxxx
xxxx

slot name=“a”
slot name=“b”


**作用域插槽**


* 作用域插槽在解析的时候不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此函数进行渲染。(插槽的作用域为子组件)
* 普通插槽渲染的作用域是父组件,作用域插槽的渲染作用域是当前子组件。



![](https://img-blog.csdnimg.cn/img_convert/88bd2b17078e178b1fd34016a78b0ed9.webp?x-oss-process=image/format,png)


// 插槽

const VueTemplateCompiler = require(‘vue-template-compiler’);
let ele = VueTemplateCompiler.compile( <my-component> <div slot="header">node</div> <div>react</div> <div slot="footer">vue</div> </my-component>
)

// with(this) {
// return _c(‘my-component’, [_c(‘div’, {
// attrs: { “slot”: “header” },
// slot: “header”
// }, [_v(“node”)] // _文本及诶点 )
// , _v(" “),
// _c(‘div’, [_v(“react”)]), _v(” "), _c(‘div’, {
// attrs: { “slot”: “footer” },
// slot: “footer” }, [_v(“vue”)])])
// }

const VueTemplateCompiler = require(‘vue-template-compiler’);
let ele = VueTemplateCompiler.compile( <div> <slot name="header"></slot> <slot name="footer"></slot> <slot></slot> </div>
);

with(this) {
return _c(‘div’, [_v(“node”), _v(" “), _t(_v(“vue”)])]), _v(” "), _t(“default”)], 2)
}
// _t定义在 core/instance/render-helpers/index.js



// 作用域插槽:
let ele = VueTemplateCompiler.compile(<app> <div slot-scope="msg" slot="footer">{{msg.a}}</div> </app>
);

// with(this) {
// return _c(‘app’, { scopedSlots: _u([{
// // 作用域插槽的内容会被渲染成一个函数
// key: “footer”,
// fn: function (msg) {
// return _c(‘div’, {}, [_v(_s(msg.a))]) } }])
// })
// }
// }

const VueTemplateCompiler = require(‘vue-template-compiler’);
VueTemplateCompiler.compile(<div><slot name="footer" a="1" b="2"></slot> </div>);

// with(this) { return _c(‘div’, [_t(“footer”, null, { “a”: “1”, “b”: “2” })], 2) }


#### 什么是 MVVM?


Model–View–ViewModel (MVVM) 是一个软件架构设计模式,由微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程方式。由 John Gossman(同样也是 WPF 和 Silverlight 的架构师)于2005年在他的博客上发表


MVVM 源自于经典的 Model–View–Controller(MVC)模式 ,MVVM 的出现促进了前端开发与后端业务逻辑的分离,极大地提高了前端开发效率,MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用


(1)View 层


View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。


(2)Model 层


Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。


(3)ViewModel 层


ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。


MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展现在 View 层,前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新。这样 View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。


我们以下通过一个 Vue 实例来说明 MVVM 的具体实现,有 Vue 开发经验的同学应该一目了然:


(1)View 层



{{message}}

Click me

(2)ViewModel 层



var app = new Vue({
el: ‘#app’,
data: { // 用于描述视图状态
message: ‘Hello Vue!’,
},
methods: { // 用于描述视图行为
showMessage(){
let vm = this;
alert(vm.message);
}
},
created(){
let vm = this;
// Ajax 获取 Model 层的数据
ajax({
url: ‘/your/server/data/api’,
success(res){
vm.message = res;
}
});
}
})


(3) Model 层



{
“url”: “/your/server/data/api”,
“res”: {
“success”: true,
“name”: “IoveC”,
“domain”: “www.cnblogs.com”
}
}


#### 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 的事件,如下所示:



父组件:

子组件:

{{value}}

props:{
value: String
},
methods: {
test1(){
this.$emit(‘input’, ‘小红’)
},
},


### v-if和v-for哪个优先级更高


* 实践中不应该把`v-for`和`v-if`放一起
* 在`vue2`中,`v-for`的优先级是高于`v-if`,把它们放在一起,输出的渲染函数中可以看出会先执行循环再判断条件,哪怕我们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比较浪费;另外需要注意的是在`vue3`中则完全相反,`v-if`的优先级高于`v-for`,所以`v-if`执行时,它调用的变量还不存在,就会导致异常
* 通常有两种情况下导致我们这样做:
	+ 为了过滤列表中的项目 (比如 `v-for="user in users" v-if="user.isActive"`)。此时定义一个计算属性 (比如 `activeUsers`),让其返回过滤后的列表即可(比如`users.filter(u=>u.isActive)`)
	+ 为了避免渲染本应该被隐藏的列表 (比如 `v-for="user in users" v-if="shouldShowUsers"`)。此时把 `v-if` 移动至容器元素上 (比如 `ul`、`ol`)或者外面包一层`template`即可
* 文档中明确指出永远不要把 `v-if` 和 `v-for` 同时用在同一个元素上,显然这是一个重要的注意事项
* 源码里面关于代码生成的部分,能够清晰的看到是先处理`v-if`还是`v-for`,顺序上`vue2`和`vue3`正好相反,因此产生了一些症状的不同,但是不管怎样都是不能把它们写在一起的


**vue2.x源码分析**



> 
> 在vue模板编译的时候,会将指令系统转化成可执行的`render`函数
> 
> 
> 


编写一个`p`标签,同时使用`v-if`与 `v-for`



{{ item.title }}


创建`vue`实例,存放`isShow`与`items`数据



const app = new Vue({
el: “#app”,
data() {
return {
items: [
{ title: “foo” },
{ title: “baz” }]
}
},
computed: {
isShow() {
return this.items && this.items.length > 0
}
}
})


模板指令的代码都会生成在`render`函数中,通过`app.$options.render`就能得到渲染函数



ƒ anonymous() {
with (this) { return
_c(‘div’, { attrs: { “id”: “app” } },
_l((items), function (item)
{ return (isShow) ? _c(‘p’, [_v(“\n” + _s(item.title) + “\n”)]) : _e() }), 0) }
}


* `_l`是`vue`的列表渲染函数,函数内部都会进行一次`if`判断
* 初步得到结论:`v-for`优先级是比`v-i`f高
* 再将`v-for`与`v-if`置于不同标签



{{item.title}}


再输出下`render`函数



ƒ anonymous() {
with(this){return
_c(‘div’,{attrs:{“id”:“app”}},
[(isShow)?[_v(“\n”),
_l((items),function(item){return _c(‘p’,[_v(_s(item.title))])})]:_e()],2)}
}


这时候我们可以看到,`v-for`与`v-if`作用在不同标签时候,是先进行判断,再进行列表的渲染


我们再在查看下vue源码


源码位置:`\vue-dev\src\compiler\codegen\index.js`



export function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === ‘template’ && !el.slotTarget && !state.pre) {
return genChildren(el, state) || ‘void 0’
} else if (el.tag === ‘slot’) {
return genSlot(el, state)
} else {
// component or element

}


在进行`if`判断的时候,`v-for`是比`v-if`先进行判断


最终结论:`v-for`优先级比`v-if`高


#### 虚拟DOM的优劣如何?


优点:


* 保证性能下限: 虚拟DOM可以经过diff找出最小差异,然后批量进行patch,这种操作虽然比不上手动优化,但是比起粗暴的DOM操作性能要好很多,因此虚拟DOM可以保证性能下限
* 无需手动操作DOM: 虚拟DOM的diff和patch都是在一次更新中自动进行的,我们无需手动操作DOM,极大提高开发效率
* 跨平台: 虚拟DOM本质上是JavaScript对象,而DOM与平台强相关,相比之下虚拟DOM可以进行更方便地跨平台操作,例如服务器渲染、移动端开发等等


缺点:


* 无法进行极致优化: 在一些性能要求极高的应用中虚拟DOM无法进行针对性的极致优化,比如VScode采用直接手动操作DOM的方式进行极端的性能优化


### 为什么Vue采用异步渲染



> 
> Vue 是组件级更新,如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以为了性能, Vue 会在本轮数据更新后,在异步更新视图。核心思想 `nextTick`
> 
> 
> 



![](https://img-blog.csdnimg.cn/img_convert/cb2e591f3389af565cd9db0fbe9c112a.webp?x-oss-process=image/format,png)

源码相关



> 
> `dep.notify()` 通知 `watcher`进行更新, `subs[i].update` 依次调用 `watcher` 的 `update` , `queueWatcher` 将`watcher` 去重放入队列, `nextTick`( `flushSchedulerQueue` )在下一`tick`中刷新`watcher`队列(异步)
> 
> 
> 



update () { /* istanbul ignore else */
if (this.lazy) {
this.dirty = true
}
else if (this.sync) {
this.run()
}
else {
queueWatcher(this); // 当数据发生变化时会将watcher放到一个队列中批量更新
}
}

export function queueWatcher (watcher: Watcher) {
const id = watcher.id // 会对相同的watcher进行过滤
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i–
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== ‘production’ && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue) // 调用nextTick方法 批量的进行更新
}
}
}


#### 你有使用过vuex的module吗?



const moduleA = {
state: () => ({ … }),
mutations: { … },
actions: { … },
getters: { … }
}
const moduleB = {
state: () => ({ … }),
mutations: { … },
actions: { … }
}
const store = createStore({
modules: {

总结

根据路线图上的重点去进行有针对性的学习,在学习过程中,学会写笔记,做总结。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

这里分享一些前端学习笔记:

  • html5 / css3 学习笔记

  • JavaScript 学习笔记

  • Vue 学习笔记

  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前端Vue见的面试中,有以下几个见问: 1. v-show和v-if的区别是什么? v-show和v-if都是Vue中用于控制元素显示与隐藏的指令,但它们的工作原理有所不同。 v-show是通过CSS的display属性来控制元素的显示与隐藏,当条件为真时,元素会显示,条件为假时,元素会隐藏。v-show适用于频繁切换元素显示与隐藏的情况。 v-if是根据条件动态地渲染或销毁元素,当条件为真时,元素会渲染到DOM中,条件为假时,元素会从DOM中移除。v-if适用于在显示逻辑复杂、切换频率较低的情况下使用。 2. Vue中如何监听数据变化? Vue提供了多种方式来监听数据的变化,其中包括: - 使用watch属性来监听特定数据的变化。 - 使用computed属性来计算派生数据,当依赖的数据发生变化时,计算属性会重新计算。 - 使用Vue提供的$watch方法来手动监听数据的变化。 3. Vue中如何编写可复用的组件? 在Vue中,可以通过编写可复用的组件来提高代码的重用性和可维护性。编写可复用的组件需要遵循以下几个步骤: - 将组件的逻辑和样式封装在一个.vue文件中。 - 使用props属性来接收父组件传递的数据。 - 使用emit方法触发自定义事件,让父组件可以监听子组件的状态变化。 - 使用slot插槽来实现组件的灵活性,让父组件可以自定义子组件的内容。 这些是前端Vue见的面试,希望对你有所帮助。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [前端vue经典面试78道(重点详细简洁)](https://blog.csdn.net/weixin_59519449/article/details/123636668)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值