2021应届秋招前端面经(一)Vue部分

Vue

Vue 虚拟DOM

  • 文档对象模型或DOM定义了一个接口,该接口允许JacasCript访问和操作HTML文档。元素由数中的节点表示,并且接口允许我们操纵它们,但是大量非常频繁的DOM操作会使页面速度变慢
  • 每个元素都是一个节点,每段文字也是一个节点,一个节点就是页面的一部分,每个节点也有对应的子节点
  • 虚拟DOM就是为了解决操作真实DOM带来的性能问题而出现的
  • 虚拟DOM就是用js对象模拟真实的DOM节点(虚拟节点VNode),也就是将所有的更新DOM的操作先全部反应在JS对象上
  • 操作内存中的js对象显然速度要快很多,等更新完成后,再将最终的JS对象映射到真实的DOM上,交由浏览器去绘制页面
  • 总结起来就是

    • 获取监听变化后生成的虚拟节点数
    • 与上一次虚拟DOM节点数进行比较
    • 找到差异的部分,渲染到真实的DOM节点上
    • 更新

diff算法

由于在浏览器中操作DOM的代价是非常“昂贵”的,所以才在Vue引入了Virtual DOM,Virtual DOM是对真实DOM的一种抽象描述,不懂的朋友可以自行查阅相关资料。

即使使用了Virtual DOM来进行真实DOM的渲染,在页面更新的时候,也不能全量地将整颗Virtual DOM进行渲染,而是去渲染改变的部分,这时候就需要一个计算Virtual DOM树改变部分的算法了,这个算法就是Diff算法。

简单的说就是新旧虚拟dom 的比较,如果有差异就以新的为准,然后再插入的真实的dom中,重新渲染

  • 只会做同级比较,不做跨级比较.
  • 比较后几种情况

    • if (oldVnode === vnode),他们的引用一致,可以认为没有变化
    • if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text),文本节点的比较,需要修改,则会调用Node.textContent = vnode.text。
    • if( oldCh && ch && oldCh !== ch ), 两个节点都有子节点,而且它们不一样,这样我们会调用updateChildren函数比较子节点,这是diff的核心
    • else if (ch),只有新的节点有子节点,调用createEle(vnode),vnode.el已经引用了老的dom节点,createEle函数会在老dom节点上添加子节点。
    • else if (oldCh),新节点没有子节点,老节点有子节点,直接删除老节点。

key的作用
不设key,newCh和oldCh只会进行头尾两端的相互比较,设key后,除了头尾两端的比较外,还会从用key生成的对象oldKeyToIdx中查找匹配的节点,所以为节点设置key可以更高效的利用dom
所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。
key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果

v-for中key的作用

主要作用:

  1. key的主要作用就是用来提高渲染性能的!
  2. key属性可以避免数据混乱的情况出现 (如果元素中包含了有临时数据的元素,如果不用key就会产生数据混乱)

    • vue很大的一个特点就是双向数据绑定,数据一旦改变,页面就渲染新的数据呈现在页面。
    • 而为了在增添数据时减少渲染值,复用未改变值,从而提高效率。
    • v-for默认使用复用策略,列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改就重现渲染这一项,否则就复用。
    • 最好是使用数值中不会变化的项作为key,对应到项目中,即每条数据都有一个唯一的 id ,来标识这条数据的唯一性。

    Vue双向绑定原理

    MVVM

  3. M(MODEL,模型层),
  4. V(View,视图层)
  5. VM(ViewModel,V与M连接的桥梁,也可以看作为控制器)
  6. MVVM支持双向绑定,意思就是当M层数据进行修改时,VM层会监测到变化,并且通知V层进行相应的修改,反之修改V层则会通知M层数据进行修改,因此实现了视图与模型层的相互解耦

vue双向数据绑定是通过数据劫持结合发布订阅模式的方式来实现的,也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变。
核心:关于Vue双向数据绑定,其核心是Object.defineProperty()方法
介绍一下Object.defineProperty()方法

  • Object.defineProperty(obj,prop,descriptor),这个语法内有三个参数,分别为obj(要定义其上属性的对象)prop(要定义或修改的属性)descriptor(具体的改变方法)
  • 简单地说,就是用这个方法来定义一个值,当调用时我们使用了它里面get方法,当我们给这个属性赋值时,又用到了它里面的set方法

具体实例内容请参考:https://www.jianshu.com/p/e7e...

响应式原理

当把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是 ES5 特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

  这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。

  每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
  
  简单理解是由数据劫持结合发布者-订阅者模式实现的。说白了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时通过发布者-订阅者模式来实现更新试图。

computed计算属性

计算属性是vue实例中的一个配置选项:computed
比如在data里写了三门课程的得分,我们需要计算平均值。
我们可以在methods里写函数来计算。
但是更好的解决方案是vue提供的computed属性
通常都是一个个计算相关的函数,函数里头可以去写大量的逻辑,最后返回计算出来的值
即我们可以把计算的过程写到一个个计算属性中,让它动态的计算。

  • 计算属性一般就是用来通过其他的数据算出一个新数据,而且有个好处就是,它把新的数据缓存下来了,
  • 当其他的依赖数据没有发生改变,它调用的是缓存的数据,这就极大提高了程序的性能
  • 而写在methods里,数据根本没有缓存的概念,所以每次都会重新机选,这也是为什么不用methods的原因

Vue生命周期

Vue实例有一个完整的生命周期,也就是说从创建,初始化数据、编译模板、挂在DOM、渲染--更新--渲染、卸载等一系列过程,我们称为Vue的生命周期,钩子就是在某个阶段给你一个做某些处理的机会

beforeCreate(创建前):

  • 在实例初始化之后,数据观测和事件配置之前被调用,此时组建的选项对象还未创建,el和data并未初始化,因此无法访问methods,data,computed等上面的方法和数据
    created(创建后):
  • 实例已经创建完成之后被调用,
  • 在这一步,实例已完成以下配置:数据观测、属性和方法的运算、watch/event事件回调,完成了data数据的初始化,el没有。
  • 然而,挂载阶段还没有开始,$el属性目前不可见,这是一个,常用的生命周期,因为你可以调用methods中的方法,改变data中的数据,并且修改可以通过Vue的响应式绑定体现在页面上,获取computed中的计算属性等等,通常我们可以在这里对实例进行预处理,
  • 也有一些喜欢在这里发ajax请求,值得注意的是,这个周期中是没有生命方法来对实例化的过程进行拦截的,因此加入有某些数据必须获取才允许进入页面的话,并不适合在这个方法发送请求,建议在组件路由钩子beforeRouterEnter中完成

beforeMount:

  • 挂载开始之前被调用,相关的render函数首次被调用(虚拟DOM),实例已完成以下的配置
  • 编译模板,把data里面的数据和模板生成html,完成了el和data初始化,注意此时还没有挂载html到页面上

mounted:

  • 挂载完成,也就是模板中的HTML渲染到HTML页面中,此时一般可以做一些ajax操作,mounted只会执行一次

beforeUpdata:

  • 在数据更新之前被调用,发生在虚拟DOM重新渲染和打补丁之前,可以在该钩子中进一步地更改状态,不会触发附加地重渲染过程

updated(更新后):

  • 在由于数据更改导致地虚拟DOM重新渲染和打补丁只会调用,
  • 调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作,然后在大多数情况下,应该避免在此期间更新状态
  • 因为这可能会导致更新无限循环,该钩子在服务器渲染期间不被调用

beforeDestory(销毁前):

  • 在实例销毁之前调用,实例仍然完全可用
  • 这一步还可以用this来获取实例
  • 一般在这一步做一些重置的操作,比如清除掉组件中的定时器和监听的dom事件

destroyed(销毁后):

  • 在实例销毁之后调用,调用后,所有的事件见同期会被移除,所有的子实例也会被销毁,该钩子在服务器端渲染期间不被调用

Vue和React区别

  1. 监听数据变化的实现原理不同
  • Vue通过getter/setter以及一些函数的劫持,能精确知道数据变化
  • React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。
  • 为什么React不精确监听数据 变化?

    • 因为Vue和React设计理念上的区别,Vue使用的是可变数据,而React更强调数据的不可变,两者没有好坏之分,Vue更加简单,React构建大型应用的适合更加好。
  1. 数据流的不同
  • Vue1.0中可以实现两种双向绑定:父子组件之间,props可以双向绑定;组件与DOM之间可以通过v-model双向绑定。
  • Vue2.x中去掉了父子组件双向绑定(但是提供了一个语法糖自动帮你通过事件的方式修改),并且Vue2.x已经不鼓励组件对自己的props进行任何修改了
  • React一直不支持双向绑定,提倡的是单向数据流,称之为onChange/setState()模式。不过由于我们一般都会用Vuex以及Redux等单向数据流的状态管理框架,因此很多时候我们感受不到这一点区别
  1. HoC和mixin
  • Vue组合不同功能的方式是通过mixin,Vue中组件是一个被包装的函数,并不简单的就是我们定义组件的时候传入的对象或者函数。比如我们定义的模板怎么被编译的?比如声明的props怎么接受到的?这些都是Vue创建组件实例的时候隐式干的事。由于Vue默默帮我们做了这么多事儿,所以我们自己如果直接把组件的声明包装以下,返回一个HoC,那么这个被包装的组件就无法正常工作了
  • React组合不同功能的方式是通过HoC(高阶组件)。React最早也是使用mixins的,不过后来他们觉得这种方式侵入太强会导致很多问题,就弃用了mixinx转而使用HoC。高阶组件本质就是高阶函数,React的组件是一个纯粹的函数,所以高阶函数对React来说非常简单。
  1. 组件通信的区别
  • Vue中有三种方式可以实现组件通信:

    • 父组件通过props向子组件传递数据或者回调,虽然可以传递回调,但我们一般只传数据
    • 子组件通过事件向父组件发送消息
    • 通过V2.2.0中新增的provide/inject来实现父组件向子组件注入数据,可以跨越多个层级
  • React中也有对应的三种方式:

    • 父组件通过props可以向子组件传递数据或者回调
    • 可以通过context进行跨层级的通信,这其实和provide/inject起到的作用差不多
    • React本身并不支持自定义事件,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数,但Vue更倾向于使用事件。
    • 在React中我们都是使用回调函数的,这可能是他们二者最大的区别
  1. 模板渲染方式的不同
  • 在表层上,模板语法的不同,React是通过JSX渲染模板。而Vue是通过一种拓展的HTML语法进行渲染,但其实这只是表面现象,比较React并不必须依赖JSX
  • 在深层上,模板的原理不同。这才是本质区别:

    • React实在组件JS代码中,通过原生JS实现模板中的常见语法, 比如插值,条件,循环等,都是通过JS语法实现的,更加纯粹更加原生。
    • 而Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要V-IF来实现。对于这一点,这样的作法显得有些独特,会把HTML弄得很乱
  • 比如说明React的好处:

    • React中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。
    • 但是在Vue中,由于模板中使用的数据都必须挂载在this上进行一次中专,所以我们import一个组件完了之后,还需要再components中再声明下,这样显得很奇怪但又不得不这样做
  1. 渲染过程不同
  • Vue可以更快的计算出VDOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件库
  • React再应用的状态被改变时,全部子组件都会重新渲染,通过shouldComponentUpdate这个生命周期方法可以进行控制,但Vue将此视为默认的优化
  • 如果应用中交互复杂,需要处理大量的UI变化,那么使用VDOM是一个好主意,如果更新元素不频繁,那么VDOM并不一定适用,性能很可能还不如直接操控DOM
  1. 框架本质不同

Vue本质时MVVM框架,由MVC发展而来
React是前端组件化框架,由后端组件化发展而来

  1. Vuex和Redux的区别

从表面上来说,store注入和适用方式有一些区别。

  • 在Vue中,$store被直接注入到了组件实例中,因此可以比较灵活的适用: 使用dispatch,commit提交更新,通过mapState或者直接通过,this.$store来读取数据。
  • 在Redux中,我们每一个组件都需要现实的用connect把需要的props和dispatch连接起来。
  • 另外Vuex更加灵活一些,组件中即可以dispatch action,也可以commitupdates,
  • 而Redux中只能进行dispatch,不能直接调用reducer进行修改

从实现原理上来说,最大的区别是两点:

  • Redux使用的是不可变数据,
  • 而Vuex的数据是可变的,
  • 因此,Redux每次都是用新state替换旧state,而Vuex是直接修改
  • Redux在检测数据变化的时候,是通过diff的的方式比较差异的,
  • 而Vuex和Vue的原理一样,是通过getter/setter来比较的
  • 这两点的区别,也是因为React和Vue设计理念不同。
  • React更偏向于构建稳定大型的应用,非常的刻板化
  • Vue更偏向于简单迅速的解决问题,更灵活,不那么严格遵循条条框框,因此也会给人一种大项目用React,小项目Vue的感觉

