大前端之vue

449 阅读17分钟

1. vue双向绑定原理

mvvm双向绑定,采用数据劫持结合发布者订阅者模式的方式,通过Object.defineProperty()函数来劫持各个属性的setter和getter函数,在数据变化时发布消息给订阅者,触发相应的监听回调。

1.png

几个要点

  1. 实现一个数据监听器observe, 能够对数据对象的每个属性进行监听,在数据更新时可拿到最新值并通知订阅者。
  2. 实现一个指令解析器compile, 对每个元素节点进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
  3. 实现一个watcher,作为连接observe和compile的桥梁,添加订阅并收集每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。
  4. mvvm入口函数,整合以上三者。

具体步骤

  1. 需要observe对数据对象进行递归遍历,包括属性对象的属性,都加上setter和getter函数,这样给对象的某个属性赋值时,就会触发setter,那么就能监听到了数据变化了。
  2. compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
  3. watcher订阅者是observe和compile之间通信的桥梁,主要做的事情是:
  • 在自身实例化时往订阅器中(Dep)中添加自己
  • 自身有一个update方法
  • 待属性变动dep.notice()时,能够调用自身的update方法,并处罚compile中绑定的回调,则功成身退
  1. MVVM作为数据绑定的入口,整合observe、compile和watcher三者,通过observe来监听自己的model数据变化,通过compile来解析编译模板指令,最终利用watcher搭起observe和compile之间的通信桥梁,达到数据变化->更新视图; 视图交互变化(input)- 数据model变更的双向绑定效果。

2. 描述下vue从初始化页面-修改数据-刷新页面UI的过程?

当Vue进入初始化阶段时,一方面Vue会遍历data中的属性,并用Object.defineProperty将其转化成getter/setter的形式,实现数据劫持(暂不谈Vue3.0的proxy); 另一方面,Vue的指令编译器compiler对元素节点的各个指令进行解析,初始化视图,并订阅watcher来更新视图,此时watcher会将自己添加到消息订阅器Dep中国,此时初始化完毕。 当数据变化时,触发Observer中setter方法,立即调用Dep.notify(), dep这个数组开始遍历所有的订阅器,并调用其update方法,Vue内部再通过diff算法,patch响应的更新完成对订阅者视图的改变。

3. 虚拟DOM实现原理

4. 既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行Diff检测差异?

考点: Vue的变化侦原理 前置知识: 依赖收集、虚拟DOM、响应式系统 现代前端框架有两种方式侦测变化,一种pull,一种是push

pull: 其代表是React,我们可以回忆一下React是如何侦测到变化的,我们通常会用setState显示更新,然后React会进行一层层的Virtual Diff操作找出差异,然后Patch到DOM上,React从一开始就不知道到底是哪发生了变化,只知道[有变化了], 然后再进行暴力的Diff操作查找[哪发生了变化了]。 另外一个代表就是Angular的脏检查操作。

push: Vue的响应式系统则是push的代表,当Vue程序初始化的时候就会对数据data进行依赖的收集,一旦数据发生了变化,响应式系统就会立刻得到通知。 因此Vue是一开始就知道是[在哪发生变化了]。 但是这又会产生一个问题,如果你熟悉Vue的响应式就会知道,通常一个绑定数据就需要一个Watcher,一旦我们绑定的细粒度过高就会产生大量的Watcher,这会带来内存以及依赖追踪的开销,而细粒度过低则会导致无法精准侦测变化,因此Vue的设计是选择中等细粒度的方案,在组件级别进行push侦测的方式,也就是那套响应式系统,通常我们会第一时间侦测到发生变化的组件, 然后在组件内部进行Virtual Dom Diff获取更加具体的差异,而Virtual Dom Diff则是pull操作,Vue是push+pull结合的方式进行变化侦测的。

5. Vue中key值的作用

当Vue用v-for正在更新已渲染过的元素列表时,它默认用就地复用策略。 如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是简单的复用此处每个元素,并且确保它在特定所印象显示已被渲染过的每个元素。 key的作用主要就是为了高效的更新虚拟DOM。结合源码分析具体就是: 在patch过程中会执行patch vnode, vnode会执行updateChildren方法,它会更新所有的两个新旧子元素,在这个过程中会通过key精准判断当前循环中的两个节点是否是同一个节点,如果没有加key就会认为永远是相同节点,就会强制去更新,没有办法避免频繁更新元素,性能就会很差。 结论:

  1. key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,减少DOM操作量,提高性能。
  2. 另外,若不设置key,还可以在列表更新时引发一些隐蔽的bug。 (比方在系列排行榜中,切换tab时,替换v-for中list项,当不设置key时,会出现诸如后一个tab的list中的前三项渲染的是上一个tablist中的三项)
  3. vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。

