Vue的面试题你必须要掌握到这个程度(下篇)

文章目录

    • 1. v-for 为什么需要绑定 key ?(0:00)
    • 2.组件的渲染与更新过程(12:20)
    • 3. 组件的 data 为什么必须是一个函数?(22:25)
    • 4. Vue 事件绑定原理(30:25)
    • 5. v-model 的原理与实现(41:45)
    • 6. v-html 的使用可能出现的问题(0:00)
    • 7. 父子组件的生命周期顺序(03:50)
    • 8. Vue 组件如何通信?(09:30)
    • 9. Vue 中相同逻辑如何抽离?(18:00)
    • 10. 为什么要异步加载组件?(23:50)
    • 11. 插槽与作用域插槽(30:10)
    • 12. 对 keep-alive 的理解(38:20)
    • 13. Vue 性能优化(43:20)
    • 14. Vue3.0 有哪些改进(54:20)
    • 15. hash 路由与 history 路由(56:00)
    • ~~16. vue-router 路由守卫~~
    • 17. Vuex 工作原理 (58:00)
    • 参考资料

1. v-for 为什么需要绑定 key ?(0:00)

理解:

  • 绑定唯一 key,避免暴力比对,可以调整顺序
  • key 默认是索引,会就地复用
  • 图解
    Vue的面试题你必须要掌握到这个程度(下篇)_第1张图片

2.组件的渲染与更新过程(12:20)

理解:

  1. 组件渲染时,会通过 Vue.extend 方法构建子组件构造函数(原型继承的方法实现)

  2. installComponentHooks 安装组件的钩子函数

    init:初始化,prepatch:预补丁、insert:插入、destroy :销毁

  3. 用 new VNode 实例化(组件的 vNode 没有 children

  4. 手动调用 $mount( ) 进行挂载

  5. 更新组件时 patchVnode 流程,其核心是 diff 算法

3. 组件的 data 为什么必须是一个函数?(22:25)

理解:

  • 保证组件的相对独立
  • 若是 data 是对象,则所有组件共享同一个对象,数据会相互感染
  • data 用函数返回一个对象,复用组件会创建多个实例
  • 根组件的 data 可以是一个对象,因为它不会被复用

原理

  • Vue.extend 中会执行 mergeOptions 方法来 合并 Vue 构造函数和子组件的 options到 vm.$options组件Vue的面试题你必须要掌握到这个程度(下篇)_第2张图片

4. Vue 事件绑定原理(30:25)

理解:
Vue 事件绑定分为两种:一是原生事件的绑定;二是组件的事件绑定。

  • 原生dom事件绑定用 addEventListener 实现(普通元素的@click 与组件的@click.native)
  • 组件自定义事件绑定采用 $on 方法实现 (组件的@click 单独处理)

原理
Vue的面试题你必须要掌握到这个程度(下篇)_第3张图片

5. v-model 的原理与实现(41:45)

理解

  • v-model 即可以作用在普通表单元素上,又可以作用在组件上,它其实是一个语法糖

  •   <input v-model="message">
      // 以上代码等价于如下代码
      <input
        v-bind:value="message"
        v-on:input="message=$event.target.value">
    
  •   let Child = {
        template: '
    ' + '' + '
    '
    , props: ['value'], methods: { updateValue(e) { this.$emit('input', e.target.value) } } } let vm = new Vue({ el: '#app', template: '
    ' + '' + '

    Message is: {{ message }}

    '
    + '
    '
    , data() { return { message: '' } }, components: { Child } }) // 以上代码等价于如下代码 let vm = new Vue({ el: '#app', template: '
    ' + '' + '

    Message is: {{ message }}

    '
    + '
    '
    , data() { return { message: '' } }, components: { Child } })

原理

  • v-model 的 directive 函数:根据 AST 元素节点的不同情况去执行不同的逻辑
    Vue的面试题你必须要掌握到这个程度(下篇)_第4张图片

6. v-html 的使用可能出现的问题(0:00)

  • 导致 xss 攻击



    用户的输入内容是不可靠的,比如输入:
    会弹出对话框

    • 会替换标签内部内容

    • 解决方法:①输入框增加验证规则②使用 Unicode 编码转移字符

  • 会替换子元素的内容

