2023高薪前端面试题(三、前端进阶 |
您所在的位置:网站首页 › vuex页面刷新数据丢失怎么办 › 2023高薪前端面试题(三、前端进阶 |
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.vue 子组件内部通过props接收传递过来的值 props: { menuTitle: String } $emit:(子传父)Children.vue 子组件通过自定义事件向父组件传递信息 传值 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.js-PHP中文网 • Vue的生命周期方法有哪些?一般在哪一步发送请求? 参考回答: 钩子概念: vue从创建到销毁过程中,会执行的一些回调函数生命周期就是vue从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期; 分为四大步(创建,挂载,更新,销毁), 每一步又分为两小步,如beforeCreate,created。 1. 生命周期的四个阶段 : 初始化阶段: beforeCreate、 created 挂载阶段 : beforeMount、mounted 更新阶段 : beforeUpdate、updated 销毁阶段: beforeDestroy、destroyed 执行顺序钩子函数执行时机1beforeCreate(){}vue实例创建了,但是el和data还没有创建 底层(初始化vue实例完成,props解析之后,初始化钩子函数,初始化一些事件和侦听器配置项),此时,无法通过VM访问到data中的数据,methods中的方法 2 created() {}data数据创建了,但是el挂载点还没有创建 底层:可以通过VM访问data中的数据和methods中的方法,响应式数据,计算属性、方法和侦听器设置完成,但$el属性仍不可用,不能获取DOM元素 Virtual DOM 生成此阶段Vue开始解析模板,生成虚拟DOM(内存中),页面还不能显示解析好的内容3beforeMount() {}el挂载点创建了,但是data数据还没有渲染,所以页面呈现的是未经vue编译的DOM结构 底层:创建el挂载点,beforeMount前虚拟DOM已经创建完成 组件已经完成了其响应式状态的设置 4mounted() {}data数据 第一次 渲染完毕 (完成初始渲染),将虚拟dom渲染成真实DOM插入页面,页面呈现的是经过vue编译的DOM结构 底层:真实的 Dom 挂载完毕,可以访问DOM节点 初始化操作:开启定时器,发送网络请求,绑定事件等 5beforeUpdate() {}数据是新的,页面是旧的,页面尚未和数据保持同步 检测到data数据变化,但是还没有开始重新渲染 (data变了,准备重新渲染中) 会执行多次 新的Virtual DOM 生成根据新数据,虚拟DOM重新渲染, 打补丁到真实DOM,即完成了 Model → View 的更新。6updated() {}数据是新的,页面也是新的,页面和数据保持同步;变化后的data数据 ,完成渲染到页面 (完成重新渲染,会重复执行)7activated() {}vkeep-alive 专属,组件被激活时调用,被keep-alive包含的组件会被缓存, 即避免重新渲染8deactivated() {}keep-alive 专属,组件被销毁时调用9beforeDestroy() {}vue实例销毁即将销毁(解除data与el的关联),之后修改data,页面不会被渲染 底层 : 解除 事件绑定、侦听器、组件,关闭计时器等 10destroyed() {}仅存在Dom节点,其他所有东西已自动销毁11errorCaptured(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面试题_小胖梅前端的博客-CSDN博客 https://www.cnblogs.com/bbxiaxia1998/p/16888171.html 如何理解Vue生命周期和钩子函数 - 骃骐网 VUE的生命周期 - 简书 【vue2】vue生命周期的理解_初映CY的前说的博客-CSDN博客 图片地址: https://upload-images.jianshu.io/upload_images/24919918-2b75253c04f7b643.png 手写最简单的vue Document /* 组件注册 */ Vue.component('button-counter', { data: function(){ return { count: 0 } }, template: '点击了{{count}}次', methods: { handle: function(){ this.count += 2; } } }) var vm = new Vue({ el: '#app', data: { } }); 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-model 绑定父组件传过来的 props 这样是不规范的写法,开发环境会报警告。 如果实在要改变父组件的 props 值可以再data里面定义一个变量,并用 prop 的值初始化它,之后用$emit 通知父组件去修改。 多种方法实现:在子组件直接用 v-model 绑定父组件传过来的 props 方法1:利用get set方法父组件 父组件msg: {{ msg }} arr: {{ arr }} 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 } } }子组件 子组件父组件传递过来的msg: {{ msg1 }} export default { props: { msg: String }, computed: { msg1: { get () { return this.msg }, set (val) { this.$emit('change',val) } } } } 方法2:监听器父组件不变 父组件msg: {{ msg }} arr: {{ arr }} 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 } } }子组件 子组件父组件传递过来的msg: {{ msg2 }} export default { props: { msg: String }, data () { return { msg2: this.msg } }, watch: { msg2 (val) { this.$emit('change', val) } } } 方法3:对象写法(推荐)父组件 父组件msg: {{ msg }} obj: {{ obj }} import children from './components/children.vue' export default { data () { return { msg: '父组件数据', obj: { name: 'parent' } } }, components: { children }, methods: { change (val) { this.msg = val } } }子组件 子组件父组件传递过来的msg: {{ obj.name }} export default { props: { obj: Object }, watch: { msg2 (val) { this.$emit('change', val) } } } 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的优先级是高于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循环 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 发布订阅模式。 Vue2.x 响应式原理 利用观察者模式 + Object.defineProperty ,收集依赖效率较低,对于深层次数据收集不友好 对于复杂类型数据的删除等操作,监听操作实现较为麻烦 Vue3.0响应式原理 使用了Proxy,收集依赖更加的高效,对于深层次数据的收集更加的方便 可以更好的监听数据的新增、删除等操作 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的原理和区别_林夏天的博客-CSDN博客 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刷新数据丢失_落叶--的悲伤的博客-CSDN博客 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 新标准 也受到浏览器厂商重点持续的性能优化当中 响应式优化。 a. defineProperty API 的局限性最大原因是它只能针对单例属性做监听。 Vue2.x 中的响应式实现正是基于 defineProperty 中的 descriptor,对 data 中的属性 做了遍历 + 递归,为每个属性设置了 getter、setter。 这也就是为什么 Vue 只能对 data 中预定义过的属性做出响应的原因,在 Vue 中使用 下标的方式直接修改属性的值或者添加一个预先不存在的对象属性是无法做到 setter 监听的,这是 defineProperty 的局限性。 b. Proxy API 的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作, 这就完全可以代理所有属性,将会带来很大的性能提升和更优的代码。 Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须 先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。 c. 响应式是惰性的 在 Vue.js 2.x 中,对于一个深层属性嵌套的对象,要劫持它内部深层次的变化,就需 要递归遍历这个对象, 执行 Object.defineProperty 把每一层对象数据都变成响应式 的,这无疑会有很大的性能消耗 。 在 Vue.js 3.0 中,使用 Proxy API 并不能监听到对象内部深层次的属性变化,因此 它的处理方式是在 getter 中去递归响应式,这样的好处是 真正访问到的内部属性才会 变成响应式,简单的可以说是按需实现响应式,减少性能消耗。 • watch 怎么深度监听对象变化 参考回答: deep 设置为 true 就可以监听到对象的变化 let vm=new Vue({ el:"#first", data:{ msg:{name:'北京'} }, watch:{ msg:{ handler (newMsg,oldMsg){ console.log(newMsg); }, immediate:true, deep:true } } }) 删除数组用 delete 和 Vue.delete 有什么区别? 参考回答: • delete:只是被删除数组成员变为 empty / undefined,其他元素键值不变 • Vue.delete:直接删了数组成员,并改变了数组的键值(对象是响应式的,确保删除能触发更新视图,这个方法主要用于避开 Vue 不能检测到属性被删除的限制) Vue3.0 编译做了哪些优化? 参考回答: a. 生成 Block tree Vue.js 2.x 的数据更新并触发重新渲染的粒度是组件级的,单个组件内部 需要遍历该 组件的整个 vnode 树。 在 2.0 里,渲染效率的快慢与组件大小成正相关:组件越大, 渲染效率越慢。并且,对于一些静态节点,又无数据更新,这些遍历都是性能浪费。 Vue.js 3.0 做到了通过编译阶段对静态模板的分析,编译生成了 Block tree。 Block tree 是一个将模版基于动态节点指令切割的嵌套区块 ,每个 区块内部的节点结构是固 定的,每个区块只需要追踪自身包含的动态节点。所以, 在 3.0 里,渲染效率不再与模 板大小成正相关,而是与模板中动态节点的数量成正相关。 b. slot 编译优化 Vue.js 2.x 中,如果有一个组件传入了 slot,那么每次父组件更新的时候,会强制使 子组件 update,造成性能的浪费。 Vue.js 3.0 优化了 slot 的生成,使得非动态 slot 中属性的更新只会触发子组件的更 新。 动态 slot 指的是在 slot 上面使用 v-if,v-for,动态 slot 名字等会导致 slot 产 生运行时动态变化但是又无法被子组件 track 的操作。 c. diff 算法优化 • Vue3.0 是如何变得更快的?(底层,源码) 参考回答: a. diff 方法优化 Vue2.x 中的虚拟 dom 是进行全量的对比。 Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比 带有 patch flag 的节点,并且可以通过 flag 的信息得知当前节点要对比的具体内容 化。 b. hoistStatic 静态提升 Vue2.x : 无论元素是否参与更新,每次都会重新创建。 Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复 用。 c. cacheHandlers 事件侦听器缓存 默认情况下 onClick 会被视为动态绑定,所以每次都会去追踪它的变化但是因为是同一 个函数,所以没有追踪变化,直接缓存起来复用即可。 • 说说你对 proxy 的理解 参考回答: vue 的数据劫持有两个缺点: 1、无法监听通过索引修改数组的值的变化 2、无法监听 object 也就是对象的值的变化 所以 vue2.x 中才会有$set 属性的存在 proxy 是 es6 中推出的新 api,可以弥补以上两个缺点,所以 vue3.x 版本用 proxy 替 换 object.defineproperty。 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |