跳到主要内容

一、VUE基础

001 - 对vue设计原则的理解

渐进式JavaScript框架

  • 与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。 另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue也完全能够为复杂的单页应用提供驱动

易用性

  • vue提供数据响应式、声明式模板语法和基于配置的组件系统等核心特性。这些使我们只需要关注应用的核心业务即可,只要会写js、html和css就能轻松编写vue应用。

灵活性

  • 渐进式框架的最大优点就是灵活性,如果应用足够小,我们可能仅需要vue核心特性即可完成功能;随着应用规模不断扩大,我们才可能逐渐引入路由、状态管理、vue-cli等库和工具,不管是应用体积还是学习难度都是一个逐渐增加的平和曲线。

高效性

  • 超快的虚拟DOM和diff算法使我们的应用拥有最佳的性能表现。追求高效的过程还在继续,vue3中引入Proxy对数据响应式改进以及编译器中对于静态内容编译的改进都会让vue更加高效。

002 - vue的基本原理

当一个Vue实例创建时,Vue会遍历data中的属性,用 Object.defineProperty(vue3.0使用proxy )将每一个属性身上绑定一个 getter和setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

003 - 双向数据绑定的原理

Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤

  • M(model--data)

    • 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
  • V(view)

    • compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
  • VM(vue)

    • Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退
  • MVVM

    • MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令, 最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果

004 - 使用 Object.defineProperty() 来进行数据劫持有什么缺点?

在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。

在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。

005 - MVVM、MVC、MVP的区别

  • MVC、MVP 和 MVVM 是三种常见的软件架构设计模式,主要通过分离关注点的方式来组织代码结构,优化开发效率。在开发单页面应用时,往往一个路由页面对应了一个脚本文件,所有的页面逻辑都在一个脚本文件里。页面的渲染、数据的获取,对用户事件的响应所有的应用逻辑都混合在一起,这样在开发简单项目时,可能看不出什么问题,如果项目变得复杂,那么整个文件就会变得冗长、混乱,这样对项目开发和后期的项目维护是非常不利的。

    • MVVM

      • VM作为更新桥梁

        • MVVM构成

          • Model代表数据模型,数据和业务逻辑都在Model层中定义
          • View代表UI视图,负责数据的展示
          • ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作
        • Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步,这种模式实现了 Model和View的数据自动同步,因此开发者只需要专注于数据的维护操作即可,而不需要自己操作DOM

    • MVC

      • M驱动V(数据驱动视图)

        • MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新
    • MVP

      • MVP 模式与 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中使用观察者模式,来实现当 Model 层数据发生变化的时候,通知 View 层的更新。这样 View 层和 Model 层耦合在一起,当项目逻辑变得复杂的时候,可能会造成代码的混乱,并且可能会对代码的复用性造成一些问题。MVP 的模式通过使用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的Controller 只知道 Model 的接口,因此它没有办法控制 View 层的更新,MVP 模式中,View 层的接口暴露给了 Presenter 因此可以在 Presenter 中将 Model 的变化和 View 的变化绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还包含了其他的响应逻辑

006 - MVVM的优缺点?

  • 优点

    • 分离视图(View)和模型(Model),降低代码耦合,提⾼视图或者逻辑的重⽤性
    • 提⾼可测试性: ViewModel的存在可以帮助开发者更好地编写测试代码
    • ⾃动更新dom: 利⽤双向绑定,数据更新后视图⾃动更新,让开发者从繁琐的⼿动dom中解放
  • 缺点

    • Bug很难被调试: 因为使⽤双向绑定的模式,当你看到界⾯异常了,有可能是你View的代码有Bug,也可能是Model的代码有问题。数据绑定使得⼀个位置的Bug被快速传递到别的位置,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的
    • ⼀个⼤的模块中model也会很⼤,虽然使⽤⽅便了也很容易保证了数据的⼀致性,但是当⻓期持有,不释放内存就造成了花费更多的内存
    • 对于⼤型的图形应⽤程序,视图状态较多,ViewModel的构建和维护的成本都会⽐较⾼

