前端高阶面试题之Vue

.什么是vue生命周期

Vue 实例从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。
作用: 生命周期中有多个事件钩子,在控制整个Vue实例的过程时更容易形成好的逻辑。
beforeCreate: 完成实例初始化,this指向被创建的实例,data,computed,watch,mothods方法和数据都不可以访问,数据观测之前(data observer)被调用。
created: 实例创建完成,data,computed,watch,methods 可被访问,未挂载dom,可对 data 进行操作,操作 dom 需放到nextTick
beforeMount: 有了el,找到对应的 template 编译成 render 函数
mounted: 完成挂载dom和渲染,可对dom进行操作,并获取dom节点,可发起后端请求拿到数据
beforeUpdate: 数据更新时调用,发生在虚拟Dom重新渲染和打补丁之前之调用
updated: 组件 dom 已完成更新,可执行依赖的 dom 操作,不要操作数据会陷入死循环
beforeDestroy: 实例销毁之前调用,可进行优化操作,如销毁定时器,解除绑定事件
destroyed: 组件已经被销毁,事件监听器和子实例都会被移除销毁

第一次页面加载会触发: beforeCreate, created, beforeMount, mounted
并且`` 渲染在 mounted 中就已经完成了。

可以使用 $on('hook:')$once('hook:') 来简化生命周期的注册

.请说一下 Vue 响应式数据的原理是什么?

在 Vue 初始化数据时, 使用 Object.defineProperty 重新定义 data 中所有属性,增加了数据 获取(getter) / 设置(setter) 的拦截功能。在 获取 / 设置 时可增加一些逻辑,这个逻辑交叫作 依赖收集。当页面取到对应属性时会进行依赖收集, 如果属性发生变化, 则会通知收集的依赖进行更新,而负责收集的就是 watcher
如负责渲染的 watcher 会在页面渲染的时候对数据进行取值,并把当前 watcher 先存起来对应到数据上,当更新数据的时候告诉对应的 watcher 去更新, 从而实现了数据响应式。

data 一般分为两大类: 对象类型 和 数组:

对象:

在 Vue 初始化的时候,会调用 initData 方法初始化 data,它会拿到当前用户传入的数据。判断如果已经被观测过则不在观测,如果没有观测过则利用 new Observer 创建一个实例用来观测数据。如果数据是对象类型非数组的话会调用 this.walk(value) 方法把数据进行遍历,在内部使用 definReactive 方法重新定义( definReactive 是比较核心的方法: 定义响应式 ),而重新定义采用的就是 Object.defineProperty 。如当前对象的值还是个对象,会自动调用递归观测。当用户取值的时候会调用 get 方法并收集当前的 wacther 。在 set 方法里,数据变化时会调用 notify 方法触发数据对应的依赖进行更新。

数组:

使用函数劫持的方式重写了数组的方法,并进行了原型链重写。使 data 中的数组指向了自己定义的数组原型方法。这样的话,当调用数组 API 时,可以通知依赖更新。如果数组中包含着引用类型,则会对数组中的引用类型进行再次监控。
也就是当创建了 Observer 观测实例后,如果数据是数组的话,判断是否支持自己原型链,如果不支持则调用 protoAugment 方法使目标指向 arrayMethods 方法。arrayMethods 就是重写的数组方法,包括 pushpopshiftunshiftsplicesortreverse 共七个可以改变数组的方法,内部采用函数劫持的方式。在数组调用重写的方法之后,还是会调用原数组方法去更新数组。只不过重写的方法会通知视图更新。如果使用 pushunshiftsplice 等方法新增数据,会调用 observeArray 方法对插入的数据再次进行观测。
如果数组中有引用类型,则继续调用 observeArray 方法循环遍历每一项,继续深度观测。前提是每一项必须是对象类型, 否则 observe 方法会直接 return

.为何Vue采用异步渲染?

如不采用异步更新, 则每次更新数据都会对当前组件进行重新渲染, 因此为了性能考虑 Vue 在本轮数据更新结束后,再去异步更新视图。
当数据变化之后, 会调用 notify 方法去通知 watcher 进行数据更新。而 watcher 会调用 update 方法进行更新(这里就是发布订阅模式)。更新时并不是让 wathcer 立即执行, 而是放在一个队列里进行过滤,相同的 watcher 只存一个, 这个队列就是 queueWatcher 方法。最后在调用 nextTick 方法通过 flushSchedulerQueue 异步清空 watcher 队列。

.nextTick 实现原理?

nextTick 方法主要是使用了宏任务微任务定义了一个异步方法。多次调用 nextTick 会将方法存入队列中, 通过这个异步方法清空当前队列。所以 nextTick 方法就是异步方法。
默认在内部调用 nextTick 时会传入 flushSchedulerQueue 方法, 存在一个数组里并让它执行。用户有时也会调用 nextTick , 调用时把用户传过来的 cb 也放在数组里 , 都是同一个数组 callbacks 。多次调用 nextTick 只会执行一次, 等到代码都执行完毕后,会调用 timerFunc 这个异步方法。在方法里会依次进行判断所支持的类型:

  1. 如支持 Promise 则把 timerFunc 包裹在了 Promise 中并把 flushCallbacks 放在了 then 中, 相当于异步执行了 flushCallBacksflushCallBacks 函数作用就是让传过来的方法依次执行。
  2. 如不是 IE 、支持 Mutationobserve 并且 是原生的 Mutationobserve。首先声明一个变量并创建一个文本节点。接着创建 Mutationobserve 实例并把 flushCallBacks 传入, 调用 observe 方法去观测每一个节点。如果节点变化会异步执行 flushCallBacks方法。
  3. 如果支持 setImmediate , 则调用 setImmediate 传入flushCallBacks 异步执行。
  4. 以上都不支持就只能调用 setTimeout 传入 flushCallBacks

作用:$nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM

.请说一下 Vue 中 Computed 和 watch ?

默认 computedwatch 内部都是用一个 watcher 实现的 。
computed有缓存功能, 不会先执行, 只有当依赖的属性发生变化才会通知视图跟新。
watcher 没有缓存,默认会先执行,只要监听的属性发生变化就会更新视图。

computed

调用 initComputed 方法初始化计算属性时,会获取到用户定义的方法,并创建一个 watcher 把用户定义传进去, 这个 watcher 有个标识: lazy = true,默认不会执行用户定义的函数。还有个标识 dirty = true 默认去求值 。watcher 内部调用 defineComputed 方法将计算属性定义在实例上, 其底层也是用的 Object.defineProperty。并且传入了 createComputedGetter 方法定义一个计算属性。在用户取值时,调用的是createComputedGetter返回函数 computedGetter。判断当前的 watcher.dirty 是否为 true。如果为 true 则调用 watcher.evaluate 方法求值。在求值时是调用的 this.get() 方法.其实 this.get() 就是用户传入的方法,执行时会把方法里的属性依次取值。而在取值前调用了 pushTarget 方法将 watcher 放在了全局上,当取值时会进行依赖收集,把当前的计算属性的 watcher 收集起来。等数据变化则通知 watcher 重新执行,也就是进入到了 update 方法中。update 并没有直接让 watcher 执行,而是将 dirty = true。这样的好处就是,如果 dirty = true,就进行求值,否则就返回上次计算后的值,从而实现了缓存的机制。

watch

调用 initWatch 方法初始化 watch 的时候, 内部传入用户定义的方法调用了 createWatcher 方法。在 createWatcher 方法中比较核心的就是 $watch 方法内部调用了 new Watcher 并传入了 expOrFn 和 回调函数。expOrFn 如果是个字符串的话, 会包装成一个函数并返回这个字符串。这时 lazy = false 了, 则直接调用了 this.get() 方法, 直接取属性的值。同 computed 在取值前也执行 pushTarget 方法将 watcher 放在了全局上, 当用户取值时就收集了 watcher。 因此当属性值发生改变时, watcher 就会更新。
如果监听的属性值是个对象, 则取对象里的值就不会更新了, 因为默认只能对属性进行依赖收集, 不能对属性值是对象的进行依赖收集。想要不管属性值是否是对象都能求值进行收集依赖, 可设置 deep = true 。如设置了deep = true ,则会调用 traverse 方法进行递归遍历。

.Vue 组件中 data 为什么必须是一个函数?

因为 js 本身的特性带来的,同一个组件被复用多次,会创建多个实例。这些实例是同一个构造函数。如果 data 是一个对象的话,那么所有组件都共享了同一个对象。为了保证组件中数据的独立性要求每个组件必须通过data 函数返回一个对象作为组件的状态。
Vue 通过 extend 创建子类之后,会调用 mergeOptions 方法合并父类和子类的选项,选中就包括 data。在循环完父类和子类之后调用 mergeField 函数的中的 strat 方法去合并 data,如果 data 不是函数而是个对象,则会报错提示 data 应该是个函数。

.谈谈MVVM模式

Model: 代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
View: 代表UI 组件,它负责将数据模型转化成UI 展现出来。
ViewModel: 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。

在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向自动的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。而开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

MVVM 和 MVC区别?

mvcmvvm其实区别并不大。都是一种设计思想。主要就是 mvcController 演变成 mvvm 中的 viewModelmvvm 主要解决了mvc 中大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。和当 Model 频繁发生变化,开发者需要主动更新到 View

.Vue 中事件绑定原理

Vue 中事件绑定分为两种:

  1. 原生事件绑定: 采用的是 addEventListener 实现
  2. 组件事件绑定: 采用的是 $on 方法实现

click 事件为例,普通 dom 元素绑定事件是 @click ,编译出来是 onclick事件,组件绑定事件是 @click 组件自定义事件 和 @click.native原生事件两种,编译出来分别是 onclick 事件, nativeOnclick 事件。组件的 nativeOn 等价于普通元素的 on ,而组件的 on 单独处理。
渲染页面时,普通 dom 会掉用 updateDOMListeners 方法,内部先把 data.on 方法拿出来,然后调用 updateListeners 方法来添加一个监听事件,同时会传入一个 add$1 方法。内部调用 addEventListener 方法直接把事件绑定到元素上。
而组件会调用 updateComponentListeners 方法。内部也是调用 updateListeners 方法但传入的是 add 方法。这里的 add 方法与普通元素的 domadd$1 方法略有不同,采用的是自己定义的发布订阅模式 $on 方法,解析的是 on 方法,组件内部通过 $emit 方法触发的。还有 click.native 方法是直接把事件绑在了最外层元素上,用的也是 updateListeners 方法传入 add$1方法。

.v-model 的实现原理是什么?

通俗讲 v-model 可以看成是 value + input 的语法糖。
组件的 v-model 也确实是这样 。在组件初始化的时候, 如果检测到有 model 属性,就会调用 transformModel 方法转化 model。如果没有 prop 属性和 event 属性, 则默认会给组件 propvalue 属性, 给 eventinput 事件 。把 prop 的属性赋给了 data.attrs 并把值也给了它, 即 data.attrs.value = '我们所赋的值'。会给 on 绑定 input 事件, 对应的就是 callback
如果在组件内自定义 modelpropevent, 这样的话组件初始化的时候, 接受 属性事件 时不再是 valueinput 了, 而是我们自定义的 属性事件
如果是普通的标签, 则在运行时会自动判断标签的类型, 生成不同的属性 domProp 和 事件 on。还增加了指令 directive, 针对输入框的输入法加上了一些逻辑并做了校验和处理。

. Vue中的 v-show 和 v-if 是做什么用的, 两者有什么区别?

