Vue相关的面试题

1、谈谈你对vue的生命周期的理解

 

vue整个生命周期实现了如下内容

initMixin(Vue)
stateMixin(Vue) // 实现$watch $set $delete
eventsMixin(Vue) // 实现$on  $once $off $emit
lifecycleMixin(Vue)
renderMixin(Vue)

 

 

vue首先执行_init,此方法在  initMixin中

// 源码部分
initLifecycle(vm)
initEvents(vm) // 事件监听器初始化
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm) // 初始化组件的各种状态
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

我们首先需要创建一个vue实例,也就是在 new Vue() 的对象过程中,首先执行了init(init是vue组件里面默认去执行的),在init的过程当中

1、首先初始化了生命周期(初始化一些vue实例上的参数)

2、初始化事件监听器(如:$on  $off)

3、初始化render函数(模板渲染函数)

4、调用beforeCreate钩子函数

5、初始化 inject 函数

6、initState,初始化组件的各种状态(重要,响应式就在这里进行处理)

7、初始化 provide 函数

8、调用created钩子函数

通过上述得知,initState是在beforeCreate 钩子之后、在created钩子之前初始化的,所以我们最早可以在created钩子里面对数据进行操作。

在created之后会判断实例vm选项中是否有挂载el模板  vm.$options.el

如果有的话,执行$mount

$mount的作用是将挂载在vm实例上的el转化为render函数

在$mount中,先判断挂载的el是否是render,如果是,则直接下一步,如果不是,则判断是否有template这个选项,如果没有,则说明挂载了一个html节点,如果有,则对template进行转化处理

也就是说优先级为:render > template > 节点   最终都会解析成为一个render函数

render (h){ // h =>  createElement 的缩写
  return h('div', {}, this.text)
}

 

render函数是发生在beforeMount和mounted之间的,这说明了 在beforeMount的时候,$el还只是我们在HTML里面写的节点,然后到mounted的时候,它才把渲染出来的内容挂载到了DOM节点上。

 

在mounted挂载完毕后,这个实例就算是走完流程了。

 

后续的钩子函数执行的过程都是需要外部条件的触发才会执行。

比如有数据的变化,会调用beforeUpdate,然后经过Virtual DOM,最后updated更新完毕。

当组件被销毁的时候,它会调用beforeDestory,以及destoryed

 

2、说说你对SPA单页面的理解,它的优缺点分别是什么?

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

优点:

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

缺点:

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

 

3、怎样理解Vue的单向数据流?

所有的prop都使得其父子prop之间形成了一个单向下行绑定:父级prop的更新会向下流动到子组件中,但是反过来则不行。

这样会防止从子组件意外改变父组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生更新时,子组件中所有的prop都将会刷新为最新的值。

这意味着你不应该在一个子组件内部改变prop。如果你这样做了,Vue会在浏览器的控制台中发出警告。

子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

有两种常见的试图改变一个prop的情形:

  • 这个prop用来传递一个初始值;这个子组件接下来希望将其作为一个本地的prop数据来使用。在这样情况下,最好定义一个本地的data属性并将这个prop用作其初始值:
props: ['initData'],
data(){
  return {
    useData: this.initData
  }
}
  • 这个prop以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个prop的值来定义一个计算属性
props: ['size'],
computed: {
  normal() {
    return this.size.trim().toLowerCase()
  }
}

 

4、Vue的父组件和子组件生命周期钩子函数执行顺序?

Vue的父组件和子组件生命周期钩子执行顺序可以归类为以下4部分:

  • 加载渲染过程

parent beforeCreate -> parent created -> parent beforeMount -> child beforeCreate -> child created -> child beforeMount > child mounted > parent mounted

  • 子组件更新过程

parent beforeUpdate -> child beforeUpdate -> child updated -> parent updated

  • 父组件更新过程

parent beforeUpdate -> parent updated

  • 销毁过程

parent beforeDestory -> child beforeDestory -> child destoryed -> parent destoryed

 

5、父组件可以监听到子组件的生命周期吗?如果可以,怎么监听?

比如有父组件Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下方式实现:

// Parent.vue
<Child @mounted="doSomething" />

// Child.vue
mounted(){
  this.$emit('mounted')
}

以上方式需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可:

// Parent.vue
<Child @hook:mounted="doSomething" />

doSomething(){
  console.log('父组件监听到 mounted 钩子函数...')
}