7. 父子组件的生命周期顺序(03:50)

  • 父 => 子 => 子 => 父

8. Vue 组件如何通信?(09:30)

  • 父子组件:父 => 子 使用 props ,子 => 父 使用 $on 与 $emit (简易的发布订阅模式)

  • 获取父子组件实例:$prarent 、$children (组件初始化时获取)

  • 在父组件中提供数据子组件消费:Provide 与 Inject

    1.Provide :将provide数据写到当前组件实例上
    2.Inject:遍历父组件查找 provide 数据(响应式数据)
    3.多用于写插件

  • Ref (非响应式)获取组件实例,调用属性与方法

  • event bus 事件总线实现跨组件通信 ,Vue.prototype.$bus = new Vue( )

  • Vuex 状态管理实现

9. Vue 中相同逻辑如何抽离?(18:00)

  • 实现:使用 Vue.mixin,给组件每个生命周期,注入一些公共逻辑
  • 核心是 mergeOption
  • 语法:
    Vue.mixin(
    	data() {
    		return {
    			//...
    		}
    	},
    	beforeCreate() {
    		// ...
    	}
    )
    

10. 为什么要异步加载组件?(23:50)

理解

  • 使用场景:为了减少首屏代码体积,往往会把一些非首屏的组件设计成异步组件,按需加载。路由的组件就是异步组件
  • 组件功能多时,打包结果会很大,异步加载组件可以实现文件的分割加载
  • import( url) 语法或 require 语法
  • 可以自定义 loading、resolve、reject、timeout 4 种状态

11. 插槽与作用域插槽(30:10)

1. 普通插槽

  • 创建组件 vnode 时,会将子元素的 vnode保存,初始化组件时,通过 slot 属性将子元素分类

  • 渲染子组件时,会替换 slot 属性的对应节点

  • 作用域为父组件

  • 示例代码:

    let AppLayout = {
      template: '
    ' + '
    '
    + '
    默认内容
    '
    + '
    '
    + '
    '
    } let vm = new Vue({ el: '#app', template: '
    ' + '' + '

    {{title}}

    '
    + '

    {{msg}}

    '
    + '

    {{desc}}

    '
    + '' + '
    '
    , data() { return { title: '我是标题', msg: '我是内容', desc: '其它信息' } }, components: { AppLayout } })

    父子组件生成的代码

    // 子组件生成的代码:
    with(this) {
      return _c('div',{
        staticClass:"container"
        },[
          _c('header',[_t("header")],2),
          _c('main',[_t("default",[_v("默认内容")])],2),
          _c('footer',[_t("footer")],2)
          ]
       )
    }
    
    // 父组件生成的代码:
    with(this){
      return _c('div',
        [_c('app-layout',
          [_c('h1',{attrs:{"slot":"header"},slot:"header"},
             [_v(_s(title))]),
           _c('p',[_v(_s(msg))]),
           _c('p',{attrs:{"slot":"footer"},slot:"footer"},
             [_v(_s(desc))]
             )
           ])
         ],
       1)}
    

    最终生成的DOM 如下

    
    <div>
      <div class="container">
        <header><h1>我是标题h1>header>
        <main><p>我是内容p>main>
        <footer><p>其它信息p>footer>
      div>
    div>
    

2. 作用域插槽

  • 作用域插槽解析时不会作为组件的子节点,而是解析为函数

  • 子组件渲染时,调用该函数渲染

  • 作用域为子组件

  • 示例代码:

    let Child = {
      template: '
    ' + '' + '
    '
    , data() { return { msg: 'Vue' } } } let vm = new Vue({ el: '#app', template: '
    ' + '' + '' + '' + '
    '
    , components: { Child } })

    父子组件生成的代码

    // 子组件生成的代码:
    with(this){
      return _c('div',
        {staticClass:"child"},
        [_t("default",null,
          {text:"Hello ",msg:msg}
        )],
      2)}
    
    // 父组件生成的代码:
    with(this){
      return _c('div',
        [_c('child',
          {scopedSlots:_u([
            {
              key: "default",
              fn: function(props) {
                return [
                  _c('p',[_v("Hello from parent")]),
                  _c('p',[_v(_s(props.text + props.msg))])
                ]
              }
            }])
          }
        )],
      1)
    }
    

    最终生成的DOM 如下

    
    <div>
      <div class="child">
        <p>Hello from parentp>
        <p>Hello Vuep>
      div>
    div>
    