007 - Computed 和 Watch 的区别

  • Computed

    • 它支持缓存,只有依赖的数据发生了变化,才会重新计算
    • 不支持异步,当Computed中有异步操作时,无法监听数据的变化
    • computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的
    • 如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed
    • 如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法
  • Watch

    • 它不支持缓存,数据变化时,它就会触发相应的操作

    • 支持异步监听

    • 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值

    • 当一个属性发生变化时,就需要执行相应的操作

    • 监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数

      • immediate:组件加载立即触发回调函数
      • deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化

总结

  • computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
  • watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。

运用场景

  • 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
  • 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ) ,限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

008 - Computed 和 Methods 的区别

computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相关依赖发生改变时才会重新求值

method 调用总会执行该函数

009 - 对Vue组件化的理解

  • 组件是独立和可复用的代码组织单元。组件系统是Vue核心特性之一,它使开发者使用小型、独立和通常可复用的组件构建大型应用
  • 组件化开发能大幅提高应用开发效率、测试性、复用性等
  • 遵循单向数据流的原则。

010 - slot是什么?有什么作用?原理是什么?

slot又名插槽,是Vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot又分三类,默认插槽,具名插槽和作用域插槽

  • 默认插槽

    • 又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽
  • 具名插槽

    • 带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽
  • 作用域插槽

    • 默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽
  • 原理

    • 当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.$slot中,默认插槽为vm.$slot.default,具名插槽为vm.$slot.xxx,xxx 为插槽名,当组件执行渲染时候,遇到slot标签,使用$slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽

011 - 过滤器的作用,如何实现一个过滤器

  • 作用

    • 根据过滤器的名称,过滤器是用来过滤数据的,在Vue中使用filters来过滤数据,filters不会修改数据,而是过滤数据,改变用户看到的输出(计算属性 computed ,方法 methods 都是通过修改数据来处理数据格式的输出显示)。
  • 使用场景

    • 需要格式化数据的情况,比如需要处理时间、价格等数据格式的输出 / 显示。
    • 比如后端返回一个 年月日的日期字符串,前端需要展示为 多少天前 的数据格式,此时就可以用fliters过滤器来处理数据。
  • 实现过滤器

    • 过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在插值表达式 {{ }} 和 v-bind 表达式 中,然后放在操作符“ | ”后面进行指示。

012 - 对keep-alive的理解

  • keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM;设置了 keep-alive 缓存的组件,会多出两个生命周期钩子activated 和deactivated