// Child.vue
mounted(){
  console.log('子组件触发 mounted 钩子函数...')
}

// 以上输出结果顺序为:
// 子组件触发 mounted 钩子函数...
// 父组件监听到 mounted 钩子函数...

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件也都可以监听

 

6、谈谈你对 keep-alive 的了解?

keep-alive 是Vue内置的一个组件,可以使被包含的组件保留状态,避免重新渲染,其有一下特性:

  • 一般结合路由和动态组件一起使用,用于缓存组件;
  • 提供 include 和 exclude 属性,两者都支持字符串或正则表达式,include 表示只有名称匹配的组件会被缓存,exclude表示任何名称匹配的组件都不会被缓存,其中exclude的优先级比include高
  • 对应两个钩子函数 activated 和 deactivated,当组件被激活时,触发钩子函数activated ,当组件被移除时,触发钩子函数 deactivated

 

7、使用过 Vue SSR 吗? 说说SSR?

Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出Vue组件,进行生成DOM和操作DOM。然而,也可以将同一组件渲染为服务端的HTML字符串,将它们直接发送到浏览器,最后将这些静态标记“激活”为客户端上完全可交互的应用程序。

即:SSR大致的意思就是vue在客户端将标签渲染成的整个html片段的工作在服务端完成,服务端形成的html片段直接返回给客户端这个过程就叫做服务端渲染。

 

服务端渲染SSR的优缺点如下:

(1)优点:

  • 更好的SEO:因为SPA页面的内容是通过ajax获取,而搜索引擎爬取工具并不会等待ajax异步完成后再抓取页面内容,所以在SPA中是抓取不到页面通过ajax获取到的内容;而SSR是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
  • 更快的内容到达时间(首屏加载更快):SPA会等待所有Vue编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的事件,所以首屏渲染需要一定的时间;而SSR直接由服务器渲染好页面直接返回显示。无需等待下载js文件及再去渲染,所以SSR有更快的内容到达时间;

(2)缺点:

  • 更多的开发条件限制:例如服务端渲染只支持 beforeCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序SPA不同,服务端渲染应用程序,需要处于Node.js server 运行环境;
  • 更多的服务器负载:在Node.js中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CUP资源,因此如果你预料在高流量环境下使用,请准备相应的服务器负载,并明智地采用缓存策略。

 

8、vue-router 路由模式有几种?

  • hash:使用URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
  • history:依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
  • abstract:支持所有JavaScript运行环境,如 Node.js 服务器端。如果发现没有浏览器的API,路由会强制进入这个模式。

 

9、什么是MVVM?

Model-View-ViewModel(MVVM)是一个软件架构设计模式,由微软 WPF 和 Silverlight 架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程方式。

MVVM源自于经典的 Model-View-Controller(MVC)模式,MVVM的出现促进了前端开发与后端业务逻辑的分离,极大地提高了前端开发效率,MVVM的核心是 ViewModel层,它就像是一个中转站,负责转换Model中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与Model层通过接口请求进行数据交互,起承上启下作用。

(1)View 层

View是视图层,也就是用户界面。前端主要由HTML和CSS来构建。

(2)Model 层

Model是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的api接口。

(3)ViewModel层

  ViewModel是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的Model数据进行转换处理,做二次封装,以生成符合View层使用预期的视图数据模型。

  需要注意的是VuewModel所封装的数据模型包括视图的状态和行为两部分,而Model层的数据模型是只包含状态的,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么 这些都属于视图行为(交互),视图状态和行为都封装在了ViewModel里。这样的封装使得ViewModel可以完整地描述View层。

  MVVM框架实现了双向绑定,这样ViewModel的内容会实时展现在View层,前端开发者再也不必低效又麻烦地通过操作DOM去更新视图。MVVM框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护ViewModel,更新数据视图就会自动得到相应更新。

  这样View层展现的不是Model层的数据,而是ViewModel的数据,由ViewModel负责与Model层交互,这就完全解耦了View层和Model层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。

 

通过一个Vue实例来说明MVVM的具体实现:

(1)View层

<div id="app">
  <p>{{message}}</p>
  <button @click="showMessage">click me</button>
</div>

(2)ViewModel层

var app = new Vue({
  el: '#app',
  data: { // 用于描述视图状态
    message: 'hello vue !'
  },
  methods: { // 用于描述视图行为
    showMessage(){
      let vm = this
      alert(vm.message)
    }
  },
  created(){
    let vm = this
    // ajax 获取 Model 层的数据
    ajax({
      url: '/your/server/data/api',
      success(res){
        vm.message = res
      }
    })
  }
})