6. 组件的生命周期

    1. beforeCreate 和 created
    1. beforeMount 和 mounted
    1. beforeUpdate 和 updated
    1. beforeDestory 和 destoryed
    1. activated 和 deactivated 生命周期详解

7. Vue组件间的通信方式有哪些

8. watch、methods和computed的区别

  • watch为了监听某个响应数据的变化。computed是自动监听依赖值的变化,从而动态返回内容。主要目的是简化模板内复杂运算。 所有区别来源用法,稚死需要动态值,就用computed; 需要知道值后执行业务逻辑采用watch
  • methosd是一个方法,它可以接受参数,而computed不能,computed是可以缓存的,methods不会。 computed可以一来其他computed,甚至是其它组件的data。

9. vue中怎么重置data

使用Object.assign(), vm.data可以获取当前状态下的data,vm.data可以获取当前状态下的data, vm.options.data.call(this)可以获取组件初始状态下的data


export default {
    props: {
        P: Object
    },
    data () {
        return {
            A: {
                a: this.methodA
            },
            B: this.P
        };
    },
    methods: {
        resetData () { // 更新时调用
            Object.assign(this.$data, this.$options.data.call(this)); // 有问题!!!
        },
        methodA () {
            // do sth.
        },
        methodB () { // 通过用户操作调用
            this.A.a && this.A.a(); 
        }
    }
}