013 - 如何保存页面的当前的状态

  • 应用场景

    • 项目中常常会有许多列表页,当我们点击进详情后再返回列表页,页面会刷新,导致列表返回到第一条,用户体验并不是很好.这时候我们就需要用到状态保持,让页面保持之前的状态,当用户返回的时候不刷新页面
  • 既然是要保持页面的状态(其实也就是组件的状态),那么会出现以下两种情况

    • 前组件会被卸载

      • 将状态存储在LocalStorage / SessionStorage

        • 只需要在组件即将被销毁的生命周期中在 LocalStorage / SessionStorage 中把当前组件的 state 通过 JSON.stringify() 储存下来就可以了。在这里面需要注意的是组件更新状态的时机

          比如从 B 组件跳转到 A 组件的时候,A 组件需要根据B组件更新自身的状态。但是如果从别的组件跳转到 A 组件的时候,实际上是希望 A组件重新渲染的,也就是不要从 Storage 中读取信息。所以需要在 Storage 中的状态加入一个 flag 属性,用来控制 A 组件是否读取 Storage 中的状态

          • 优点

            • 兼容性好,不需要额外库或工具。
            • 简单快捷,基本可以满足大部分需求
          • 缺点

            • 状态通过 JSON 方法储存(相当于深拷贝),如果状态中有特殊情况(比如 Date 对象、Regexp 对象等)的时候会得到字符串而不是原来的值。(具体参考用 JSON 深拷贝的缺点)
            • 如果 B 组件后退或者下一页跳转并不是前组件,那么 flag 判断会失效,导致从其他页面进入 A 组件页面时 A 组件会重新读取 Storage,会造成很奇怪的现象
      • 路由传值

        • 通过 vue-router 的 Link 组件的 prop —— to 可以实现路由间传递参数的效果。在这里需要用到 state 参数,在 B 组件中通过 history.location.state 就可以拿到 state 值,保存它。返回 A 组件时再次携带 state 达到路由状态保持的效果。

          • 优点

            • 简单快捷,不会污染 LocalStorage / SessionStorage。
            • 可以传递 Date、RegExp 等特殊对象(不用担心 JSON.stringify / parse 的不足)
          • 缺点

            • 如果 A 组件可以跳转至多个组件,那么在每一个跳转组件内都要写相同的逻辑
    • 前组件不会被卸载

      • 要切换的组件作为子组件全屏渲染,父组件中正常储存页面状态

        • 优点

          • 代码量少
          • 不需要考虑状态传递过程中的错误
        • 缺点

          • 增加 A 组件维护成本
          • 需要传入额外的 prop 到 B 组件
          • 无法利用路由定位页面
      • 用keep-alive来缓存页面

        • 在Vue中,还可以是用keep-alive来缓存页面,当组件在keep-alive内被切换时组件的activated、deactivated这两个生命周期钩子函数会被执行,被包裹在keep-alive中的组件的状态将会被保留

014 - 常见的事件修饰符及其作用

  • .stop:等同于 JavaScript 中的 event.stopPropagation() ,防止事件冒泡;
  • .prevent :等同于 JavaScript 中的 event.preventDefault() ,防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
  • .capture :与事件冒泡的方向相反,事件捕获由外到内;
  • .self :只会触发自己范围内的事件,不包含子元素;
  • .once :只会触发一次。
  • .passive:事件的默认行为立即执行,无需等待事件回调执行完毕。

015 - v-if、v-show、v-html 的原理

  • v-if会调用addIfCondition方法,生成vnode的时候会忽略对应节点,render的时候就不会渲染,v-if指令会根据表达式重建或销毁元素、组件以及它们所绑定的事件;
  • v-show会生成vnode,render的时候也会渲染成真实节点,只是在render过程中会在节点的属性中修改show属性值,也就是常说的display,v-show 指令只是简单地设置 css 属性;
  • v-html会先移除节点下的所有节点,调用html方法,通过addProp添加innerHTML属性,归根结底还是设置innerHTML为v-html的值。

016 - v-if和v-show的区别

  • 手段

    • v-if是动态的向DOM树内添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显隐
  • 编译过程

    • v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换
  • 编译条件

    • v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译; v-show是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留
  • 性能消耗

    • v-if有更高的切换消耗;v-show有更高的初始渲染消耗
  • 使用场景

    • v-if适合不大可能改变;v-show适合频繁切换

017 - v-if和v-for哪个优先级更高?如果同时出现,应如何优化?

vue2.0版本中v-for优先于v-if被解析,如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能。要避免出现这种情况,则在外层嵌套template,在这一层进行v-if判断,然后在内部进行v-for循环。如果条件出现在循环内部,可通过计算属性提前过滤掉那些不需要显示的项

3.x 版本中 v-if 总是优先于 v-for 生效

018 - v-model的实现原理

vue中v-model可以实现数据的双向绑定,但是为什么这个指令就可以实现数据的双向绑定呢?其实v-model是vue的一个语法糖。即利用v-model绑定数据后,既绑定了数据,又添加了一个input事件监听。

实现原理

  • v-bind绑定响应数据

  • 触发input事件并传递数据

019 - v-model 是如何实现的,语法糖实际是什么?

  • 作用在表单元素上

    • 动态绑定了 input 的 value 指向了 message 变量,并且在触发 input 事件的时候去动态把 message设置为目标值:
  • 作用在组件上

    • 在父组件中,v-model =“message”默认会将message传递给子组件,子组件用props:["message"] 接收,并为自身绑定一个input事件,这个input事件会接收子组件传过来的值,进而修改自己的message的值;在子组件内,用props:["message"] 接收父组件传递过来的值,利用事件来触发父组件的事件,并将改变的值传递给父组件

      • 父组件内
      • 子组件内

020 - v-model 可以被用在自定义组件上吗?如果可以,如何使用?

  • 父组件

      • 相当于

        • 父组件将searchText变量传入custom-input 组件,使用的 prop 名为value
        • custom-input 组件向父组件传出名为input的事件,父组件将接收到的值赋值给searchText
  • 子组件

021 - data为什么是一个函数而不是对象

Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致它们共用一个data对象,那么状态变更将会影响所有组件实例,这是不合理的;采用函数形式定义,在initData时会将其作为工厂函数返回全新data对象,有效规避多实例之间状态污染问题。而在Vue根实例创建过程中则不存在该限制,也是因为根实例只能有一个,不需要担心这种情况

022 - $nextTick 原理及作用

原理

  • Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种应用。nextTick 的本质是为了利用 JavaScript 的异步回调任务队列来实现 Vue 框架中自己的异步回调队列。

作用

  • Vue采用了数据驱动视图的思想,但是在一些情况下,仍然需要操作DOM。有时候,可能遇到这样的情况,DOM1的数据发生了变化,而DOM2需要从DOM1中获取数据, 那这时就会发现DOM2的视图并没有更新,这时就需要用到了nextTick了。由于Vue的DOM操作是异步的,所以,在上面的情况中,就要将DOM2获取数据的操作写在$nextTick中

      -
    • 在以下情况下,会用到nextTick

      • ● 在数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的DOM结构的时候,这个操作就需要方法在nextTick()的回调函数中。
      • ● 在vue生命周期中,如果在created()钩子进行DOM操作,也一定要放在nextTick()的回调函数中。因为在created() 钩子函数中,页面的DOM还未渲染,这时候也没办法操作DOM,所以,此时如果想要操作DOM,必须将操作的代码放在nextTick()的回调函数中。

023 - Vue 中给 data 中的对象属性添加一个新的属性时会发生什么?如何解决?

  • 问题

    • 点击 button 会发现,obj.b 已经成功添加,但是视图并未刷新
  • 原因

    • 这是因为在Vue实例创建时,obj.b并未声明,因此就没有被Vue转换为响应式的属性,也就是没有为该属性设置getter和setter,自然就不会触发视图的更新
  • 解决方法

    • 使用Vue的全局 api $set(),$set()方法相当于手动的去把obj.b处理成一个响应式的属性,此时视图也会跟着改变了

024 - Vue中封装的数组方法有哪些,其如何实现页面更新

在Vue中,对响应式处理利用的是Object.defineProperty对数据进行拦截,而这个方法并不能监听到数组内部变化,数组长度变化,数组的截取变化等,所以需要对这些操作进行hack,让Vue能监听到其中的变化

Vue中封装的数组方法

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

如何实现页面更新

简单来说就是,重写了数组中的那些原生方法,首先获取到这个数组的ob,也就是它的Observer对象,如果有新的值,就调用observeArray继续对新的值观察变化(也就是通过targetproto == arrayMethods来改变了数组实例的型),然后手动调用notify,通知渲染watcher,执行update。

025 - vue如何监听对象或者数组某个属性的变化

  • 先判断属性是否原本就存在,若存在,则利用自身的setter和getter实现数据的实时更新

  • 当在项目中直接设置数组的某一项的值,或者直接设置对象的某个属性值,这个时候,你会发现页面并没有更新。这是因为Object.defineProperty()限制,监听不到变化。

    • 解决方式

      • 数组

        • this.$set(你要改变的数组,你要改变的位置,你要改成什么value)
        • vue源码里缓存了array的原型链,然后重写了这几个方法,触发这几个方法的时候会observer数据,意思是使用这些方法不用再进行额外的操作,视图自动进行更新。
      • 对象

        • this.$set(你要改变的对象,你要改变的key,你要改成什么value)
      • vm.$set 的实现原理

        • ● 如果目标是数组,直接使用数组的 splice 方法触发响应式;
        • ● 如果目标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法)