v-if:会在 with 方法里进行判断,如果条件为 true 则创建相应的虚拟节点,否则就创建一个空的虚拟节点也就是不会渲染 DOM
v-show: 会在 with 方法里创建了一个指令就 v-show,在运行的时候处理指令,添加了 style: display = none / originalDisplay

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

相比之下, v-show 就简单的多,不管初始条件是什么,元素总会被渲染, 并且只是简单的基于 css 进行切换。

一般来说,v-if 有更高的切换开销,v-show 有更高的初始渲染开销。
因此,如需要频繁的切换则使用 v-show 较好,如在运行时条件不大可能改变则使用 v-if 较好。

. v-if 和 v-for 为什么不能连用?

v-for 的优先级会比 v-if 要高, 在 调用 with 方法编译时会先进行循环, 然后再去做 v-if的条件判断, 因此性能不高。
因此一般会把 v-if 提出来放在 v-for 外层, 或者想要连用把渲染数据放在计算属性里进行过滤。

.Vue 中的 v-html 会导致哪些问题

v-html 其原理就是用 innerHtml 实现的的, 如果不能保证内容是完全可以被依赖的, 则可能会导致 xxs 攻击。
在运行的时候, 调用 updateDOMProps 方法或解析配置的属性, 如果判断属性是 innerHTML 的话, 会清除所有的子元素。

.Vue 中f父子组件的调用顺序

组件的调用都是先父后子,渲染完成的过程顺序都是先子后父
组件的销毁操作是先父后子,销毁完成的顺序是先子后父
在页面渲染的时候,先执行父组件的 beforeCreate -> created->befroreMount, 当父组件实例化完成的时候会调用 rander 方法, 判断组件是不是有子组件, 如果有子组件则继续渲染子组件以此类推。当子组件实例化完成时候, 会把子组件的插入方法先存起来放到 instertedVNodeQueue 队列里, 最后会调用 invokeIntertHook 方法把当前的队列依次执行。
更新也是一样, 先父beforeUpdate -> 子beforeUpdate 再到 子 updated -> 父 updated

.Vue 中组件怎么通讯?

  1. 父子通讯: 父 -> 子 props, 子 -> 父 $on / $emit
    通过 eventsMixin 方法中的 $on 方法 维护一个事件的数组,然后将函数名传入 $emit 方法,循环遍历出函数并执行。
  2. 获得父子组件实例的方式:$parent / $children
    在初始化的时候调用 initLifecycle 方法初始化 $parent$children 放在实例上
  3. 在父组件中提供数据供子组件/孙子组件注入进来: Provide / Inject
    通过 initProvideinitInjections 方法分别把 providereject 放在 $options 上。在调用 reject 的时候,调用 resolveInject 方法遍历,查看父级是否有此属性,有则就直接 return 并把它定义在自己的实例上。
  4. Ref 获得实例的方式调用组件的属性或方法
    ref 被用来给元素或子组件注册引用信息。 引用信息将会注册在父组件的 $refs 对象上。
    用在 DOM 上就是 DOM 实例,用在组件上就是组件实例
  5. Event bus 实现跨组件通讯
    实质上还是基于 $on$emit,因为每个实例都有 $on$emit 并且事件的绑定和触发必须在同一个实例,所以一般会专门定义一个实例去用于通信,如 Vue.prototype.$bnts = new Vue
  6. Vuex 状态管理实现通讯
  7. $attrs$Listeners 实现数据 和 事件的传递,还有 v-bind="$prop"

.为什么使用异步组件?

可使用异步的方式加载组件,减少打包体积,主要依赖 import() 语法,可实现文件的分割加载

components:{
  testCpt: (resove) => import("../components/testCpt")  或
  testCpt: r => require(['@/views/assetsInfo/assetsProofList'],r)
}