(3)Model层

{
  "url": "/your/server/data/api",
  "res": {
    "success": true,
    "name": "Dylan",
    "age": 36
  }
}

 

10、Proxy 与 Object.defineProperty 优劣对比

Proxy的优势如下:

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

Object.defineproperty 的优势如下

  • 兼容性好,支持IE9,而Proxy 存在浏览器兼容性问题,而且无法用 polyfill 磨平。

 

11、你对Vue项目进行过哪些优化?

(1)代码层面的优化

  • v-if 和 v-show 区分使用场景
  • computed 和 watch 区分使用场景
  • v-for 遍历必须为 item 添加key,且避免同时使用 v-if
  • 长列表性能优化
  • 事件的销毁
  • 图片资源懒加载
  • 路由懒加载
  • 第三方插件的按需引入
  • 优化无限列表性能
  • 服务端渲染 SSR or 预渲染

(2)webpack层面的优化

  • webpack 对图片进行压缩
  • 减少ES6转为ES5的冗余代码
  • 提取公共代码
  • 模板预编译
  • 提取组件的CSS
  • 优化 SourceMap
  • 构建结果输出分析
  • Vue项目的编译优化

(3)基础的Web技术优化

  • 开启 gzip压缩
  • 浏览器缓存
  • CDN的使用
  • 使用 Chrome Performance 查找性能瓶颈

 

12、谈谈 watch 和 computed 的实现原理

watch的实现原理:

watch的分类

  • deep watch(深层次监听)
  • user watch (用户监听)
  • computed watcher(计算属性)
  • sync watcher (同步监听)

watch的实现过程:

  • watch的初始化在data初始化之后(此时的data已经通过 Object.defineProperty设置成响应式)
  • watch的key会在 Watcher 里进行值得读取,也就是立马执行get获取value(从而实现data对应的key执行getter实现对watch的依赖收集)。此时如果有 immediate 属性,那么立马执行watch对应的回调函数
  • 当data对应的key发生变化时,触发 user watch 实现watch回调函数的执行

computed运行原理:

  • computed的属性是动态挂载到vm实例上的,和普通的响应式数据再data里声明不同
  • 设置computed的getter,如果执行了computed对应的函数,由于函数会读取data属性值,因此又会触发data属性值的getter函数,在这个执行过程中就可以处理 computed 相对于 data 的依赖收集关系了。
  • 首次计算 computed 的值时,会执行 vm.computed 属性对应的 getter 函数(用户指定的 computed 函数,如果没有设置 getter ,那么将当前指定的函数赋值 computed 属性的getter)。进行上述的依赖收集。
  • 如果 computed 的属性值又依赖了其他 computed 计算属性值,那么会将当前 target 暂存到栈中,先进行其他computed 计算属性值的依赖收集,等其他计算属性依赖收集完成后,再从栈中 pop 出来,继续进行当前 computed 的依赖收集
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

  由于 this.firstName 和 this.lastName 都是响应式变量,因此会触发它们的getter,根据我们之前的分析,它们会把自身持有的 dep 添加到当前正在计算的 watcher 中,这个时候 Dep.target 就是这个 computed watcher,具体步骤如下:

  • data属性初始化 getter setter
  • computed 计算属性初始化,提供的函数将用作属性 vm.fullName 的getter
  • 当首次获取 fullName 计算属性的值时,Dep开始依赖收集
  • 在执行 message getter 方法时,如果 Dep处于依赖收集状态,则判定 firstName 和 lastName 为 fullName 的依赖,并建立依赖关系
  • 当 firstName 或 lastName 发生变化时,根据依赖关系,触发 fullName 的重新计算
  • 如果计算值没有发生变化,不会触发视图更新

通过以上的分析,我们知道计算属性本质上就是一个 computed watcher,也了解了它的创建过程和被访问触发 getter 以及依赖更新的过程,其实这是最新的计算属性的实现,之所以这么设计是因为 Vue 想确保不仅仅是计算属性依赖值发生变化,而是当计算属性最终的值发生变化才会触发渲染 watcher 重新渲染,本质上是一种优化。

 

posted @ 2019-08-20 11:38  落叶无痕~  阅读(522)  评论(0编辑  收藏  举报