-
Vue 运行机制,全局流程图
Vue.js 运行机制全局概览 · 剖析 Vue.js 内部运行机制 · 看云
-
Vue为什么出现?解决了什么问题?Vue跟传统开发的区别?
1、如果使用传统开发模式 jQuery + Ajax + 模板引擎:
(jQuery是一个封装了原生JS常用的功能代码JavaScript库)
前端程序员需要手动拼接模板字符串,然后手动操作DOM元素,把拼接出来的模板字符串,添加到页面指定区域;
所以,如果使用传统的开发模式,在公司中,主要的工作就是获取数据、拼接数据、append到页面上,同时,还要处理一部分的前端业务逻辑。
总结:
传统的开发模式下,前端开发人员,除了要关心前端的业务逻辑,更多的是在操作DOM。
考虑:前端能不能只关心业务,尽量不去操作DOM元素呢?(操作DOM的工作没有任何含金量的)
为了实现以上的需求,就需要用到前端的MVVM设计思想了。
2、总结:
- Vue所有的界面事件,都是只去操作数据的,Jquery操作DOM
- Vue所有界面的变动,都是根据数据自动绑定出来的,Jquery操作DOM
-
说说你对vue的理解?
特点:
-
Vue核心特性
数据驱动(MVVM)
首先,MVVM是前端的概念,MVC是后端的;MVVM把前端中的没一个页面,都分成了三部分:
MVVM
表示的是 Model-View-ViewModel
Model(模型)
模型是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)。
View(视图)
就像在MVC和MVP模式中一样,视图是用户在屏幕上看到的结构、布局和外观(UI)。
ViewModel(视图模型)
视图模型层,即VM调度者,用来连接Model和View,是Model和View之间的通信桥梁。
优点:
低耦合 :View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化
的时候Model可以不变,当Model变化的时候View也可以不变。
可重用性 : 可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
独立开发 : 开发人员可以专注于业务逻辑和数据的开发,设计人员可以专注于页面的设计。
- Model:模型层,负责处理业务逻辑以及和服务器端进行交互
- View:视图层:负责将数据模型转化为UI展示出来,可以简单的理解为HTML页面
- ViewModel:视图模型层,即VM调度者,用来连接Model和View,是Model和View之间的通信桥梁
这时候需要一张直观的关系图,如下
在MVVM框架下 视图和模型是不能直接通信的,只能通过ViewModel进行交互,它能够监听到数据的变化,然后通知视图进行自动更新,而当用户操作视图时,VM也能监听到视图的变化,然后通知数据做相应改动,这实际上就实现了数据的 双向绑定 。并且V和VM可以进行通信。
VM的好处:解放了程序员,不用操作DOM,只需要关心业务逻辑就可以了
组件化
1.什么是组件化
一句话来说就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue
中每一个.vue
文件都可以视为一个组件
2.组件化的优势
- 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、时间、范围等组件作具体的实现
- 调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单
- 提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级
指令系统
解释:指令 (Directives) 是带有 v- 前缀的特殊属性作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
-
常用的内置指令
- 条件渲染指令
v-if
- 列表渲染指令
v-for
- 属性绑定指令
v-bind
- 事件绑定指令
v-on
- 双向数据绑定指令
v-model
- 条件渲染指令
没有指令之前我们是怎么做的?是不是先要获取到DOM然后再....干点啥
-
什么是MVC?
MVC是应用最广泛的软件架构之一,一般MVC分为:Model(模型),View(视图),Controller(控制器)。 这主要是基于分层的目的,让彼此的职责分开.View一般用过Controller来和Model进行联系。Controller是Model和View的协调者,View和Model不直接联系。基本都是单向联系。M和V指的意思和MVVM中的M和V意思一样。C即Controller指的是页面业务逻辑。MVC是单向通信。也就是View跟Model,必须通过Controller来承上启下。
Model(模型)表示应用程序核心(如数据库)。
View(视图)显示效果(HTML页面)。
Controller(控制器)处理输入(业务逻辑)。
MVC 模式同时提供了对 HTML、CSS 和 JavaScript 的完全控制。
Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责在数据库中存取数据。
View(视图)是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。
Controller(控制器)是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
优点:
- 低耦合
- 重用性高
- 生命周期成本低
- 部署快
- 可维护性高
- 有利软件工程化管理
3. MVC与MVVM的区别:
MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。
MVC中Controller演变成MVVM中的ViewModel
MVVM通过数据来显示视图层而不是节点操作
MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验
-
Vue和React对比
这里就做几个简单的类比吧,当然没有好坏之分,只是使用场景不同
相同点
- 都有组件化思想
- 都支持服务器端渲染
- 都有Virtual DOM(虚拟dom)
- 数据驱动视图
- 都有支持native的方案:
Vue
的weex
、React
的React native
- 都有自己的构建工具:
Vue
的vue-cli
、React
的Create React App
区别
- 数据变化的实现原理不同。
react
使用的是不可变数据,而Vue
使用的是可变的数据 - 组件化通信的不同。
react
中我们通过使用回调函数来进行通信的,而Vue
中子组件向父组件传递消息有两种方式:事件和回调函数 - diff算法不同。
react
主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。Vue
使用双向指针,边对比,边更新DOM
面试官:说说你对vue的理解? · Issue #1 · febobo/web-interview · GitHub
-
为什么data是一个函数而不是一个对象
根实例对象data
根实例对象data
可以是对象也可以是函数(根实例是单例),不会产生数据污染情况
组件实例对象data
组件实例对象data
必须为函数,目的是为了防止多个组件实例对象之间共用一个data
,产生数据污染。采用函数的形式,initData
时会将其作为工厂函数都会返回全新data
对象
总结:
组件的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一分新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果,即数据污染。
-
Vue组件通讯有哪些方式?
组件间通信的分类
-
父子组件之间的通信
-
兄弟组件之间的通信
-
祖孙与后代组件之间的通信
-
非关系组件间之间的通信
组件间通信的方案
1、props 和 $emit。父组件向子组件传递数据是通过props传递的,子组件传递给父组件是通过$emit触发事件来做到的。
2、$parent 和 $children 获取当前组件的父组件和当前组件的子组件。
3、$attrs 和 $listeners A -> B -> C。Vue2.4开始提供了$attrs和$listeners来解决这个问题。
4、父组件中通过 provide 来提供变量,然后在子组件中通过 inject 来注入变量。(官方不推荐在实际业务中适用,但是写组件库时很常用。)
5、$refs 获取组件实例。
6、envetBus 兄弟组件数据传递,这种情况下可以使用事件总线的方式。
7、vuex 状态管理
具体演示:
props:(父传子)
Father.vue 父组件通过属性将值传递给子组件
<Children :menu-title="title" />
Children.vue 子组件内部通过props接收传递过来的值
props: {
menuTitle: String
}
$emit:(子传父)
Children.vue 子组件通过自定义事件向父组件传递信息
<button @click="setUser">传值</button>
data() {
return {
user: '子组件要传给父组件的值'
}
},
methods: {
setUser() {
this.$emit('handle',this.user)
}
}
Father.vue 父组件监听子组件的事件
< Children @handle="getUser" />
data() {
return {
username: ''
}
},
methods: {
getUser(msg) {
this.username = msg
}
}
ref
父组件在使用子组件的时候设置ref
父组件通过设置子组件ref来获取数据
父组件Father.vue
< Children ref="foo" />
this.$refs.foo // 获取组件实例,通过子组件实例我们就能拿到对应的数据
vuex
适用场景: 复杂关系的组件数据传递
Vuex作用相当于一个用来存储共享变量的容器
state用来存放共享变量的地方
getter,可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值
mutations用来存放修改state的方法。
actions也是用来存放修改state的方法,不过action是在mutations的基础上进行。常用来做一些异步操作
• Vue的生命周期方法有哪些?一般在哪一步发送请求?
生命周期就是vue从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期;
分为四大步(创建,挂载,更新,销毁),
每一步又分为两小步,如beforeCreate,created。
1. 生命周期的四个阶段 :
- 初始化阶段: beforeCreate、 created
- 挂载阶段 : beforeMount、mounted
- 更新阶段 : beforeUpdate、updated
- 销毁阶段: beforeDestroy、destroyed
执行顺序 | 钩子函数 | 执行时机 |
1 | beforeCreate(){} | vue实例创建了,但是el和data还没有创建 底层(初始化vue实例完成,props解析之后,初始化钩子函数,初始化一些事件和侦听器配置项),此时,无法通过VM访问到data中的数据,methods中的方法 |
2 | created() {} | data数据创建了,但是el挂载点还没有创建 底层:可以通过VM访问data中的数据和methods中的方法,响应式数据,计算属性、方法和侦听器设置完成,但$el属性仍不可用,不能获取DOM元素 |
Virtual DOM 生成 | 此阶段Vue开始解析模板,生成虚拟DOM(内存中),页面还不能显示解析好的内容 | |
3 | beforeMount() {} | el挂载点创建了,但是data数据还没有渲染,所以页面呈现的是未经vue编译的DOM结构 底层:创建el挂载点,beforeMount前虚拟DOM已经创建完成 组件已经完成了其响应式状态的设置 |
4 | mounted() {} | data数据 第一次 渲染完毕 (完成初始渲染),将虚拟dom渲染成真实DOM插入页面,页面呈现的是经过vue编译的DOM结构 底层:真实的 Dom 挂载完毕,可以访问DOM节点 初始化操作:开启定时器,发送网络请求,绑定事件等 |
5 | beforeUpdate() {} | 数据是新的,页面是旧的,页面尚未和数据保持同步 检测到data数据变化,但是还没有开始重新渲染 (data变了,准备重新渲染中) |
新的Virtual DOM 生成 | 根据新数据,虚拟DOM重新渲染, 打补丁到真实DOM,即完成了 Model → View 的更新。 | |
6 | updated() {} | 数据是新的,页面也是新的,页面和数据保持同步;变化后的data数据 ,完成渲染到页面 (完成重新渲染,会重复执行) |
7 | activated() {}v | keep-alive 专属,组件被激活时调用,被keep-alive包含的组件会被缓存, 即避免重新渲染 |
8 | deactivated() {} | keep-alive 专属,组件被销毁时调用 |
9 | beforeDestroy() {} | vue实例销毁即将销毁(解除data与el的关联),之后修改data,页面不会被渲染 底层 : 解除 事件绑定、侦听器、组件,关闭计时器等 |
10 | destroyed() {} | 仅存在Dom节点,其他所有东西已自动销毁 |
11 | errorCaptured(2.5.0+ 新增) | 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。 |
beforeCreate前,也就是new Vue的时候会初始化事件和生命周期;
beforeCreate 在实例初始化之后,数据观测(data observe)和 event/watcher 事件配置之前被调用。在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问。
created 实例已经创建完成之后被调用。在这一步,实例已经完成以下的配置:数据观测(data observe ),属性和方法的运算,watch/event 事件回调。这里没有 $el,如果非要想与 DOM 进行交互,可以通过vm.$nextTick 来访问 DOM。
beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted 在挂载完成后发生,在当前阶段,真实的 Dom 挂载完毕,数据完成双向绑定,可以访问到 Dom节点。
beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁 (patch)之前。可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。(数据修改页面未修改)
updated 发生在更新完成之后,当前阶段组件 Dom 已经完成更新。要注意的是避免在此期间更新数据,因为这个可能导致无限循环的更新,该钩子在服务器渲染期间不被调用。
beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。我们可以在这时进行 善后收尾工作,比如清除定时器。
destroyed Vue实例销毁后调用。调用后,Vue实例指示的东西都会解绑定,所有的事件监听器会被移除,左右的子实例也会被销毁,该钩子在服务器端渲染不被调用。
activated keep-alive 专属,组件被激活时调用,可以实现组件的缓存,当组件切换时不会对当前组件进行卸载。常用的2个属性
include/exclude
deactivated keep-alive 专属,组件被销毁时调用
2. 异步请求在哪一步发起?
可以在钩子函数 created、beforeMount、mounted 中进行异步请求,因为在这三个钩子函数中,data已经创建,可以将服务器端返回的数据进行赋值。
如果异步请求不需要依赖 DOM 推荐加载 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
- 能更快获取到服务端数据,减少页面loading时间;
- ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
如果依赖DOM元素:需要再mounted里面进行请求
3. 第一次加载页面会触发哪几个钩子函数?
beforeCreate, created, beforeMount, mounted 这几个钩子函数
2023年最新的Vue全套面试题(含答案)_vue面试题_小胖梅前端的博客-优快云博客
https://www.cnblogs.com/bbxiaxia1998/p/16888171.html
【vue2】vue生命周期的理解_初映CY的前说的博客-优快云博客
图片地址:
https://upload-images.jianshu.io/upload_images/24919918-2b75253c04f7b643.png
-
手写最简单的vue
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<button-counter></button-counter>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
组件注册
*/
Vue.component('button-counter', {
data: function(){
return {
count: 0
}
},
template: '<button @click="handle">点击了{{count}}次</button>',
methods: {
handle: function(){
this.count += 2;
}
}
})
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
-
v-if 和 v-show 的区别
v-if
在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点。元素销毁和重建控制显示隐藏
v-show
会被编译成指令,条件不满足时控制样式将此节点隐藏(display:none) css样式控制。
使用场景
v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景。
v-show 适用于需要非常频繁切换条件的场景。
扩展补充:display:none 、 visibility:hidden 和 opacity:0 之间的区别?
三者公共点都是 隐藏。
不同点:
是否占据空间。
display:none,隐藏之后不占位置;
visibility:hidden、opacity:0,隐藏后任然占据位置。
子元素是否继承。
display:none --- 不会被子元素继承,父元素都不存在了,子元素也不会显示出来。
visibility:hidden --- 会被子元素继承,通过设置子元素 visibility:visible 来显示子元素。
opacity:0 --- 会被子元素继承,但是不能设置子元素 opacity:0 来重新显示。
事件绑定。
display:none 的元素都已经不存在了,因此无法触发他绑定的事件。
visibility:hidden 不会触发他上面绑定的事件。
opacity:0 元素上面绑定的事件时可以触发的。
过度动画。
transition对于display是无效的。
transition对于visibility是无效的。
transition对于opacity是有效的。
-
说说 vue 内置指令
- v-once - 只编译一次,显示内容后不再具有响应功能。
- v-cloak - 解决插值表达式“闪动”问题, 原理是先隐藏,替换好值之后再显示最终值。
- v-bind - 绑定属性,动态更新HTML元素上的属性。例如 v-bind:class,简写:class。
- v-on - 用于监听DOM事件。例如 v-on:click v-on:keyup,简写@click
- v-html - 填充HTML片段,赋值就是变量的innerHTML -- 注意防止xss攻击,存在安全问题,本网站内部数据可用,第三方的数据不可用;
- v-text - 填充纯文本,相比插值表达式更简洁;
- v-pre - 填充原始信息,跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度。
- v-model - 1、在普通标签。变成value和input的语法糖,并且会处理拼音输入法的问题。2、在组件上。也是处理value和input语法糖。
- v-if / v-else / v-else-if。可以配合template使用;在render函数里面就是三元表达式。
- v-show - 使用指令来实现 -- 最终会通过display来进行显示隐藏
- v-for - 循环指令编译出来的结果是 -L 代表渲染列表。优先级比v-if高最好不要一起使用,尽量使用计算属性去解决。注意增加唯一key值,不要使用index作为key。
-
怎样理解 Vue 的单项数据流
数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能请求父组件对原始数据进行修改。这样会防止从子组件意外改变父组件的状态,从而导致你的应用的数据流向难以理解。
注意:在子组件直接用 v-model 绑定父组件传过来的 props 这样是不规范的写法,开发环境会报警告。
如果实在要改变父组件的 props 值可以再data里面定义一个变量,并用 prop 的值初始化它,之后用$emit 通知父组件去修改。
多种方法实现:在子组件直接用 v-model 绑定父组件传过来的 props
方法1:利用get set方法
父组件
<template>
<div>
<h2>父组件</h2>
<p>msg: {{ msg }}</p>
<p>arr: {{ arr }}</p>
<children :msg="msg" @change="change" />
</div>
</template>
<script>
import children from './components/children.vue'
export default {
data () {
return {
msg: '父组件数据',
arr: [10, 20, 30],
num: '',
count: 1000
}
},
components: {
children
},
methods: {
change (val) {
this.msg = val
}
}
}
</script>
子组件
<template>
<div>
<h2>子组件</h2>
<input type="text" v-model="msg1">
<p>父组件传递过来的msg: {{ msg1 }}</p>
</div>
</template>
<script>
export default {
props: {
msg: String
},
computed: {
msg1: {
get () {
return this.msg
},
set (val) {
this.$emit('change',val)
}
}
}
}
</script>
方法2:监听器
父组件不变
<template>
<div>
<h2>父组件</h2>
<p>msg: {{ msg }}</p>
<p>arr: {{ arr }}</p>
<children :msg="msg" @change="change" />
</div>
</template>
<script>
import children from './components/children.vue'
export default {
data () {
return {
msg: '父组件数据',
arr: [10, 20, 30],
num: '',
count: 1000
}
},
components: {
children
},
methods: {
change (val) {
this.msg = val
}
}
}
</script>
子组件
<template>
<div>
<h2>子组件</h2>
<input type="text" v-model="msg2">
<p>父组件传递过来的msg: {{ msg2 }}</p>
</div>
</template>
<script>
export default {
props: {
msg: String
},
data () {
return {
msg2: this.msg
}
},
watch: {
msg2 (val) {
this.$emit('change', val)
}
}
}
</script>
方法3:对象写法(推荐)
父组件
<template>
<div>
<h2>父组件</h2>
<p>msg: {{ msg }}</p>
<p>obj: {{ obj }}</p>
<children :msg="msg" @change="change" :obj="obj"/>
</div>
</template>
<script>
import children from './components/children.vue'
export default {
data () {
return {
msg: '父组件数据',
obj: {
name: 'parent'
}
}
},
components: {
children
},
methods: {
change (val) {
this.msg = val
}
}
}
</script>
子组件
<template>
<div>
<h2>子组件</h2>
<input type="text" v-model="obj.name">
<p>父组件传递过来的msg: {{ obj.name }}</p>
</div>
</template>
<script>
export default {
props: {
obj: Object
},
watch: {
msg2 (val) {
this.$emit('change', val)
}
}
}
</script>
-
computed 和 watch 的区别和运用的场景
区别:
computed:计算属性
- 支持缓存,data不变不会重新计算函数;
- 不支持异步操作;
- 自动监听依赖值的变化,从而动态返回内容;
- 可以设置getter和setter。
watch: 侦听属性
- 不支持缓存,只要数据变化,就会执行侦听函数
- 支持异步操作
- 侦听属性的值可以是一个对象,接受handler回调,deep,immediate三个属性
watch: {
isHot: {
// 设置为true时会立刻执行以表达式的当前值触发回调
inmediate: true,
handler接收两个参数(newVal:新值,oldVal:旧值
handler(newvalue, oldvalue) {
console.log('修改了', newvalue, oldvalue);
},
//deep设置为true时会监听对象内部值的变化
deep:true
}
}
使用场景
computed:
用在模板渲染中,当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
watch:
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
-
Vue中key的作用
vue 中 key 值的作用可以分为两种情况来考虑:
v-if 中使用 key
- 第一种情况是 v-if 中使用 key。由于 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。因此当使用 v-if 来实现元素切换的时候,如果切换前后含有相同类型的元素,那么这个元素就会被复用。如果是相同的 input 元素,那么切换前后用户的输入不会被清除掉,这样是不符合需求的。因此可以通过使用 key 来唯一的标识一个元素,这个情况下,使用 key 的元素不会被复用。这个时候 key 的作用是用来标识一个独立的元素。
v-for 中使用 key
- 第二种情况是 v-for 中使用 key。用 v-for 更新已渲染过的元素列表时,它默认使用“就地复用”的策略。如果数据项的顺序发生了改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处的每个元素。因此通过为每个列表项提供一个 key 值,来以便 Vue 跟踪元素的身份,从而高效的实现复用。这个时候 key 的作用是为了高效的更新渲染虚拟 DOM。
对于diff 算法
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,diff 操作可以更准确、更快速
- 更准确:因为带 key 就不是就地复用了,在 sameNode 函数a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。
- 更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快
-
v-if 和 v-for 为什么不建议一起使用
-
在vue2中,v-for的优先级更高
-
在vue3中,v-if的优先级更高
Vue2
在Vue2中,v-for的优先级是高于v-if的,如果作用在同一元素上,输出的渲染函数中可以看出会先执行先循环再判断条件,也哪怕只渲染列表中一小部分元素,得在每次重渲染的时候遍历整个列表,这会造成性能的浪费
Vue3
而在Vue3中,v-if的优先级时高于v-for的,这意味着v-if的条件将访问不到v-for作用域内定义的变量别名,会导致报错。
使用场景
通常有两种情况导致需要v-if和v-for同时使用:
- 为了过滤列表中的项目,例如v-for = 'user in users' v-if = 'user.isActive' 。此时可以定义出一个计算属性,例如activeUsers,让其返回过滤后的列表即可,users.filter( u => u.isActive)
如果条件出现在循环内部,可通过计算属性computed提前过滤掉那些不需要显示的项
computed: {
items: function() {
return this.list.filter(function (item) {
return item.isShow
})
}
}
- 为了避免渲染本应该被隐藏的列表,例如v-for = 'user in users' v-if = 'shouldShowUsers'。此时可以把v-if绑定在容器元素上,例如ul,ol或在外包一层template
如果避免出现这种情况,则在外层嵌套template(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环
<template v-if="isShow">
<p v-for="item in items">
</template>
10. Vue 2.0 响应式数据的原理(常问)
整体思路是 数据劫持 + 观察者模式
对象内部通过 defineReactive 方法,使用 Object.defineProperty 将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。当页面使用对应属性时,每个属性都拥有自己的 dep 属性,存放他所依赖的 watcher(依赖收集),当属性变化后会通知自己对应的 watcher 去更新(派发更新)。
Vue 在初始化数据时 ,会使用 Object.defineProperty 重新定义 data 中的所有属性 ,当页面 使用对 应 属性时,首先会进行 依赖收集 (收集当前组件的 watcher ),如果属性发生变化会通知相关 依赖进行 更新操作( 发布订阅 )
Vue2.x 采用 数据劫持结合发布订阅模式 (PubSub 模式)的方式,通过 Object.defineProperty 来劫持各个属性 的 setter、getter ,在 数据变动时发布消息给订阅者 , 触发相应的监听回调。
当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用
Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让
Vue 追踪依赖,在属性被访问和修改时 通知变化 。
Vue 的数据 双向绑定 整合了 Observer,Compile 和 Watcher 三者,通过 Observer 来监听 自己的model 的数据变化,通过 Compile 来解析编译模板指令,最终 利用 Watcher 搭 起 Observer 和Compile 之间的 通信桥梁 ,达到数据变化->视图更新,视图交互变化(例如 input 操作)->数据model 变更的双向绑定效果。
Vue3.x 放弃了 Object.defineProperty ,使用 ES6 原生的 Proxy,来解决以前使用
Object.defineProperty 所存在的一些问题。
总结:
1、Object.defineProperty 数据劫持
2、使用 getter 收集依赖 ,setter 通知 watcher派发更新。
3、watcher 发布订阅模式。
-
Vue3.0响应式原理
Vue2.x 响应式原理
利用观察者模式 + Object.defineProperty ,收集依赖效率较低,对于深层次数据收集不友好
对于复杂类型数据的删除等操作,监听操作实现较为麻烦
Vue3.0响应式原理
使用了Proxy,收集依赖更加的高效,对于深层次数据的收集更加的方便
可以更好的监听数据的新增、删除等操作
-
Vue 如何检测数组变化
Vue2.x 中实现检测数组变化的方法,考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择对 7 种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写(AOP 切片思想)。
Vue 将 data 中的数组进行了 原型链重写 ,指向了自己定义的数组原型方法。这样当调用数组 api 时,可以通知依赖更新 。如果数组中包含着引用类型,会对数组中的引用类型 再次递归遍历进行监控 。这样就实现了 监测数组变化 。
流程:
1. 初始化传入 data 数据执行 initData
2. 将数据进行观测 new Observer
3. 将数组原型方法指向重写的原型
4. 深度观察数组中的引用类型
有两种情况无法检测到数组的变化 。
1. 当利用索引直接设置一个数组项时,例如 vm.items[indexOfItem] = newValue
2. 当修改数组的长度时,例如 vm.items.length = newLength
不过这两种场景都有对应的解决方案。
利用索引设置数组项的替代方案
使用该方法进行更新视图 vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
-
Vue的父子组件生命周期钩子函数执行顺序
加载渲染过程
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created
-> 子 beforeMount -> 子 mounted -> 父 mounted
子组件更新过程
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父组件更新过程
父 beforeUpdate -> 父 updated
销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
总结:
父组件先开始 子组件先结束
-
v-model 双向绑定的原理是什么?
v-model 本质 就是 : value + input 方法的语法糖 。可以通过 model 属性的 prop 和 event 属性来进行自定义。原生的 v-model,会根据标签的不同生成不同的事件和属性。
例如:
1. text 和 textarea 元素使用 value 属性和 input 事件
2. checkbox 和 radio 使用 checked 属性和 change 事件
3. select 字段将 value 作为 prop 并将 change 作为事件
以输入框为例,当用户在输入框输入内容时,会触发 input 事件,从而更新 value。而 value 的改变同样会更新视图,这就是 vue 中的双向绑定。双向绑定的原理,其实现思路 如下:
首先要对 数据进行劫持监听 ,所以我们需要设置 一个监听器 Observe r,用来 监听 所有属 性。如果属性发上变化了,就需要告 诉订阅者 Watcher 看是否需要更新 。
因为订阅者是有很多个,所以我们需要有一个 消息订阅器 Dep 来专门收集这些订阅者 ,然 后在监听器 Observer 和订阅者 Watcher 之间 进行统一管理的。
接着,我们还需要有一个 指令解析器 Compile ,对每个节点元素进 行扫描和解析 ,将相关指令对应初始化成一个订阅者 Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者 Watcher 接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
因此接下去我们执行以 下 3 个步骤,实现数据的双向绑定 :
1. 实现一个 监听器 Observer ,用来 劫持并监听所有属性,如果有变动的,就通知订阅者。
2. 实现一个 订阅者 Watcher ,可以 收到属性的变化通知并执行相应的函数,从而更新视图。
3. 实现一个 解析器 Compile ,可以 扫描和解析每个 节点的相关指令,并根据 初始化模板数据以及初始化相应的订阅器。
-
Vue3.x 响应式数据原理
Vue3.x 响应式数据原理是什么?
在 Vue 2 中,响应式原理就是使用的 Object.defineProperty 来实现的。但是在 Vue 3.0 中采用了 Proxy,抛弃了 Object.defineProperty 方法。 究其原因,主要是以下几点:
Object.defineProperty 无法监控 到数组下标的变化 ,导致通过数组下标添加元素,不能实时响应 Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。 Proxy 可以劫持整个对象,并返回一个新的对象 。
Proxy 只会代理对象的第一层,那么 Vue3 又是怎样处理这个问题的呢 ?
判断当前 Reflect.get 的返回值是否为 Object,如果是则再通过 reactive 方法做代理, 这样就实现了深度观测。
监测数组的时候可能触发多次 get/set ,那么如何 防止触发多次呢?
我们可以判断 key 是否为当前被代理对象 target 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。
Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。
-
vue2.x 和 vuex3.x 渲染器的 diff 算法分别说一下?
简单来说,diff 算法有以下过程
- 同级比较,再比较子节点
- 先判断一方有子节点一方没有子节点的情况(如果新的 children 没有子节点,将旧的子
- 节点移除)
- 比较都有子节点的情况(核心 diff)
- 递归比较子节点
正常 Diff 两个树的时间复杂度是 O(n^3),但实际情况下我们很少会进行跨层级的移动 DOM,所以 Vue 将 Diff 进行了优化,从O(n^3) -> O(n),只有当新旧 children 都为多个子节点时才需要用核心的 Diff 算法进行同层级比较。
Vue2 的核心 Diff 算法采用了双端比较的算法 ,同时从新旧 children 的两端开始进行比较, 借助 key 值找到可复用的节点,再进行相关操作。相比 React 的 Diff 算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
Vue3.x 借鉴了 ivi 算法和 inferno 算法
在 创建 VNode 时就确定其类型,以及在 mount/patch 的过程中 采用位运算来判断 一个 VNode 类型,在这个基础之上再配合核心的 Diff 算法,使得性能上较 Vue2.x 有了提 升 。该算法中还运用了动态规划的思想求解最长递归子序列。
-
hash 模式和 history 模式的实现原理
相同点:更新视图但不重新请求页面
hash 值的变化 , 不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面;通过监听 hashchange 事件可以知道 hash 发生了哪些变化,然后根据 hash 变化来实现更新页面部分内容的操作。
history 模式 的实现,主要是 HTML5 标准发布的 两个 API , pushState 和 replaceState ,这两API 可以在改变 URL,但是不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作。
两种模式的区别:
1、首先是在 URL 的展示上,hash 模式有“#”,history 模式没有
刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 没有处理的话,会返回404,一般需要后端将所有页面都配置重定向到首页路由
2、在兼容性上,hash 可以支持低版本浏览器和 IE,hash 兼容IE8以上,history 兼容 IE10 以上
hash 模式
1 、 location.has 的值实际就是 URL 中 # 后面的东西。它的特点在于: hash 虽然出现 URL 中,但不会被包含在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新 加载页面。
2 、可以为 hash 的改变添加监听事件
window.addEventListener("hashchange",funcRef,false)
hash值变化会触发window.hashChange
事件,每一次改变 hash (window.location.hash) ,都会在浏览器的访问历史中增加一个记录, 利用hash 的以上特点,就可以实现前端路由 “ 更新视图但不重新请求页面 ” 的功能了
特点:兼容性好但是不美观
history 模式
利用 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。 这两个方法应用于浏览器的历史记录站,在当前已有的 back 、 forward 、 go 的基础上, 他们提供了对历史记录进行修改的功能。这两个方法有个共同点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页面应用前端路由“ 更新视图但不重新请求页面 ” 提供了基础
特点:虽然美观,但是刷新会出现 404 需要后端进行配置。
hash模式和history模式 实现原理及区别_hash和history的原理和区别_林夏天的博客-优快云博客
-
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:{
"router":function(){
this.getData(this.$router.params.xxx)
}
}
2 、用 :key 来阻止复用
router-view :key="$route.fullPath"
-
谈一下对 vuex 的个人理解
vuex 是什么
vuex 是一个专为 Vue 应用程序开发 的状态管理器, 采用集中式 存储管理 应用的所有组件的状态。每 一个 vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着应用中大部分 的状态 (state)。
为什么需要 vuex
由于组件只维护自身的状态(data),组件创建时或者路由切换时,组件会被初始化,从而导致 data 也 随之销毁。
使用方法
在 main.js 引入 store,注入。只用来读取的状态集中放在 store 中, 改变状态的方式是提交
mutations,这是个同步的事物,异步逻辑应该封装在 action 中。
什么场景下会使用到 vuex 如果是 vue 的小型应用,那么没有必要使用 vuex,这个时候使用 vuex 反而会带来负担。组件之间的 状态传递使用 props、自定义事件来传递即可。 但是如果 涉及到 vue 的大型应用 ,那么就需要类似于 vuex 这样的集中管 理状态的状态机来管理所有 组件的状态。例如登录状态、加入购物车、音乐播放等,总之只要是开发 vue 的大型应用,都推荐使 用 vuex 来管理所有组件状态
主要包括以下几个模块:
- State:定义了应用状态的数据结构,可以在这里设置默认的初始化状态。
- Getter:允许组件从Store中获取数据,mapGetters 辅助函数仅仅是将 store 中的getter 映射到局部计算属性。
- Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
- Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步请求。
- Module:允许将单一的 Store 拆分更多个 store 且同时保存在单一的状态树中。
-
Vuex 页面刷新数据丢失怎么解决?
一:数据丢失的原因
- vuex存储的数据只是在页面中,相当于全局变量,页面刷新的时候vuex里的数据会重新初始化,导致数据丢失。
- 因为vuex里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,vuex里面的数据就会被重新赋值。
二:解决的思路
- 将vuex中的数据直接保存到浏览器缓存中(sessionStorage、localStorage、cookie)
- 页面刷新后再从浏览器中取出
- 使用 vuex-persist ( 脯肉赛斯特 ) 插件,它是为 Vuex 持久化储存而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中。
解决Vuex刷新页面数据丢失的问题_vuex刷新数据丢失_落叶--的悲伤的博客-优快云博客
-
vue 中使用了哪些设计模式?
- 工厂模式 - 传入参数即可创建实例 虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode。
- 单例模式 - 整个程序有且仅有一个实例 vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉。
- 发布-订阅模式。(vue 事件机制)
- 观察者模式。(响应式数据原理)
- 装饰器模式(@装饰器的用法)
- 策略模式,策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案 - 比如选项的合并策略。
-
你都做过哪些 Vue 的性能优化?
这里只列举针对 Vue 的性能优化,整个项目的性能优化是一个大工程。
- 对象层级不要过深,否则性能就会差。
- 不需要响应式的数据不要放在 data 中(可以使用 Object.freeze() 冻结数据)
- v-if 和 v-show 区分使用场景
- computed 和 watch 区分场景使用
- v-for 遍历必须加 key,key最好是id值,且避免同时使用 v-if
- 大数据列表和表格性能优化 - 虚拟列表 / 虚拟表格
- 防止内部泄露,组件销毁后把全局变量和时间销毁
- 图片懒加载
- 路由懒加载
- 异步路由
- 第三方插件的按需加载
- 适当采用 keep-alive 缓存组件
- 防抖、节流的运用
- 服务端渲染 SSR or 预渲染
-
nextTick 的作用是什么?他的实现原理是什么
作用 :vue 更新 DOM 是异步更新的,数据变化,DOM 的更新不会马上完成, nextTick的回调是在下次 DOM 更新循环结束之后执行的延迟回调 。
实现原理 :nextTick 主要使用了 宏任务和微任务 。根据执行环境分别尝试采用
- Promise:可以将函数延迟到当前函数调用栈最末端
- MutationObserver :是 H5 新加的一个功能,其功能是监听 DOM 节点的变动,在所有 DOM 变动完成后,执行回调函数setImmediate:用于中断长时间运行的操作,并在浏览器完成其他操作(如事件和显 示更新)后立即运行回调函数
- 如果以上都不行则采用 setTimeout 把函数延迟到 DOM 更新之后再使用,原因是宏任务消耗大于微任务,优先使用微任务,最后使用消耗最大的宏任务
-
keep-alive 使用场景和原理
keep-alive 组件是 vue 的内置组件 ,用于 缓存内部组件 实例。这样做的目的在于,keep
alive 内部的组件切回时, 不用重新创建 组件实例,而直接使用缓存中的实例,一方面能够避免创建组件带来的开销,另一方面可以保留组件的状态 。
keep-alive 具有 include 和 exclude 属性,通过它们可以控制哪些组件进入缓存。另外它 还提供了 max 属性,通过它可以设置最大缓存数,当缓存的实例超过该数时,vue 会移除最久没有使用的组件缓存。
受keep-alive的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是activated 和 deactivated,它们分别在组件激活和失活时触发。第一次 activated 触发是在 mounted 之后
在具体的实现上,keep-alive 在内部维护了一个 key 数组和一个缓存对象
1 // keep-alive 内部的声明周期函数
2 created () {
3 this.cache = Object.create(null)
4
5 this.keys = []
6 }
Vue.set 方法原理
了解 Vue 响应式原理的同学都知道在两种情况下修改 Vue 是不会触发视图更新的。
- 在实例创建之后添加新的属性到实例上(给响应式对象新增属性)
- 直接更改数组下标来修改数组的值。
Vue.set 或者说是 $set 原理如下
因为响应式数据 我们给对象和数组本身新增了 __ob__ 属性,代表的是 Observer 实例。
当给对象新增不存在的属性,首先会把新的属性进行响应式跟踪 然后会触发对象 __ob__的 dep 收集到的 watcher 去更新,当修改数组索引时我们调用数组本身的 splice 方法去更新数组。
-
Proxy 相比 defineProperty 的优势在哪里
Vue3.x 改用 Proxy 替代 Object.defineProperty
原因在于 Object.defineProperty 本身存在的一 些问题 :
- Object.defineProperty 只能劫持对象属性的 getter 和 setter 方法。
- Object.definedProperty 不支持数组(可以监听数组,不过数组方法无法监听自己重写),更准确的说是不支持数组的各种 API(所以 Vue 重写了数组方法。
而相比 Object.defineProperty,Proxy 的优点在于:
- Proxy 是直接代理劫持整个对象。
- Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。
目前,Object.definedProperty 唯一比 Proxy 好的一点就是兼容性,不过 Proxy 新标准
也受到浏览器厂商重点持续的性能优化当中
• watch 怎么深度监听对象变化
let vm=new Vue({
el:"#first",
data:{
msg:{name:'北京'}
},
watch:{
msg:{
handler (newMsg,oldMsg){
console.log(newMsg);
},
immediate:true,
deep:true
}
}
})
-
删除数组用 delete 和 Vue.delete 有什么区别?
-
Vue3.0 编译做了哪些优化?