加载组件的时候, 如果组件是个函数会调用 resolveAsyncComponent 方法, 并传入组件定义的函数 asyncFactory , 并让其马上执行。因为是异步的所以执行后并不会马上返回结果, 而返回的是一个 promise,因此没有返回值, 返回的是一个占位符。
加载完成后,会执行 factory 函数并传入了成功/失败的回调。在回调 resolve 成功的回调时会调用 forceRander 方法, 内部调用 $forceUpdate 强制刷新。之后 resolveAsyncComponent 判断已经执行成功,就是去创建组件、初始化组件和渲染组件。

.什么是作用域插槽?

  1. 插槽: 创建组件虚拟节点时,会将组件儿子的虚拟节点先保存起来。初始化组件时,通过插槽属性将儿子进行分类。(作用域为父组件)
    渲染组件时会拿对应的 slot 属性的节点进行替换操作。
  2. 作用域插槽: 在解析的时候不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此函数进行渲染。(作用域为子组件)

普通插槽编译时调用 createElement 方法创建组件,并把子节点生成虚拟 dom 做好标识存起来。渲染时调用 randerSlot 方法循环匹配出对应的虚拟节点在父组件替换当前位置。
而作用域插槽在编译时会把子组件编译成函数,函数不调用就不会渲染。也就是说在初始化组件的时候并不会渲染子节点。渲染页面时调用 randerSlot 方法执行子节点的函数并把对应的属性传过来。当节点渲染完成之后在组件内部替换当前位置。

.说说对keep-alive的了解

keep-alive 是一个抽象组件,可实现组件缓存。当组件切换时不会对当前组件进行卸载。
算法: LRU -->最近最久未使用法
常用的生命周期: activateddeactivated

声明 keep-alive 时在函数里设置了几个属性: propscreateddestroyedmountedrander 等;

  1. props: 调用 keep-alive 组件可设置的属性,共有三个属性如下:
    include: 想缓存的组件
    exclude:不想缓存的组件
    max:最多缓存多少个
  2. created: 创建一个缓存列表
  3. destroyed: 销毁时清空所有缓存列表
  4. mounted: 会监听 include 和 exclude, 动态添加 或 移除缓存.
  5. rander: 渲染时拿到第一个组件,拿到第一个组件,判断是不是在缓存里.

.$route$router 的区别是什么?

$routerVueRouter 实例,是个全局路由对象,包含路由跳转方法、钩子函数等。
$route 是路由信息对象||跳转的路由对象,每一个路由都会有一个route对象,是一个局部对象,包含path,params,hash,query,fullPath,matched,name等路由信息参数。

. Vue 路由的钩子函数

首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。
一些需要登录才能调整页面的重定向功能。
beforeEach主要有3个参数to,from,next:
to:route即将进入的目标路由对象,
from:route当前导航正要离开的路由
next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。

. vue-router有哪几种路由守卫?

全局守卫:beforeEach
后置守卫:afterEach
全局解析守卫:beforeResolve
路由独享守卫:beforeEnter

全局路由勾子(router.beforeEach)
件路由勾子(beforeRouteEnter)
组件路由勾子的next里的回调(beforeRouteEnter)

.Vue 中常见的性能优化

  1. 编码优化
    (1). 不要将所有的数据放在data里,data中的数据都会增加 getter和setter,收收集对应的 watcher
    (2). 在v-for时给每项元素绑定事件必须使用时间代理
    (3). SPA页面采用 keep-alive 缓存组件
    (4). 拆分组件(提高复用性,增加代码的可维护性,减少不必要的渲染)
    (5). v-if 当值为 false 时内部指令不执行具有阻断功能,很多情况下使用v-if 代替 v-show
    (6). 使用 key 保证唯一性
    (7). 使用 Object.freeze 冻结数据,冻结后不再有 gettersetter
    (8). 合理使用路由懒加载和异步组件
    (9). 数据持久化问题如: 防抖、节流
  2. Vue 加载性能优化
    (1). 第三方模块按需导入(babel-plugin-component)
    (2). 滚动可视区域动态加载(vue-virtual-scroll-list / 'vue-virtual-scroller') -- 长列表优化
    (3). 图片懒加载(vue-lazyload)
  3. 用户体验
    (1). app-skeleton 骨架屏
    (2). app-sheapp
  4. SEO 优化
    (1). 预加载插件 prerender-spa-plugin
    (2). 服务端渲染 ssr
  5. 打包优化
    (1). 使用 CDN 的方式加载第三方模块
    (2). 多线程打包
    (3). splitChunk 抽离公共文件
  6. 缓存 压缩
    (1). 客户端缓存和服务端缓存
    (2). 服务端gzip压缩