026 - Vue 单页应用与多页应用的区别

  • 概念

    • SPA单页面应用(SinglePage Web Application),指只有一个主页面的应用,一开始只需要加载一次js、css等相关资源。所有内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换相关组件,仅仅刷新局部资源
    • MPA多页面应用 (MultiPage Application),指有多个独立页面的应用,每个页面必须重复加载js、css等相关资源。多页应用跳转,需要整页资源刷新
  • 区别

027 - 对 SPA 单页面的理解,它的优缺点分别是什么?

  • 概念

    • SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
  • 优点

    • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
    • 基于上面一点,SPA 相对对服务器压力小;
    • 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
  • 缺点

    • 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
    • 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
    • SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

028 - Vue模版编译原理

vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所以需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析parse,优化optimize,生成generate,最终生成可执行函数render。

  • 解析阶段

    • 使用大量的正则表达式对template字符串进行解析,将标签、指令、属性等转化为抽象语法树AST
  • 优化阶段

    • 遍历AST,找到其中的一些静态节点并进行标记,方便在页面重新渲染的时候进行diff比较时,直接跳过这一些静态节点,优化runtime的性能
  • 生成阶段

    • 将最终的AST转化为render函数字符串,生成render函数,浏览器执行render函数,在页面中渲染出对应的HTML元素

029 - Vue template 到 render 的过程

  • vue 在模版编译版本的码中会执行 compileToFunctions 将template转化为render函数

    • CompileToFunctions中的主要逻辑如下

      • 调用parse方法将template转化为ast(抽象语法树)

        • ● parse的目标:把template转换为AST树,它是一种用 JavaScript对象的形式来描述整个模板。 ● 解析过程:利用正则表达式顺序解析模板,当解析到开始标签、闭合标签、文本的时候都会分别执行对应的 回调函数,来达到构造AST树的目的。 AST元素节点总共三种类型:type为1表示普通元素、2为表达式、3为纯文本
      • 对静态节点做优化

        • 这个过程主要分析出哪些是静态节点,给其打一个标记,为后续更新渲染可以直接跳过静态节点做优化,深度遍历AST,查看每个子树的节点元素是否为静态节点或者静态节点根。如果为静态节点,他们生成的DOM永远不会改变,这对运行时模板更新起到了极大的优化作用
      • 生成代码

        • generate将ast抽象语法树编译成 render字符串并将静态部分放到 staticRenderFns 中,最后通过 new Function( render) 生成render函数

030 - 对SSR的理解

  • 概念

    • SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端
  • 优势

    • 更好的SEO
    • 首屏加载速度更快
  • 劣势

    • 开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子;
    • 当需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境;
    • 更多的服务端负载。

031 - Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?

  • 不会立即同步执行重新渲染。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环tick中,Vue 刷新队列并执行实际(已去重的)工作。

032 - 什么是 mixin ?

  • Mixin 使我们能够为 Vue 组件编写可重用的功能。
  • 如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。
  • 然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优先于组件自已的 hook。

033 - 简述 mixin、extends 的覆盖逻辑

  • mixin 和 extends

    • mixin 和 extends均是用于合并、拓展组件的,两者均通过 mergeOptions 方法实现合并

      • ● mixins 接收一个混入对象的数组,其中混入对象可以像正常的实例对象一样包含实例选项,这些选项会被合并到最终的选项中。Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
      • ● extends 主要是为了便于扩展单文件组件,接收一个对象或构造函数。
  • mixin、extends 的覆盖逻辑

  • mergeOptions 的执行过程

    • 规范化选项

    • 对未合并的选项,进行判断

    • 合并处理。根据一个通用 Vue 实例所包含的选项进行分类逐一判断合并,如 props、data、 methods、watch、computed、生命周期等,将合并结果存储在新定义的 options 对象里

    • 返回合并结果 options