原理
Vue的面试题你必须要掌握到这个程度(下篇)_第5张图片

12. 对 keep-alive 的理解(38:20)

一般为了组件的缓存优化而使用 组件。它有两个常用属性:include和 exclude,两个生命周期钩子函数:activated 和 deactivated。

1. 内置组件实现

  • 它有一个属性 abstract 为 true,是一个抽象组件

  • 在 created 钩子里定义了 this.cache 和 this.keys,本质上它就是去缓存已经创建过的 vnode,当缓存数超限了,会采用LRU 算法移除缓存节点

    LRU(Least Recently Used) 算法:LRU 是一种缓存淘汰机制,每次从内存中找到最久未使用的数据然后置换出来,从而存入新的数据!它的主要衡量指标是使用的时间,附加指标是使用的次数。

  • 只处理第一个子元素,所以一般和它搭配使用的有 component 动态组件或者是 router-view

2. 组件渲染

  • 首次渲染,initComponent 函数缓存 vnode 创建生成的 DOM 节点,除建立缓存外,与普通组件渲染无差别

  • 缓存渲染

    • 若包裹的第一个组件 vnode 命中缓存,则直接返回缓存中的 vnode.componentInstance
    • 接着又会执行 patch 过程,再次执行到 createComponent 方法
    • createComponent 里会执行reactivateComponent 方法,过执行 insert(parentElm, vnode.elm, refElm) 就把缓存的 DOM 对象直接插入到目标元素中
    • 命中缓存则不会在执行组件的 created、mounted 等钩子函数

问题:对于组件 vnode 而言,是没有 children 的,那么对于 组件而言,如何更新它包裹的内容呢?
答:原来 patchVnode 在做各种 diff 之前,会先执行 prepatch 的钩子函数,其核心逻辑就是执行 updateChildComponent 方法。updateChildComponent 主要是去更新组件实例的一些属性,重点关注 slot ,prepatch 会重新解析 组件的 slots,从而实现更新它包裹的内容。

3. 生命周期

  • activated 钩子函数:它的执行时机是 包裹的组件渲染的时候

  • deactivated 钩子函数:它是发生在 vnode 的 destory 钩子函数

13. Vue 性能优化(43:20)

  1. 编码优化

    • 不必要的数据不要放在 data 中(因为 Vue 的响应式数据处理会遍历数据,内存开销不小)
    • v-for 的事件使用事件代理
    • SPA(单页应用)采用 keep-alive 缓存组件
    • 拆分组件(提高复用性、代码可维护性,减少不必要的渲染<数据变化只会重新渲染当前组件的数据>
    • 尽量使用 v-if,仅当元素需要频繁切换显示或隐藏时采用 v-show
    • 保证 key 的唯一性(vue 默认就地复用的策略,会导致问题)
    • 对于data 中静态数据,可使用 Object.freeze 进行属性冻结(禁止改变对象属性)
    • 合理使用路由懒加载与异步组件
    • 持久化数据问题(防抖、节流)
  2. 加载性能优化

    • 第三方模块按需加载(babel-plugin-component)
    • 滚动可视区域动态加载(vue-virtual-scroll-list))
    • 图片懒加载(vue-lazyload)

      参考博客:解析vue-lazyload的设计思想

  3. 用户体验

    • 首页加载骨架屏(vue-skeleton-webpack-plugin)
    • PWA(progressive-web-application 渐进式网页应用)(使用较少,兼容性差)
  4. SEO 优化

    • 预渲染插件(prerender-spa-plugin)
    • SSR(服务端渲染)
  5. 打包

    • CDN 加载第三方模块
    • 多线程打包(happypack???)
    • suurcemap 使用 (source-map)

      参考博客:sourceMap是个啥

  6. 缓存与压缩

    • 客户端缓存、服务端缓存
    • 服务端 gzip 压缩