vue-router中keep-alive的使用

背景:在使用基于Vue的单页面富应用的开发中,总会面向这样的产品需求,即从列表页跳转到详情页面,返回时需要保存列表页的状态
这种场景下,若是将数据写入到全局状态中,总会面临复杂的页面路由判断来清除和存储页面状态
keep-alive简介
Vue在2.x版本中将keep-alive这已组建属性扩展成了内置的抽象组件,它自身不会渲染一个DOM元素,也不会出现在父组件链中,当组件在内被切换,它的activated和deactivated这两个生命周期钩子函数将会被对应执行。使得被keep-alive组件包裹的组件的状态得以保留也避免了该组件的重复渲染

computed里的get set

  • get是基于其他的数据来获取到computed里定义的那个数据
  • set是根据computed里定义的那个数据来改变其他的数据

Vuex

Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
个人理解 Vuex就是管理组件之间通信的一个插件
Vuex和单纯的全局对象有以下两点不同:

  1. Vuex的状态存储时响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应的得到高效更新
  2. 不能直接改变store中的状态。改变store中的状态的唯一途径就是显式的提交(commit)mutation。这样使得我们可以方便地追踪每一个状态地变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用
  • Store

    • 每一个Vuex应用的核心就是store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态(state)
  • State

    • 驱动应用的数据源,用于保存所有组建的公共数据
  • Getter

    • 可以将getter理解为store的计算属性,getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
  • Mutation

    • mutations对象中保存着更改数据的回调函数,该函数名官方规定叫type,第一个参数式state,第二个参数payload,也就是自定义的参数。mutation必须是同步函数。mutation对象里的方法需要使用store.commiit调用
  • Action

    • Action提交的是mutation而不是直接变更状态。action可以包含任意异步操作。
    • action对象里的方法需要store.dispatch调用
    • Action函数接受一个与store实例具有相同方法的属性的context对象,因此你可以调用context.commit提交一个mutation,或者通过context.state和context.getters来获取state和getters

hash和history理解

hash模式:
即地址栏 URL 中的 # 符号(此 hash 不是密码学里的散列运算)。比如这个 URL: http://www.abc.com/#/hello ,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。
history模式:
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。

vue3新特性

  • 2020.9.19,尤大正式发布了Vue3.0版本。代号One Piece
  • 此框架新的主要版本提供了更好的性能,更小的捆绑包体积,更好的TypeScript集成,用于处理大规模用例的新API,并为框架未来的长期迭代奠定了坚实的基础
  • Vue3.0中引入了Proxy

    • Proxy是ES6中的新增特性,用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)
    • Proxy可以理解为,在目标对象志强假设一层拦截。外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,修改某些操作的默认行为,
    • 这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接操作对象
    • Proxy这个词的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器
  • 组合API(Composition API)

    • 当组件变得越来越大时,逻辑关注点的列表也会增长。这可能会导致组件难以阅读和理解,且碎片化使得理解和维护复杂组件变得困难
    • 选项的分离掩盖了潜在的逻辑问题。
    • 此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码地选项块
    • 如果能够将同一个逻辑关注点相关的代码配置在一起会更好,于是组合API应运而生
    • 使用composition api 地位置被称为setup
  • 引入createAPP

从技术上讲,Vue2没有app的概念,我们定义的应用程序只是通过new Vue()创建的根Vue实例。从同一个Vue构造函数创建的每个根实例共享相同的全局配置,因此全局配置使得在测试期间很容易意外地污染其他测试用例。用户需要仔细存储原始全局配置,并在每次测试后恢复。有些api没有恢复方法,这使得设计插件的测试特别棘手

  • 组件上v-model用法已更改

    • 自定义v-model时,prop和时间默认名称已更改

      • prop: value -> modelValue
      • enent: input -> update: modelValue
    • .sync和组件的model选项已移除,可用v-model作为替代
    • 现在可以在同一个组件上使用多个v-model进行双向绑定
    • 现在可以定义v-model修饰符

      • 比如自定义v-model.capiyalize,绑定为字符串第一个字母的大写