.Vue等单页面应用(spa)及其优缺点

优点: Vue的目标是通过尽可能简单的 API实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好;即第一次就将所有的东西都加载完成,因此,不会导致页面卡顿。
缺点: 不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏览器的导航按钮需要自行实现前进、后退。

.assets 和 static的区别

相同点: assets 和 static两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下。
不相同点:
assets 中存放的静态资源文件在项目打包时,会将 assets 中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在static文件中跟着index.html一同上传至服务器。
static 中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是static中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于assets中打包后的文件提交较大点。在服务器中就会占据更大的空间。
建议:将项目中template需要的样式文件js文件等都可以放置在assets中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如iconfoont.css等文件可以放置在static中,因为这些引入的第三方文件已经经过处理,我们不再需要处理,直接上传。

.hash模式 和 history模式

hash: 在 url 中带有 #,其原理是 onhashchange 事件。
可以在 window 对象上监听这个事件:

window.onhashchange = function(event){
     ...
}

history: 没有原 # , 其原理是 popstate 事件,需要后台配置支持。
html5中新增两个操作历史栈的API: pushState()replaceState() 方法。

history.pushState(data[,title][,url]); // 向历史记录中追加一条记录
history.replaceState(data[,title][,url]); // 替换当前页在历史记录中的信息。

这两个方法也可以改变url,页面也不会重新刷新,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。

.Vuex是什么? 怎么使用它? 哪种功能场景使用?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex 只能使用在 vue 上,因为其高度依赖于 vue 的双向绑定和插件系统。
调用了 Vue.mixin,在所有组件的 beforeCreate 生命周期注入了设置 this.$store 这样一个对象。
场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车
state: Vuex 使用单一状态树,存放的数据状态,不可以直接修改里面的数据。
mutations: 定义的方法动态修改Vuex 的 store 中的状态或数据。
getters: 类似vue的计算属性,主要用来过滤一些数据。
actions: 可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。
modules: 项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

actions 和 mutations的区别

action主要处理的是异步的操作,mutation必须同步执行,而action就不受这样的限制,也就是说action中我们既可以处理同步,也可以处理异步的操作
action改变状态,最后是通过提交mutation

Vue 中 key 的作用是什么?

需要使用 key 给每一个节点做唯一标识
diff 算法可以正确识别此节点,可以更高效的更新虚拟DOM。

.用vnode描述一个DOM结构

虚拟节点就是用一个对象描述真实的dom元素
会将 template 先转换成 ast 树, ast 通过代码生成 codegen 转成 rander函数, rander函数内部调用 $createElement 方法简称 _c, 传入 tag (创建的元素), data(元素的属性), children(子元素) . 会判断 children 是不是一个字符串, 否则会做深度递归, 最后返回的结果就是一个对象,可描述出DOM 结构.

.简述 Vue 中 diff 算法原理

  1. 先同级比较, 在比较子节点.
  2. 判断出一方有子节点另一方没有子节点的情况.
    如果新的一方有子节点,老的没有,则把子节点直接插入到老节点里即可.
    如果老的一方有子节点,新的没有,则把老的子节点直接删除.
  3. 判断出都有子节点的情况, 递归遍历子采用双指针(头/尾指针)的方式比对节点.

.Vue与Angular以及React的区别?

