阿里前端常考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 学习笔记

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/578366.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WebSocket 深入浅出

WebSocket 深入浅出 1. WebSocket 是什么2. WebSocket 建立连接通信的过程3. WebSocket 和http的联系与区别4. WebSocket 的使用场景及限制 1. WebSocket 是什么 定义&#xff1a;WebSocket 是一种网络通信协议&#xff0c;它允许在单个TCP连接上进行全双工通信。是HTML5规范提…

网络安全实训Day15

写在前面 电子垃圾&#xff0c;堂堂恢复连载。本来不想分天数梳理了&#xff0c;但是最后要写实训报告&#xff0c;报告里还要有实训日记记录每日学的东西&#xff0c;干脆发这里留个档&#xff0c;到时候写报告提供一个思路。 网络空间安全实训-渗透测试 渗透测试概述 定义 一…

关于conda占C盘内存的问题

文章目录 前言一、C盘中.conda文件中的envs二、C盘中.conda文件中的pkgs 前言 最近发现C盘空间越来越少&#xff0c;于是就去清理了一下conda在C盘的存储&#xff0c;不看不知道&#xff0c;一看吓一跳&#xff0c;足足十几G&#xff01;于是去网上搜索了相关的包能不能删除&a…

(3)C程序可执行文件的生成过程

原文链接&#xff1a;https://www.jianshu.com/p/b7e44f749211 一、可执行文件的生成 我们先通过一个简单C程序&#xff0c;回顾一下可执行文件的生成过程。 ​​​​​​​ ​​​​​​​ 可执行文件的生成过程如下图&#xff1a; 如图&#xff0c;可执行文…

------分割线之 WebSecurityConfigrerAdapter弃用问题------

WebSecurityConfigurerAdapter 被弃用的原因是 Spring Security 项目的维护者希望将项目的主要开发工作集中在新的配置方式上&#xff0c;即基于 Java 的配置&#xff08;Java Configuration&#xff09;和基于 Lambda 的表达式。这主要是因为 Spring 5.0 引入了重量级的 Java …

Windows系统下使用MySQL8.0.22创建第二套数据库

配置新的 MySQL 实例&#xff1a; 为了创建一个新的数据库实例&#xff0c;你需要复制 MySQL 的安装目录并创建一个新的数据目录和配置文件。假设你已经安装了 MySQL 在 C:\Program Files\MySQL\ 下&#xff0c;按照以下步骤操作&#xff1a; 复制整个 MySQL 文件夹&#xff0c…

【探索Java编程:从入门到入狱】Day2

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

STM32 USB HID报告描述符没有报告长度

STM32 USB HID设置(STM32CubeMX)_我也想成大侠的博客-CSDN博客 不影响鼠标功能

神经网络中多层卷积的作用

在神经网络中采用多层卷积的目的是为了逐步提取和组合图像的抽象特征&#xff0c;从而更有效地学习数据的表示并执行复杂的任务。不同层的卷积具有不同的作用&#xff0c;从较低层次的特征&#xff08;例如边缘、纹理&#xff09;到较高层次的抽象特征&#xff08;例如物体部件…

中国AI崛起!领先全球实现胰腺癌早筛,打破美国垄断!

人工智能在医疗领域的应用近年来备受关注&#xff0c;尤其是在癌症早筛领域。近期&#xff0c;斯坦福大学发布了《2024年AI指数报告》&#xff0c;透露2023年美国人工智能投资额为672亿美元&#xff0c;是中国的约8.7倍。其中&#xff0c;阿里巴巴达摩院&#xff08;湖畔实验室…

【Godot4.2】有序和无序列表函数库 - myList

概述 在打印输出或其他地方可能需要构建有序或无序列表。本质就是构造和维护一个纯文本数组。并用格式化文本形式&#xff0c;输出带序号或前缀字符的多行文本。 为此我专门设计了一个类myList&#xff0c;来完成这项任务。 代码 以下是myList类的完整代码&#xff1a; # …

AI对决:谷歌 VS 微软,谁更赚钱|TodayAI

尽管Alphabet和微软都发布了强劲的季度财报&#xff0c;显示两家科技巨头均超越了销售和利润的预期&#xff0c;但在生成式人工智能&#xff08;AI&#xff09;领域的投资回报方面&#xff0c;它们展现了不同的情况。Alphabet的CEO桑达尔皮查伊表示&#xff0c;他对Google通过出…

【Win】PsPing:深入网络性能测试与故障排查

在维护 Azure 虚拟机的过程中&#xff0c;可能会遇到一些网络连通性的问题。例如&#xff0c;当您尝试从个人 PC 上 ping 虚拟机的公网 IP 地址时&#xff0c;可能会发现出现 “Request time out” 的信息&#xff0c;导致无法 ping 通。这种情况的发生&#xff0c;通常是因为在…

【C++打怪之路Lv3】-- 类和对象(上)

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;C打怪之路&#xff0c;python从入门到精通&#xff0c;数据结构&#xff0c;C语言&#xff0c;C语言题集&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文(平均质量分82)&#…

IDEA上配置Maven环境

1.选择IDEA中的Setting 2.搜索maven 3.设置IDEA使用本地安装的Maven&#xff0c;并修改配置文件路径 配置文件&#xff0c;本地仓库&#xff0c;阿里云仓库配置及路径教程 在IDEA上配置完成。

Java学习路线及自我规划

荒废了一段时间&#xff0c;这段时间的总结开始了JavaWeb的学习但是困难重重&#xff0c;例如Maven&#xff0c;Vue的路由等&#xff0c;所以我反省了一段时间&#xff0c;因为基础薄弱&#xff0c;加之学习的资源是速成视频&#xff0c;导致大厦将倾的局面&#xff08;也算不上…

Golang | Leetcode Golang题解之第52题N皇后II

题目&#xff1a; 题解&#xff1a; func totalNQueens(n int) (ans int) {columns : make([]bool, n) // 列上是否有皇后diagonals1 : make([]bool, 2*n-1) // 左上到右下是否有皇后diagonals2 : make([]bool, 2*n-1) // 右上到左下是否有皇后var backtrack func(int)…

使用预训练模型构建自己的深度学习模型(迁移学习)

在深度学习的实际应用中&#xff0c;很少会去从头训练一个网络&#xff0c;尤其是当没有大量数据的时候。即便拥有大量数据&#xff0c;从头训练一个网络也很耗时&#xff0c;因为在大数据集上所构建的网络通常模型参数量很大&#xff0c;训练成本大。所以在构建深度学习应用时…

【redis】Redis数据类型(二)Hash类型

目录 Hash类型介绍特性hash 的内部编码方式/底层结构hashtableziplistlistpack 适用场景举例 常用命令hset示例 hsetnx示例&#xff1a; hmset示例 hget示例 hmget示例 hgetall示例 hdel示例 hlen示例 hexists示例 hincrby示例 hincrbyfloat示例 hkeys示例 hvals示例 Hash类型介…

VS2019编译OSG3.7.0+OSGEarth3.3+OSGQt5.15.2时遇到的问题及解决方法

注:本次编译以文章《VS2019编译OSG3.7.0+OSGEarth3.3+OSGQt》为基础搜集资料并进行编译 一 OSG编译 1.Osg3.7.0编译中,cmake阶段按照文章步骤即可。 2.另外,还需要对以下三项进行设置,参照《OSG-OpenSceneGraph在WIN10与VS2022下的部署(OSG3.6.5+VS2022+Win10_x64)个…