14. Vue3.0 有哪些改进(54:20)

  • 使用 ts 做静态数据监测
  • 使用 composition API(解决 mixin 的缺陷,代码更加条理清晰,降低耦合性)
  • 响应式数据用 proxy 实现
  • vdom算法优化,只更新绑定动态数据部分

15. hash 路由与 history 路由(56:00)

路由:通过改变 URL 实现不重新请求页面而刷新视图

  • hash路由

    1. 通过 onhashchange 事件实现
    2. hash 改变时触发
    3. 特点(缺点) :URL 带#
  • history 路由

    1. history.pushState、history.replaceState(HTML5 history 的 api)实现,前者增加历史记录,后者是替换当前历史记录

    2. 使用 back()(等价于go(-1)), forward()等价于go(1)和 go() 方法可实现在用户历史记录中向后和向前的跳转

    3. 注意 pushState() 绝对不会触发 hashchange 事件(即时只改变hash),也不会触发 popstate 事件

      popstate 事件能监听除 history.pushState() 和 history.replaceState() 外 url 的变化

    4. 不支持跨域,否则会抛出异常

    5. 优点:美观,甩掉了丑陋的 #

    6. 缺陷,对于服务器没有的资源,会出现 404

      解决方法:服务端设置匹配不到静态资源则返回 index.html。
      但这样会出现服务端不再返回 404 错误页面的问题,解决方法:

      在Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面,如

      const router = new VueRouter({
        mode: 'history',
        routes: [
          { path: '*', component: NotFoundComponent }
        ]
      })
      

16. vue-router 路由守卫

17. Vuex 工作原理 (58:00)

  • 状态共享管理,可以理解为为一个商店(store),各个组件可以获取商店的物品(state)

  • getter(可以认为是 store 的计算属性):可以对 state 中的数据进行计算,并根据其依赖缓存,仅当依赖的值变化时菜重新计算

  • mutation(可以认为是 store 的method属性)可以改变 state 中的数据的状态

    • 使用方法:mutations: { changeState (state [, obj]) { //handler } },changeState 是mutation 事件类型,多使用常量替代。如:

      // mutations.js
      export default const SOME_MUTATION = 'SOME_MUTATION'
      
      // store.js
      import Vuex from 'vuex'
      import { SOME_MUTATION } from './mutations'
      
      const store = new Vuex.Store({
      	  state: { ... },
      	  mutations: {
      	    // 计算属性命名功能来使用一个常量作为函数名(ES2015 风格)
      	    [SOME_MUTATION] (state) {
      	      // mutate state
      	    }
      	  }
      	})
      })
      
    • 调用(提交)方法:
      1.main.js中提交:store.commit( 'changeState' [, obj]),可以通过 obj 进行跨组件传参
      2.组件中提交:使用 this.$store.commit('xxx')

    • 注意: mutation 必须是同步函数

      若是在 mutation 中混合异步调用会导致你的程序很难调试

  • action:action 类似于 mutation,

    • 区别
      1.action 提交的是 mutation,而不是直接变更状态。
      2.action 可以包含任意异步操作。
    • 示例
      actions: {
        incrementAsync ({ commit }) {
          setTimeout(() => {
            commit('increment')
          }, 1000)
        }
      }
      
    • 分发 action:
      1.载荷形式:store.dispatch('xxx', { //... })
      2.对象形式:store.dispatch( { type: 'xxx', key: value } )
      3.组件中分发:this.$store.dispatch('xxx') 或使用 mapAction辅助函数 ...mapAction([ 'xxx' ]) (将 this.xxx() 映射为 this.$store.dispatch('xxx')
  • module:将 store 模块化,使得逻辑更加清晰

Vue的面试题你必须要掌握到这个程度(下篇)_第6张图片


参考资料

  • Vue的面试题你必须要掌握到这个程度
  • Vue.js 技术揭秘

你可能感兴趣的:(vue,vue.js,前端)