034 - mixin 和 mixins 区别

  • mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。虽然文档不建议在应用中直接使用 mixin,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的 ajax 或者一些工具函数等等。

  • mixins 应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行(watch也是),并且在遇到同名选项的时候也会有选择性的进行合并。

035 - 描述下Vue自定义指令

  • 为什么要使用自定义指令

    • 在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令,一般需要对DOM元素进行底层操作时使用,尽量只用来操作 DOM展示,不修改内部的值。当使用自定义指令直接修改 value 值时绑定v-model的值也不会同步更新;如必须修改可以在自定义指令中使用keydown事件,在vue组件中使用 change事件,回调中修改vue数据
  • 自定义指令分类

    • 全局自定义指令

      • 在任意页面的任意位置都可使用的自定义指令
    • 局部自定义指令

      • 只针定义自定义指令所在的组件内的元素
  • 自定义指令定义方式

    • 函数形式

    • 对象形式

      • 配置对象中常用的3个回调: □ bind:指令与元素成功绑定时调用。 □ inserted:指令所在元素被插入页面时调用。 □ update:指令所在模板结构被重新解析时调用。
  • 钩子函数

    • bind

      • 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
    • inSerted

      • 被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。
    • update

      • 所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前调用。指令的值可能发生了改变,也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。
    • ComponentUpdate

      • 指令所在组件的 VNode及其子VNode全部更新后调用
    • unbind

      • 只调用一次,指令与元素解绑时调用
  • 钩子函数参数

    • el

      • 绑定元素
    • binding

      • 指令核心对象,描述指令全部信息属性
    • name

    • value

    • oldValue

    • expression

    • arg

    • modifers

    • vnode

      • 虚拟节点
    • oldVnode

      • 上一个虚拟节点(更新钩子函数中才有用)
  • 使用场景

    • 普通DOM元素进行底层操作的时候,可以使用自定义指令
    • 自定义指令是用来操作DOM的。尽管Vue推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的DOM操作,并且是可复用的。
  • 使用案例

    • 初级应用

      • 鼠标聚焦
      • 下拉菜单
      • 相对时间转换
      • 滚动动画
    • 高级应用

      • 自定义指令实现图片懒加载
      • 自定义指令集成第三方插件

036 - 子组件可以直接改变父组件的数据吗?

  • 子组件不可以直接改变父组件的数据。这样做主要是为了维护父子组件的单向数据流。每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果这样做了,Vue 会在浏览器的控制台中发出警告。Vue提倡单向数据流,即父级 props 的更新会流向子组件,但是反过来则不行。这是为了防止意外的改变父组件状态,使得应用的数据流变得难以理解,导致数据流混乱。如果破坏了单向数据流,当应用复杂时,debug 的成本会非常高。只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

037 - Vue是如何收集依赖的?

  • 在初始化 Vue 的每个组件时,会对组件的 data 进行初始化,就会将由普通对象变成响应式对象,在这个过程中便会进行依赖收集的相关逻辑

038 - Vue的优点

  • 轻量级框架

    • 只关注视图层,是一个构建数据的视图集合,大小只有几十 kb
  • 简单易学

    • 国人开发,中文文档,不存在语言障碍 ,易于理解和学习
  • 双向数据绑定

    • 保留了 angular 的特点,在数据操作方面更为简单
  • 组件化

    • 保留了 react 的优点,实现了 html 的封装和重用,在构建单页面应用方面有着独特的优势
  • 视图,数据,结构分离

    • 使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作
  • 虚拟DOM

    • dom 操作是非常耗费性能的,不再使用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种方式;
  • 运行速度更快

    • 相比较于 react 而言,同样是操作虚拟 dom,就性能而言, vue 存在很大的优势