data()中若使用了this来访问props或methods,在重置data时,注意this.data时,注意this.options.data()的this指向,最好使用this.options.data.call(this)[his.options.data.call(this)。 [his.options.data()重置组件data时,data()里用this获取的props或method都为undefined](blog.csdn.net/mocoe/artic…)

10. 组件中写name选项有什么作用

    1. 项目使用keep-alive时,可搭配组件name进行缓存过滤
    1. DOM做递归组件时需要调用自身的nane
    1. vue-devtools调试工具里显示的组件名称是由vue中组件的name决定的

11. vue-router有哪些钩子函数

12. route和router的区别是什么

route是“路由信息对象”,包括path,params, hash, query,fullpath,mathced, name等路由信息参数 router是“路由实例对象”,包括了路由的跳转方法(push,replace),钩子函数等

13. 说一下Vue和React的认识,做一个简单的对比

1. 监听数据变化的实现原理不同 参考【4. 既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行Diff检测差异?】

  • 组件级别进行push侦测的方式,也就是那套响应式系统,通常我们会第一时间侦测到发生变化的组件, 然后在组件内部进行Virtual Dom Diff获取更加具体的差异,而Virtual Dom Diff则是pull操作,Vue是push+pull结合的方式进行变化侦测的。
  • React默认是通过比较引用的方式进行,如果不优化,每当应用的状态被改变时,全部子组件都会重新进行渲染,可能会导致大量不必要的VDOM的重新渲染。
  • Vue不需要特别的优化就能达到很好的性能,而对于React而言,需要通过PureComponent/ShouldComponentUpdate这个生命周期方法来进行控制。 如果你的应用中,交互复杂,需要处理大量的UI变化,那么使用Virtual DOM是一个好主意。如果你更新元素并不频繁,那么Virtual DOM并不一定适用,性能很可能还不如直接操作DOM。 为什么React不精确监听数据变化呢?这是因为Vue和React设计理念上的区别,Vue使用的是可变的数据,而React更强调数据的不可变。 2. 数据流的不同 2.png
  • Vue中默认支持双向绑定,组件和DOM之间可以通过v-model双向绑定。但是父子组件之间,props在2.x版本是单项数据流。
  • React一直提倡单向数据流,他称之为onChange/setState()模式

不过由于我们一般都会用vuex以及Redux等单项数据流的状态管理框架,因此很多时候我们感受不到这一点区别了。

3. 模板渲染方式不同 在表层上,模板的语法不同

  • React是通过JSX渲染模板
  • 而Vue是通过一种扩展的HTML语法进行渲染 在深层上,模板的原理不同,这才是他们本质区别:
  • React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JS语法实现的
  • Vue是在和组件JS代码分离的单独模块中,通过指令来实现的,比如条件语句就需要v-if来实现。

对于这一点,我个人比较喜欢React的做法,因为他更加纯粹更加原生,而Vue的做法显得有些独特,会把HTML弄的很乱。举个栗子,说明React的好处,react中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用,但是在Vue中,由于模板中使用的数据都必须挂载this上进行一次中转,所以我们import一个组件玩了之后,还需要在components中再声明一下,这样显然是很奇怪但是又不得不这样的做法。

14 Vuex有哪几种属性

有五种,分别是state, getter, mutation, action, module

15. vue首屏加载优化

1. 把不常改变的库放到index.html中,通过CDN引入

<link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.css" rel="stylesheet"/>
<link href="https://cdn.bootcss.com/element-ui/2.4.0/theme-chalk/index.css" rel="stylesheet"/>

<body>
    <div id="APP"></div>
    
    <script src="https"//cdn.bootcss.com/vue/2.5.16/vue.mim.js"></script>
    <script src="https"//cdn.bootcss.com/vue-router/3.0.1/vue-router.mim.js"></script>
    <script src="https"//cdn.bootcss.com/element-ui/2.4.0/index.js"></script>
</body>

然后找到build/webapck.base.conf.js文件,在module.export={}中添加以下代码

externals: { 
    'vue': 'Vue',
    'vue-router': 'VueRouter',
    'element-ui': 'ELEMENT',
},

这样webpack就不会把vue.js, vue-router,element-ui库打包了 ,声明一下,我把main.js中对element的引入删掉了,不然我会发现打包后的app.css还是会把element的css打包进去,删掉后就没有了。 然后你打包就会发现vendor文件小了很多~

2. vue 路由的懒加载 import 或者 require 懒加载。你打包就会发现,多了很多 1.xxxxx.js;2.xxxxx.js 等等,而 vendor.xxx.js 没了,剩下 app.js 和 manifest.js,而且 app.js 还很小,我这里是 100k 多一点。

3. 不生成 map 文件 找到 config/index.js,修改为 productionSourceMap: false

4. vue 组件尽量不要全局引入 5. 使用更轻量级的工具库 6. 开启gzip压缩 这个优化是两方面的,前端将文件打包成.gz文件,然后通过nginx的配置,让浏览器直接解析.gz文件 7. 首页单独做服务端渲染

16 Vue3.0了解

尤大

17 vue-cli做了哪些事

首先需要知道vue-cli是什么?它是基于vue.js进行快速开发的完整系统,也可以理解成很多npm包的集合。 其次,vue-cli完成的功能有哪些?

  • vue文件 --> js文件
  • ES6t语法 --> ES5语法
  • Sass,Less, Stylus --> css
  • 对jpg,png, font等静态资源的处理
  • 热更新
  • 定义环境变量,区分dev和production模式
  • …… 如果开发者需要补充或默认设置,需要在package.json同级下新建一个vue.config.js文件

18.Proxy与Object.defineProperty的优劣对比

Proxy的优势如下

  • 可以直接监听对象,而非属性
  • 可以直接监听数组的变化
  • proxy有多达13种拦截方法,不限于apply, ownKeys,deleteProperty, has等等是Object.dedfineProperty不具备的
  • Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
  • Proxy作为新标准将受到浏览器厂商终点持续的性能优化,也就是传说中的新标准的性能红利

Object.defineProperty的优势如下

19. 理解Vue的响应式系统

1.png

上述图片解读

  • 任何一个Vue Component都有一个与之对应的Watcher实例。
  • Vue的data上的属性会被添加getter和setter属性
  • 当Vue Component render函数被执行的时候,data上会被触碰(touch),即被读,getter方法会被调用,此时Vue会记录此Vue Component所依赖的所有data(这一过程被称为依赖收集)
  • data被改动时(主要时用户操作),即被写,setter方法会被调用,此时Vue会去通知所有依赖于此的data的组件去调用他们的render函数进行更新。

20 虚拟DOM的优劣如何

优点:

  • 保证性能下限:虚拟DOM可以经过diff找出最小差异,然后批量进行patch,这种操作虽然比不上手动优化,但是比起粗暴的DOM操作性能能要好很多,因此虚拟DOM可以保证性能下限。
  • 无需手动操作DOM: 虚拟DOM的diff和patch都是在一次更新中自动进行的,我们无需手动操作DOM,极大提高了开发效率。
  • 跨平台: 虚拟DOM本质上时javaScript对象,而DOM与平台强相关,相比之下虚拟DOM可以进行更方便地跨平台操作,例如服务器渲染,移动端开发等等

缺点:

  • 无法进行极致优化: 在一些性能要求极高地应用中,虚拟DOM无法进行针对性地极致优化,比如VScode采用直接手动操作DOM地方式进行极端地性能优化

21 虚拟DOM实现原理

  • 虚拟DOM本质上时JavaScript对象,是对真实DOM地抽象
  • 状态变更时,记录新树和旧树地差异
  • 最后把差异更新到真正地DOM中 详情见

22. 既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行diff检测差异?

考点: Vue的变化侦测原理

前置知识: 依赖收集、虚拟DOM、响应式系统

现代前端框架尤两种方式侦测变化,一种是pull,一种是push

pull: 其代表是React,我们可以回忆一下React是如何侦测到变化的,我们通常会用到setState API显示更新, 然后React会进行一层层的Virtual Dom Diff操作找出差异,然后Patch到DOM上,React从一开始旧不知道到底哪发生了变化,只知道【有变化了】,然后再进行比较暴力的Diff操作查找【哪发生变化了】,另一个代表就是Angular的脏查操作。

push: Vue的响应式系统则是push的代表,当Vue程序初始化的时候就会对数据data进行依赖的收集,一旦数据发生变化,响应式系统就会立刻得知,因此Vue是一开始旧知道是【哪发生变化了】,但是这又会产生一个问题,如果你熟悉Vue的响应式旧知道,同池一个绑定一个数据旧需要一个Watcher, 一旦我们的绑定的细粒度过高就会产生大量的Watcher, 这会带来内存以及依赖追踪的开销,而细粒度过低会无法精准侦测变化。 因此Vue的设计选择中等细粒度方案,在组件级别进行push侦测方式,也就是那套响应式系统; 通常我们会第一时间侦测到发生变化的组件, 然后在组件内部进行Virtual Dom Diff获取更加具体的差异, 而Virtual Dom Diff则是pull操作, Vue是push+ pull结合的方式进行变化侦测的

组件级别和组件内部的理解:Vue 对于响应式属性的更新,只会精确更新依赖收集的当前组件,而不会递归的去更新子组件,这也是它性能强大的原因之一。

<template>
   <div>
      {{ msg }}
      <ChildComponent />
   </div>
</template

我们在触发 this.msg = 'Hello, Changed~'的时候,会触发组件的更新,视图的重新渲染。

但是 这个组件其实是不会重新渲染的,这是 Vue 刻意而为之的。 为什么说 Vue 的响应式更新精确到组件级别?(原理深度解析)

23 Vue为什么没有类似于React中shouldComponentUpdate的生命周期?

考点: Vue的变化侦测原理

前置知识: 依赖收集、虚拟DOM、响应式系统

根本原因是Vue与React的变化侦测方式有所不同

React是pull的方式侦测变化,当React知道发生变化后,会使用Virtual Dom Diff进行差异检测,但是很多组 件实际上是肯定不会发生变化的,这个时候需要用shouldComponentUpdate进行手动操作来减少diff,从 而提高程序整体的性能.

Vue是pull+push的方式侦测变化的,在一开始就知道那个组件发生了变化,因此在push的阶段并不需要手 动控制diff,而组件内部采用的diff方式实际上是可以引入类似于shouldComponentUpdate相关生命周期 的,但是通常合理大小的组件不会有过量的diff,手动优化的价值有限,因此目前Vue并没有考虑引入 shouldComponentUpdate这种手动优化的生命周期.

24. Vue中的key到底有什么用?

key 是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确、更快速

diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的 key 与旧节点进行 比对,然后超出差异

diff程可以概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比 较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较 的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完 了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.

  • 准确: 如果不加 key ,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下 来,会产生一系列的bug

  • 快速: key的唯一性可以被Map数据结构充分利用,相比于遍历查找的时间复杂度O(n),Map的时间复 杂度仅仅为O(1)