1.与AngularJS的区别
相同点:

  1. 都支持指令:内置指令和自定义指令。
  2. 都支持过滤器:内置过滤器和自定义过滤器。
  3. 都支持双向数据绑定。
  4. 都不支持低端浏览器。

不同点:

  1. AngularJS 的学习成本高,比如增加了 Dependency Injection 特性,而Vue.js本身提供的API都比较简单、直观。
  2. 在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢。
    Vue.js使用基于依赖追踪的观察并且使用异步队列更新。所有的数据都是独立触发的。 对于庞大的应用来说,这个优化差异还是比较明显的。

2.与React的区别
相同点:

  1. React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用。
  2. 中心思想相同:一切都是组件,组件实例之间可以嵌套。
  3. 都提供合理的钩子函数,可以让开发者定制化地去处理需求。
  4. 都不内置AJAX,Route等功能核心包,而是以插件的方式加载。
  5. 在组件开发中都支持mixins的特性。

不同点:

React依赖Virtual DOM,而Vue.js使用的是DOM模板。React采用的Virtual DOM会对渲染出来的结果做脏检查。
Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作DOM

11. 高精度全局权限处理

权限控制由前端处理时,通常使用 v-if / v-show 控制元素对不同权限的响应效果。这种情况下,就会导致很多不必要的重复代码,不容易维护,因此可以造一个小车轮,挂在全局上对权限进行处理。

  // 注册全局自定义指令,对底层原生DOM操作
  Vue.directive('permission', {
        // inserted → 元素插入的时候
        inserted(el, binding){
            // 获取到 v-permission 的值
            const { value } = binding
            if(value) {
                // 根据配置的权限,去当前用户的角色权限中校验
                const hasPermission = checkPermission(value)
                if(!hasPermission){
                    // 没有权限,则移除DOM元素
                    el.parentNode && el.parentNode.removeChild(el)
                }
            } else{
                throw new Error(`need key! Like v-permission="['admin','editor']"`)
            }
        }
    })
    // --> 在组件中使用 v-permission
    <button v-permission="['admin']">权限1</button>
    <button v-permission="['admin', 'editor']">权限2</button>

12 Vue.use 与 Vue.component 的区别

都用于注册全局组件/插件的

Vue.component() 每次只能注册一个组件,功能很单一。
Vue.component('draggable', draggable)

Vue.use() 内部调用的仍是 Vue.component() 去注册全局组件/插件,但它可以做更多事情,比如多次调用 Vue.component() 一次性注册多个组件,还可以调用Vue.directive()、Vue.mixins()、Vue.prototype.xxx=xxx 等等,其第二个可选参数又可以传递一些数据

Vue.use({
    install:function (Vue, options) {
        // 接收传递的参数: { name: 'My-Vue', age: 28 }
        console.log(options.name, options.age)
        Vue.directive('my-directive',{
            inserted(el, binding, vnode) { }
        })
        Vue.mixin({
            mounted() { }
        })
        Vue.component('draggable', draggable)
        Vue.component('Tree', Tree)
    }
}, 
{ name: 'My-Vue', age: 28 })

在main.js 文件里 动态注册全局组件时, 或用到 require.context

require.context(): 一个 Webpack 的API,获取一个特定的上下文(创建自己的context),主要用来实现自动化导入模块。
它会遍历文件夹中的指定文件,然后自动化导入,而不需要每次都显式使用 import / require 语句导入模块!
在前端工程中,如果需要一个文件夹引入很多模块,则可以使用 require.context()

require.context(directory, useSubdirectories = false, regExp = /^\.\//)

directory {String} 读取目录的路径
useSubdirectories {Boolean} 是否递归遍历子目录
regExp {RegExp} 匹配文件的正则

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

(1). 监测机制的改变

3.0 基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。替代了Vue 2采用 defineProperty去定义get 和 set, 意味着彻底放弃了兼容IE, 这也取消除了 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 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。
vue3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易。

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

(4). 其它方面的更改

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335