039 - assets和static的区别

  • 相同点

    • assets 和 static 两个都是存放静态资源文件。项目中所需要的资源文件图片,字体图标,样式文件等都可以放在这两个文件下
  • 不同点

    • assets 中存放的静态资源文件在项目打包时,也就是运行 npm run build 时会将 assets 中放置的静态资源文件进行打包上传,所谓打包简单点可以理解为压缩体积,代码格式化。而压缩后的静态资源文件最终也都会放置在 static 文件中跟着 index.html 一同上传至服务器
    • static 中放置的静态资源文件就不会要走打包压缩格式化等流程,而是直接进入打包好的目录,直接上传至服务器。因为避免了压缩直接进行上传,在打包时会提高一定的效率,但是 static 中的资源文件由于没有进行压缩等操作,所以文件的体积也就相对于 assets 中打包后的文件提交较大点。在服务器中就会占据更大的空间。
  • 建议

    • 将项目中 template需要的样式文件js文件等都可以放置在 assets 中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如iconfoont.css 等文件可以放置在 static 中,因为这些引入的第三方文件已经经过处理,不再需要处理,直接上传。

040 - delete和Vue.delete删除数组的区别

  • delete 只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变,数组长度不变

  • Vue.delete 直接删除了数组元素,改变了数组的键值,数组长度变化。

041 - Vue的性能优化有哪些

  • 编码阶段

    • ● 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
    • ● v-if和v-for不能连用
    • ● 如果需要使用v-for给每项元素绑定事件时使用事件代理
    • ● SPA 页面采用keep-alive缓存组件
    • ● key保证唯一
    • ● 使用路由懒加载、异步组件
    • ● 防抖、节流
    • ● 第三方模块按需导入
    • ● 长列表滚动到可视区域动态加载
    • ● 图片懒加载
  • SEO优化

    • ● 预渲染(渲染到虚拟DOM上)
    • ● 服务端渲染SSR
  • 打包优化

    • ● 压缩代码
    • ● Tree Shaking/Scope Hoisting
    • ● 使用cdn加载第三方模块
    • ● 多线程打包happypack
    • ● splitChunks抽离公共文件
    • ● sourceMap优化
  • 用户体验

    • ● 骨架屏
    • ● PWA
    • ● 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

042 - 常见的Vue性能优化方法

  • 路由懒加载

  • keep-alive缓存页面

  • 使用v-show复用DOM

    • 两个模块来回切换
  • v-for 遍历避免同时使用 v-if

  • 长列表滚动到可视区域动态加载

  • 图片懒加载

    • 对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。
  • 第三方插件按需引入

    • 像element-ui这样的第三方组件库可以按需引入避免体积太大
  • SSR(服务端渲染)

  • 变量本地化

  • 子组件分隔

  • 无状态的组件标记为函数式组件

043 - vue初始化页面闪动问题

  • 闪动问题出现原因

    • 使用vue开发时,在vue初始化之前,由于div是不归vue管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于{{message}}的字样,虽然一般情况下这个时间很短暂,但是还是有必要让解决这个问题的。
  • 解决方案

    • 我们可以使用 v-cloak 指令来解决这一问题。v-cloak 指令设置样式,这些样式会在 Vue 实例编译结束时,从绑定的 HTML 元素上被移除
  • 注意

    • 在简单项目中,使用 v-cloak 指令是解决屏幕闪动的好方法。但在大型、工程化的项目中(webpack、vue-router)只有一个空的 div 元素,元素中的内容是通过路由挂载来实现的,这时我们就不需要用到 v-cloak 指令(大型项目中一般不会出现闪现现象)

044 - 脚手架目录:public + assets文件夹区别

public文件夹:一般放置一些共用的静态资源,打包上线的时候,public文件夹里面资源原封不动打包到dist文件夹里面

assets文件夹:经常放置一些静态资源(图片),assets文件夹里面资源webpack会进行打包为一个模块(js文件夹里面)