• vue面试题


    1.什么是vue生命周期

    指vue实例对象从创建之初到销毁的过程,vue所有功能的实现都是围绕其生命周期进行的,在生命周期的不同阶段调用对应的钩子函数实现组件数据管理和DOM渲染两大重要功能

    2.vue生命周期的作用是什么

    管理vue实例从创建到销毁的过程,并且提供给开发者加入自定义代码逻辑提供入口

    3.第一次页面加载会触发哪几个钩子

    beforeCreate, created, beforeMount, mounted
    推荐在 created 钩子函数中异步请求,优点:
    能更快获取到服务端数据,减少页面加载时间,用户体验更好;
    SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性

    4.简述每个周期具体适合哪些场景

    1、beforeCreate
    vue实例创建之前,此时获取不到监听数据和方法,如果有需要在初始化就执行的全局方法 可以在这里调用
    2、created
    在vue实例创建完成,dom没有渲染时这个阶段可以ajax调用。 在这一步,实例已完成以下的配置:数据观测 (data observer)属性和方法的运算,
    watch/event 事件回调。然而,挂载阶段还没开始,。这里没有$el
    3、beforeMount
    执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面,在挂载开始之前被调用:相关的 render 函数首次被调用
    4、mounted
    执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。
    如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行
    5、beforeUpdate
    数据更新时调用,发生在虚拟DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器,该钩子在服务器端渲染期间不被调用,因为只有初次渲染会 在服务端进行
    6、updated
    由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子
    7、activated
    keep-alive 组件激活时调用。该钩子在服务器端渲染期间不被调用
    8、deactivated
    keep-alive 组件停用时调用。该钩子在服务器端渲染期间不被调用
    9、beforeDestroy
    实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用
    10、destroyed
    Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监 听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用
    11、errorCaptured(2.5.0+ 新增)
    当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生 错误的组件实例以及一个包含错误来源信息的字符串,此钩子可以返回 false 以阻止该错误继续向上传播

    5.created和mounted的区别

    他们是vue生命周期的两个不同的阶段,执行时机不同。

    created(已创建):是在模板渲染成HTML前调用的,此时data已经准备完毕,el仍是undefined,因为没有渲染成HTML,所以不能操作dom节点,可以在这个阶段执行ajax;
    mounted(已挂载):是在模板渲染成HTML之后调用的,此时data,el都已准备好,可以操作html的dom节点,可以通过id什么的来查找页面元素;
    通常created使用的次数多,而mounted通常是在一些插件的使用或者组件的使用中进行操作。如果写入created中,你会发现无法对插件进行一些初始化配置,一定要等到html渲染完成才可以进行,这时候就需要用到mounted。

    那么在用户体验解决了什么问题呢?

    比如在传统模式下开发,网速慢的时候会先展示默认的静态数据,等ajax请求完成后才显示动态数据,这样对于用户体验不是很好,现在vue的钩子函数created就解决了这个问题,咱们把ajax请求的数据放到created里面,这样页面在加载dom之前就先把数据获取出来然后在渲染到页面上就解决了之前显示默认静态数据的问题,提升用户体验。

    6.vue获取数据在哪个周期函数

    一般 created/beforeMount/mounted 皆可. 比如如果你要操作 DOM , 那肯定 mounted 时候才能操作.

    7.请详细说下你对vue生命周期的理解?

    总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。

    创建前/后:
    在beforeCreated阶段,vue实例的挂载元素 e l 和数据对象 d a t a 都为 u n d e f i n e d ,还未初始化。在 c r e a t e d 阶段, v u e 实例的数据对象 d a t a 有了, el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了, el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,el还没有。
    载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
    更新前/后:当data变化时,会触发beforeUpdate和updated方法。
    销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在。

    8.mvvm框架是什么?

    MVVM是Model-View-ViewModel的简写。即模型-视图-视图模型。 【Model】指的是后端传递的数据。

    【View】指的是DOM,即所有看到的页面,可以理解为将数据以某种方式呈现给用户。

    【ViewModel】mvvm模式的核心,它是连接view和model的桥梁。它有两个方向:一是将【模型】转化成【DOM】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将【DOM】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM
    事件监听。这两个方向都实现的,我们称之为数据的双向绑定。

    总结:在MVVM的框架下DOM和模型是不能直接通信的。它们通过ViewModel来通信,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的DOM做自动更新,而当用户操作DOM,ViewModel也能监听到DOM的变化,然后通知数据做改动,这实际上就实现了数据的双向绑定。并且MVVM中的View
    和 ViewModel可以互相通信。

    这也是vue官网中提到的【虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm
    (ViewModel 的缩写) 这个变量名表示 Vue 实例。】 View指的是dom而非真正的视图
    谈谈你对 MVVM的理解? 为什么要有这些模式:目的:职责划分、分层 ( 将Model层、View层进行分类 ) 借鉴 后端思想。对于前端而言就是如何将数据同步到页面上。

    MVC 模式 : Backbone + underscore + jquery
    在这里插入图片描述

    对于前端而言,数据变化无法同步到视图中。需要将逻辑聚拢在controller 层 MVVM 模式 : 映射关系的简化
    (隐藏controller)
    在这里插入图片描述

    MVVM就是将其中的View的状态和行为抽象化,让我们的视图UI和业务逻辑进行分离。

    在MVVM架构下,View和Model没有直接的联系,而是通过ViewModel进行数据交互,Model和ViewModel之间的交互是双向的,因此View数据的变化会同步到Model中,Model数据的变化也会立即反应到View上面。

    ViewModel通过双向数据绑定把View和Model层连接起来,而View和Model之间的同步工作是完全自动的,不需要进行人为的干涉,因此开发者只需要关注业务逻辑,不需要手动操作Dom,不需要关注数据状态的同步问题,复杂的数据状态的维护完全由MVVM来进行管理

    10、请说一下Vue响应式数据的理解

    数组和对象类型当值变化时如何劫持到。对象内部通过 defineReactive 方法,使用 Object.defineProperty
    将属性进行劫持(只会劫持已经存在的属性),数组则是 通过重写数组方法来实现。 多层对象是通过递归来实现劫持

    11、vue-router 是什么?它有哪些组件

    Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。通俗来讲主要是来实现页面的跳转,通过设置不同的path,向服务器发送的不同的请求,获取不同的资源。
    路由中有三个基本的概念 route, routes, router。 1。 route,它是一条路由,由这个英文单词也可以看出来,它是单数,
    Home按钮 => home内容, 这是一条route, about按钮 => about 内容, 这是另一条路由。
    2. routes 是一组路由,把上面的每一条路由组合起来,形成一个数组。[{home 按钮 =>home内容 }, { about按钮 => about 内容}]
    3. router 是一个机制,相当于一个管理者,它来管理路由。因为routes 只是定义了一组路由,它放在哪里是静止的,当真正来了请求,怎么办? 就是当用户点击home 按钮的时候,怎么办?这时router
    就起作用了,它到routes 中去查找,去找到对应的 home 内容,所以页面中就显示了 home 内容。
    4.客户端中的路由,实际上就是dom 元素的显示和隐藏。当页面中显示home 内容的时候,about 中的内容全部隐藏,反之也是一样。客户端路由有两种实现方式:基于hash 和基于html5 history api.路由跳转等都需要vue-router

    包含的功能有:
    嵌套的路由/视图表
    模块化的、基于组件的路由配置
    路由参数、查询、通配符
    基于Vue.js 过渡系统的视图过渡效果
    细粒度的导航控制
    带有自动激活的 CSS class 的链接
    HTML5 历史模式或 hash模式,在 IE9 中自动降级
    自定义的滚动条行为
    组件 router-view、router-link

    12、怎么定义vue-router的动态路由?怎么获取传过来的值

    params传参: 配置路由格式:/router/:id 传递的方式:在path后面跟上对应的值 传递后形成的路径:/router/123

     //方法1: 按钮 
     // 方法2: this.$router.push({name:'users',params:{uname:wade}})
     // 方法3:this.$router.push('/user/' + wade)
    
    • 1
    • 2
    • 3

    可以通过$route.params.userid 获取你说传递的值

    query传参: 配置路由格式:/router,也就是普通配置 传递的方式:对象中使用query的key作为传递方式
    传递后形成的路径:/route?id=123

    // 方法1: 按钮 
    // 方法2: this.$router.push({ name: 'users',query:{ uname:james }}) 
    // 方法3: 按钮 
    // 方法4: this.$router.push({ path: '/user', query:{ uname:james }}) 
    // 方法5:this.$router.push('/user?uname=' + jsmes)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以通过$route.query 获取你所传递的值

    13. r o u t e 和 route和 routerouter的区别

    一个是用来获取路由信息的,一个是用来操作路由的 $route
    route是路由信息对象,里面主要包含路由的一些基本信息,包括name、meta、path、hash、query、params、fullPath、matched、redirectedFrom
    $router router是VueRouter的实例,包含了一些路由的跳转方法,钩子函数等

    14.vue-router有哪几种导航钩子?
    1.全局守卫: router.beforeEach

    const router = new VueRouter({ ... })
      router.beforeEach((to, from, next) => {
      // ...
    })
    
    • 1
    • 2
    • 3
    • 4

    当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。

    每个守卫方法接收三个参数:

    to: Route: 即将要进入的目标 路由对象

    from: Route: 当前导航正要离开的路由

    next: Function: 一定要调用该方法来resolve这个钩子。执行效果依赖 next 方法的调用参数。

    next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是confirmed (确认的)。

    next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。

    next(‘/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home’ 之类的选项以及任何用在router-link的 to prop或 router.push中的选项。

    next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError()注册过的回调。

    确保要调用 next方法,否则钩子就不会被 resolved

    2.全局解析守卫: router.beforeResolve
    用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是:在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用

    即在 beforeEach 和 组件内beforeRouteEnter 之后,afterEach之前调用。

    3.全局后置钩子: router.afterEach
    全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

    router.afterEach((to, from) => {
      // ...
    })
    
    • 1
    • 2
    • 3

    4.路由独享的守卫: beforeEnter
    在路由配置上直接定义 beforeEnter 守卫

    const router = new VueRouter({
      routes: [
        {
          path: '/foo',
          component: Foo,
          beforeEnter: (to, from, next) => {
            // ...
          }
        }
      ]
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    5.组件内的守卫: beforeRouteEnter、beforeRouteUpdate (2.2 新增)、beforeRouteLeave
    在路由组件内直接定义以下路由导航守卫

    beforeRouteEnter
     
    beforeRouteUpdate (2.2 新增)
     
    beforeRouteLeave
     
    const Foo = {
      template: `...`,
      beforeRouteEnter (to, from, next) {
        // 在渲染该组件的对应路由被 confirm 前调用
        // 不!能!获取组件实例 `this`
        // 因为当守卫执行前,组件实例还没被创建
      },
      //不过,你可以通过传一个回调给 next来访问组件实例。
      //在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
      beforeRouteEnter (to, from, next) {
        next(vm => {
          // 通过 `vm` 访问组件实例
        })
      },
      beforeRouteUpdate (to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
        // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 可以访问组件实例 `this`
      },
      beforeRouteLeave (to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    15.vue-router响应路由参数的变化

    当使用路由参数时,例如从 /user/foo 导航到
    /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。

    复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象:

    > const User = {   template: '...',   watch: {
    >     $route(to, from) {
    >       // 对路由变化作出响应...
    >     }   
    >   } 
    > } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    或者使用 2.2 中引入的 beforeRouteUpdate 导航守卫:
    
    • 1
    
    > const User = {   template: '...',   beforeRouteUpdate (to, from, next)
    > {
    >     // react to route changes...
    >     // don't forget to call next()   
    > } }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    16.vue-router传参

    一、router-link路由导航方式传参
    父组件:
    子组件:this.$route.params.content接受父组件传递过来的参数

    > 例如: 路由配置:
    
    > bashbash{path:'/father/son/:num',name:A,component:A}```
    > 地址栏中的显示:
    > http://localhost:8080/#/father/son/44
    > 调用方法:
    > <router-link to="/father/son/传入的参数">父亲组件<router-link> 
    >  子组件通过 this.$route.params.num 接受参数 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    二、调用$router.push实现路由传参
    父组件:通过实践触发,跳转代码

    > <button @click="clickHand(123)">push传参</button>   
    > methods: {
    >     clickHand(id) {
    >       this.$router.push({
    >         path: `/d/${id}`
    >       })
    >     }   } 
    >   路由配置
    > {path: '/d/:id', name: D, component: D} 
    > 地址栏中显示:
    > http://localhost:8080/d/123 子组件接受参数方式
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    mounted () { this.id = this.$route.params.id }
    三、通过路由属性name匹配路由,再根据params传递参数
    父组件:

    > <button @click="ClickByName()">params传参</button>
    >     ClickByName() {
    >       this.$router.push({
    >         name: 'B',
    >         params: {
    >           context: '吴又可吴又可吴又可'
    >         }
    >       })
    >     } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    路由配置:路径后不需要在加上传入的参数,但是name必须和父组件中的name一致

    > {path: '/b', name: 'B', component: B}
    
    • 1

    地址栏中的显示:地址栏不会带有传入的参数,而且再次刷新页面后参数会丢失

    > http://localhost:8080/#/b 
    
    • 1

    子组件接收参数的方式:

    > <template>   
    >   <div id="b">
    >     This is page B!
    >     <p>传入参数:{{this.$route.params.context}}</p>   
    >    </div> 
    ></template> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    四、通过query来传递参数 父组件:

    > <button @click="clickQuery()">query传参</button>
    >     clickQuery() {
    >       this.$router.push({
    >         path: '/c',
    >         query: {
    >           context: '1'
    >         }
    >       })
    >     }
    > 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    路由配置:不需要做任何修改

    > {path: '/c', name: 'C', component: C} 
    
    • 1

    地址栏中的显示(中文转码格式):

    > http://localhost:8080/#/c?sometext=%E8%BF%99%E6%98%AF%E5%B0%8F%E7%BE%8A%E5%90%8C%E5%AD%A6
    
    • 1

    子组件接受方法:

    
    > <template>   
    >   <div id="C">
    >     This is page C!
    >     <p>这是父组件传入的数据: {{this.$route.query.context}}</p> 
    >   </div>
    > </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    17.vue-router的两种模式

    Vue-Router有两个路由模式,分别是哈希hash模式和历史history模式,然后默认的是哈希hash模式。

    hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件:

    在这里插入图片描述
    上面的代码可以通过改变hash来改变页面字体颜色,虽然没什么用,但是一定程度上说明了原理。

    更关键的一点是,因为hash发生变化的url都会被浏览器记录下来,从而你会发现浏览器的前进后退都可以用了,同时点击后退时,页面字体颜色也会发生变化。这样一来,尽管浏览器没有请求服务器,但是页面状态和url一一关联起来,后来人们给它起了一个霸气的名字叫前端路由,成为了单页应用标配。

    history路由

    随着history api的到来,前端路由开始进化了,前面的hashchange,你只能改变#后面的url片段,而history
    api则给了前端完全的自由 history api可以分为两大部分,切换和修改

    切换历史状态
    包括back,forward,go三个方法,对应浏览器的前进,后退,跳转操作,有同学说了,(谷歌)浏览器只有前进和后退,没有跳转,嗯,在前进后退上长按鼠标,会出来所有当前窗口的历史记录,从而可以跳转(也许叫跳更合适):

    > history.go(-2);//后退两次   history.go(2);//前进两次  
    > history.back(); //后退   hsitory.forward(); //前进 ```
    
    • 1
    • 2

    通过pushstate把页面的状态保存在state对象中,当页面的url再变回这个url时,可以通过event.state取到这个state对象,从而可以对页面状态进行还原,这里的页面状态就是页面字体颜色,其实滚动条的位置,阅读进度,组件的开关的这些页面状态都可以存储到state的里面。

    history模式怕啥
    不怕前进,不怕后退,就怕刷新,(如果后端没有准备的话),因为刷新是实实在在地去请求服务器的。在history模式下,你可以自由的修改path。history模式最终的路由都体现在url的pathname中,这部分是会传到服务器端的,因此需要服务端对每一个可能的path值都作相应的映射。
    当刷新时,如果服务器中没有相应的响应或者资源,会分分钟刷出一个404来不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问
    http://oursite.com/user/id 就会返回 404,这就不好看了。

    所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html
    页面,这个页面就是你 app 依赖的页面

    nginx

    location / {
      try_files $uri $uri/ /index.html;
    }
    
    • 1
    • 2
    • 3

    区别:

    1、hash - 即地址栏URL中的 # 符号(此hash不是密码学里的散列运算)
    比如这个URL:http://www.abc.com/#/hello,hash的值为#/hello.它的特点在于:hash虽然出现在URL中,但不会被包括在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。
    2、history - 利用了HTML5 History
    Interface中新增的pushState()和replaceState()方法。(需要特定浏览器支持)
    这两个方法应用于浏览器的历史记录栈,在当前已有的back、forward、go的基础上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的URL,但浏览器不会即向后端发送请求。history模式会将URL修改得就和正常请求后端的URL一样,如后端没有配置对应/user/id的路由处理,则会返回
    404 错误。

    因此可以说,hash模式和histoury模式都是属于浏览器自身的特性,Vue-Router只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由

    18.vue-router实现路由懒加载(动态加载路由)

    也叫延迟加载,即在需要的时候进行加载,随用随载
    VUE官网给出的解释:
    当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

    结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。

    首先,可以将异步组件定义为返回一个 Promise 的工厂函数 (该函数返回的 Promise 应该 resolve 组件本身):

    const Foo = () => Promise.resolve({ /* 组件定义对象 */ })
    
    • 1

    第二,在 Webpack 2 中,我们可以使用动态 import语法来定义代码分块点 (split point):

    import('./Foo.vue') // 返回 Promise
    
    • 1

    结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件。

    const Foo = () => import('./Foo.vue')
    
    • 1

    在路由配置中什么都不需要改变,只需要像往常一样使用 Foo:

    const router = new VueRouter({
      routes: [
        { path: '/foo', component: Foo }
      ]
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    把组件按组分块
    有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。

    const Foo = r => require.ensure([], () => r(require('./Foo.vue')), 'group-foo')
    const Bar = r => require.ensure([], () => r(require('./Bar.vue')), 'group-foo')
    const Baz = r => require.ensure([], () => r(require('./Baz.vue')), 'group-foo')
    
    • 1
    • 2
    • 3
    const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
    const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
    const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
    
    • 1
    • 2
    • 3

    Webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。

    (1)第一种写法:使用 AMD 风格的 require,于是就更简单了:

    例:const Foo = resolve => require(['./Foo.vue'], resolve)
    
    • 1
    const routers = [
        {
            path: '/',
            name: 'index',
            component: (resolve) => require(['./views/index.vue'], resolve)
        }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2)第二种写法:(使用import)

    例:component: () => import('@/components/Two')
    
    • 1
    const Index = () => import(/* webpackChunkName: "group-home" */  '@/views/index')
    const routers = [
        {
            path: '/',
            name: 'index',
            component: Index
        }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3)第三种写法:使用webpack特有的require.ensure()。注:require.ensure 是 Webpack 的特殊语法,用来设置 code-split point

    例:components: r => require.ensure([], () => r(require('@/components/Three')),'group-home')
    
    • 1
    const Index = r => require.ensure([], () => r(require('./views/index')), 'group-home');
    const routers = [
        {
            path: '/',
            name: 'index',
            component: Index
        }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    18.vue优缺点

    优点:
    1.数据驱动视图,对真实 dom 进行抽象出 virtual dom(本质就是一个 js 对象), 并配合 diff 算法、响应式和观察者、异步队列等手段以最小代价更新 dom,渲染页面
    2.组件化,组件用单文件的形式进行代码的组织编写,使得我们可以在一个文 件里编写 html\css(scoped 属性配置 css 隔离)\js 并且配合 Vue-loader 之后,支 持更强大的预处理器等功能
    3.强大且丰富的 API 提供一系列的 api 能满足业务开发中各类
    4.由于采用虚拟 dom,让 Vue SSR(服务器渲染) 先天就足
    5.生命周期钩子函数,选项式的代码组织方式,写熟了还是蛮顺畅的,但仍然 有优化空间(Vue3 composition-api)
    6.生态好,社区活跃
    7.双向数据绑定:当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。

    缺点:
    1.由于底层基于 Object.defineProperty 实现响应式,而这个 api 本身不支持 IE8 及以下浏览器
    2.CRS(浏览器端渲染) 的先天不足,首屏性能问题(白屏),首屏加载速度没有静态页面快
    3.由于百度等搜索引擎爬虫无法爬取 js 中的内容,故 spa(单页Web应用) 先天就对 seo(搜索引擎优化) 优化心有余力不足
    4.因为是单页面应用,不利于 seo 优化

    19.vue父组件向子组件传递数据?

    父组件中的操作:把数据放在子组件的 :xxxxx 上,这样就传过去了

    <feed-section :recommend_feeds="recommend_feeds"></feed-section>
    
    • 1

    子组件的操作

    props: [
      'recommend_feeds'
    ],
    
    • 1
    • 2
    • 3

    通过 props 中写一个recommend_feeds 这样就可以拿到父组件通过recommend_feeds穿过来的数据了

    20.子组件向父组件传递事件

    1.子组件需要使用this.$emit 将子组件的事件进行上级传递。
    2. 父组件需要使用@事件名的方式,接收子组件的事件。

    21.v-show和v-if指令的共同点和不同点

    相同点: v-show 和 v-if 都能控制元素的显示和隐藏。
    不同点: 1、实现本质方法不同 v-show 本质就是通过设置 css 中的 display 设置为 none,控制隐藏 v-if 是动态的向 DOM 树内添加或者删除 DOM 元素
    2.编译的区别 v-show 其实就是在控制 css v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的 事件监听和子组件
    3.编译的条件 v-show 都会编译,初始值为 false,只是将 display 设为 none,但它也编译了 v-if 初始值为 false,就不会编译了
    4.性能比较 v-show 只编译一次,后面其实就是控制 css,而 v-if 不停的销毁和创建,故 v-show 性能更好一点。 注意点:因为 v-show 实际是操作 display:" "或者 none,当 css 本身有 display:none 时,v-show
    无法让显示 总结(适用场景):如果要频繁切换某节点时,使用 v-show(无论 true 或者 false 初始都会进行渲染,此后通过
    css 来控制显示隐藏,因此切换开销比较小,初始 开销较大),如果不需要频繁切换某节点时,使用 v-if(因为懒加载,初始为 false
    时,不会渲染,但是因为它是通过添加和删除 dom 元素来控制显示和隐藏的, 因此初始渲染开销较小,切换开销比较大)

    22.如何让CSS只在当前组件中起作用

    当前组件<style>写成<style  scoped>
    
    • 1

    23.的作用是什么?

    包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。

    比如有一个列表和一个详情,那么用户就会经常执行打开详情=>返回列表=>打开详情…这样的话列表和详情都是一个频率很高的页面,那么就可以对列表组件使用进行缓存,这样用户每次返回列表的时候,都能从缓存中快速渲染,而不是重新渲染

    24.如何获取dom

    1.使用ref,给相应的元素加ref=“name” 然后再this.$refs.name获取到该元素

    2.使用原生js获取
    3.对于动态元素 使用$nextTick 在该函数中获取

    25.说出几种vue当中的指令和它的用法?

    1、v-if:判断是否隐藏

    2、v-for:数据循环

    3、v-bind:class:绑定一个属性

    4、v-model:实现数据双向绑定

    5、v-html: 更新元素的 innerHTML

    6、v-text:更新元素的 textContent

    7、v-on:绑定事件监听器

    8、v-bind:动态地绑定一个或多个 attribute

    26.vue-loader是什么?使用它的用途有哪些?

    一、vue-loader作用:
    解析和转换.vue文件。提取出其中的逻辑代码 script,样式代码style,以及HTML 模板template,再分别把他们交给对应的loader去处理
    
    二、用途
    js可以写es6,style样式可以写scss或less
    
    三、
    vue-template-compiler:把vue-loader提取出的HTML模板编译成可执行的jacascript代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    27.为什么使用key

    为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,更好地区别各个组件
    key的作用主要是为了高效的更新虚拟DOM
    Vue在patch过程中通过key可以判断两个虚拟节点是否是相同节点。 (可以复用老节
    点)
    无key会导致更新的时候出问题
    尽量不要采用索引作为key

    28.axios及安装

    Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中

    特性 从浏览器中创建 XMLHttpRequests 从 node.js 创建 http 请求 支持 Promise API 拦截请求和响应
    转换请求数据和响应数据 取消请求 自动转换 JSON 数据 客户端支持防御 XSRF 浏览器支持:
    在这里插入图片描述

    bash 安装:npm install --save axios

    fetch.js

        export default function fetch (url = '', data = {}, method = 'post') {   method = method.toLowerCase()   let ops = { params: data }
    if (method === 'post') {
        ops = { data: data }   }   let options = {
        url,
        ...ops,
        method   }   return new Promise((resolve, reject) => {
        const instance = axios.create({
          baseURL: BASE_URL,
          timeout: 100000,
          withCredentials: true,
          crossDomain: true,
          // headers: { 'Content-Type': 'application/json;charset=UTF-8' }
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        })
        instance(options).then((res) => {
          resolve(res.data)
        }).catch((data) => {
          reject(data)
        })   }) } ```
    
    index.js
    
    ```bash export const BASE_URL = '//****.**.com' // 接口域名   export const
    INTERFACE_NAME = BASE_URL + '/name ```
    
    ' service.js
    
    ```bash import fetch from '../common/fetch' import * as api from
    './index'   // 接口函数 export const intserfaceName = (params) =>
    fetch(api.INTERFACE_NAME, params, 'post') 调用侧vue
    
      created () {
        interfaceName({}).then(res=>{
            // do 
        })   }, ```
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    29.axios解决跨域

    一、Vuecli2 版本

    this.$axios.get("https://www.v2ex.com/api/site/info.json")
    .then(res=>{
        console.log(res)
    })
    .catch(err=>{
        console.log(err)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行程序后,控制台报错如下:
    在这里插入图片描述

    Step1:配置BaseUrl

    首先在main.js中,配置下我们访问的Url前缀:

    '/api':{
        target: "https://www.v2ex.com/api",
        changeOrigin:true,
        pathRewrite:{
            '^/api':''
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    Step3:修改请求Url

    修改刚刚的axios请求,把url修改如下

    this.$axios.get("/site/info.json")
    .then(res=>{
    	console.log(res)
    })
    .catch(err=>{
    	console.log(err)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Step4:重启服务
    重启服务后,此时已经能够访问了:
    在这里插入图片描述

    原理:

    因为我们给url加上了前缀 /api,我们访问 http://127.0.0.1:19323/site/info.json 就当于访问了:http://127.0.0.1:19323/api/site/info.json。(假设本地访问端口号为 19323)

    又因为在 index.js 中的 proxyTable 中拦截了 /api ,并把 /api 及其前面的所有替换成了 target 中的内容,因此实际访问 Url 是https://www.v2ex.com/api/site/info.json。
    二、Vuecli3 版本
    升级到 Vue3 后,会发现 Vue2 中存放配置的 config 文件夹没有了,大家不要慌张。可以在 package.json 文件的同级目录下创建 vue.config.js 文件。给出该文件的基础配置:

    module.exports = {
        outputDir: 'dist',   //build输出目录
        assetsDir: 'assets', //静态资源目录(js, css, img)
        lintOnSave: false, //是否开启eslint
        devServer: {
            open: true, //是否自动弹出浏览器页面
            host: "localhost", 
            port: '8081', 
            https: false,   //是否使用https协议
            hotOnly: false, //是否开启热更新
            proxy: null,
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    修改 vue.config.js 中 devServer 子节点内容,添加一个 proxy:

    devServer: {
        open: true, //是否自动弹出浏览器页面
        host: "localhost", 
        port: '8081',
        https: false,
        hotOnly: false, 
        proxy: {
            '/api': {
                target: 'https://www.v2ex.com/api', //API服务器的地址
                changeOrigin: true,
                pathRewrite: {
                    '^/api': ''
                }
            }
        },
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    30.v-model的使用

    表单元素的双向绑定,本质是一个语法糖

    v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

    • text 和 textarea 元素使用 value property 和 input 事件;
      checkbox 和 radio 使用 checked property 和 change 事件;
      select 字段将 value 作为 prop 并将 change 作为事件

    31.scss的安装以及使用

    npm install node-sass sass-loader style-loader --save-dev

    配置moudle下rules

            test: /\.scss$/,
            loaders: ['style', 'css', 'sass']  } ```
    
    • 1
    • 2

    32.请说出vue.cli项目中src目录每个文件夹和文件的用法?

    vue-cli 项目中src目录每个文件夹和文件的用法

    assets 文件夹是放静态资源
    components 是放组件
    router 是定义路由相关的配置
    view 视图
    app.vue是一个应用主组件 main.js 是入口文件

    33.分别简述computed和watch的使用场景

    1、区别

    watch中的函数是不需要调用的

    computed内部的函数调用的时候不需要加()

    watch 属性监听 监听属性的变化

    computed:计算属性通过属性计算而得来的属性

    watch需要在数据变化时执行异步或开销较大的操作时使用

    对于任何复杂逻辑或一个数据属性在它所依赖的属性发生变化时,也要发生变化,这种情况下,我们最好使用计算属性computed。

    computed 属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。主要当作属性来使用;

    computed中的函数必须用return返回最终的结果

    当computed中的函数所依赖的属性如果没有发生改变的时候,那么调用当前函数的时候结果会从缓存中读取

    watch 一个对象,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;

    2、使用场景

    computed

    当一个属性受多个属性影响的时候就需要用到computed

    最典型的例子: 购物车商品结算的时候

    watch

    当一条数据影响多条数据的时候就需要用watch

    搜索数据

    34.v-on可以监听多个方法吗

    v-on传一个对象,即可监听多个方法

         type="text"
         v-on="{
         input:onInput,
         focus:onFocus,
         blur:onBlur,
         }"
    > ```
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    35.$nextTick在哪里使用?原理是?

    将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

    可用于获取更新后的 DOM。 Vue中数据更新是异步的,使用 nextTick 方法可以保证用户定义的逻辑在更新之后 执行。

    36.vue组件中data为什么必须是一个函数

    组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,
    这样可以保证多个组件间数据互不影响。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。

    37.vue事件对象的使用

    vue文档:

    在监听原生 DOM 事件时,方法以事件为唯一的参数。如果使用内联语句,语句可以访问一个 $event
    property:v-on:click=“handle(‘ok’, $event)”。

    38.组件间的通信

    方法一、props/$emit
    父组件 A 通过 props 的方式向子组件 B 传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。

    方法二、 e m i t / emit/ emit/on
    这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案 vuex。

     var Event=new Vue();
        Event.$emit(事件名,数据);
        Event.$on(事件名,data => {});
    
    • 1
    • 2
    • 3

    方法三、vuex
    在这里插入图片描述

    1. 简要介绍 Vuex 原理

    Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 进行,Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action,但 Action 也是无法直接修改 State 的,还是需要通过 Mutation 来修改 State 的数据。最后,根据 State 的变化,渲染到视图上。

    1. 简要介绍各模块在流程中的功能:

    Vue Components:Vue 组件。HTML 页面上,负责接收用户操作等交互行为,执行 dispatch 方法触发对应 action 进行回应。
    dispatch:操作行为触发方法,是唯一能执行 action 的方法。
    actions:操作行为处理模块,由组件中的$store.dispatch(‘action 名称’, data1)来触发。然后由 commit()来触发 mutation 的调用 , 间接更新 state。负责处理 Vue Components 接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台 API 请求的操作就在这个模块中进行,包括触发其他 action 以及提交 mutation 的操作。该模块提供了 Promise 的封装,以支持 action 的链式触发。
    commit:状态改变提交操作方法。对 mutation 进行提交,是唯一能执行 mutation 的方法。
    mutations:状态改变操作方法,由 actions 中的commit(‘mutation 名称’)来触发。是 Vuex 修改 state 的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些 hook 暴露出来,以进行 state 的监控等。
    state:页面状态管理容器对象。集中存储 Vue components 中 data 对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。
    getters:state 对象读取方法。图中没有单独列出该模块,应该被包含在了 render 中,Vue Components 通过该方法读取全局 state 对象。
    3. Vuex 与 localStorage

    vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在 vuex 里数据改变的时候把数据拷贝一份保存到 localStorage 里面,刷新之后,如果 localStorage 里有保存的数据,取出来再替换 store 里的 state。

    let defaultCity = "上海"
    try {   // 用户关闭了本地存储功能,此时在外层加个try...catch
      if (!defaultCity){
        defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
      }
    }catch(e){}
    export default new Vuex.Store({
      state: {
        city: defaultCity
      },
      mutations: {
        changeCity(state, city) {
          state.city = city
          try {
          window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
          // 数据改变的时候把数据拷贝一份保存到localStorage里面
          } catch (e) {}
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    这里需要注意的是:由于 vuex 里,我们保存的状态,都是数组,而 localStorage 只支持字符串,所以需要用 JSON 转换

    方法四、 a t t r s / attrs/ attrs/listeners

    1. 简介

    多级组件嵌套需要传递数据时,通常使用的方法是通过 vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此 Vue2.4 版本提供了另一种方法---- a t t r s / attrs/ attrs/listeners
    1. a t t r s :包含了父作用域中不被 p r o p 所识别 ( 且获取 ) 的特性绑定 ( c l a s s 和 s t y l e 除外 ) 。当一个组件没有声明任何 p r o p 时,这里会包含所有父作用域的绑定 ( c l a s s 和 s t y l e 除外 ) ,并且可以通过 v − b i n d = " attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=" attrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(classstyle除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(classstyle除外),并且可以通过vbind="attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
    2. l i s t e n e r s :包含了父作用域中的 ( 不含 . n a t i v e 修饰器的 ) v − o n 事件监听器。它可以通过 v − o n = " listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=" listeners:包含了父作用域中的(不含.native修饰器的)von事件监听器。它可以通过von="listeners" 传入内部组

    // index.vue
    <template>
      <div>
        <h2>浪里行舟</h2>
        <child-com1
          :foo="foo"
          :boo="boo"
          :coo="coo"
          :doo="doo"
          title="前端工匠"
        ></child-com1>
      </div>
    </template>
    <script>
    const childCom1 = () => import("./childCom1.vue");
    export default {
      components: { childCom1 },
      data() {
        return {
          foo: "Javascript",
          boo: "Html",
          coo: "CSS",
          doo: "Vue"
        };
      }
    };
    </script>
     
     
    // childCom1.vue
    <template class="border">
      <div>
        <p>foo: {{ foo }}</p>
        <p>childCom1的$attrs: {{ $attrs }}</p>
        <child-com2 v-bind="$attrs"></child-com2>
      </div>
    </template>
    <script>
    const childCom2 = () => import("./childCom2.vue");
    export default {
      components: {
        childCom2
      },
      inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
      props: {
        foo: String // foo作为props属性绑定
      },
      created() {
        console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
      }
    };
    </script>
     
     
    // childCom2.vue
    <template>
      <div class="border">
        <p>boo: {{ boo }}</p>
        <p>childCom2: {{ $attrs }}</p>
        <child-com3 v-bind="$attrs"></child-com3>
      </div>
    </template>
    <script>
    const childCom3 = () => import("./childCom3.vue");
    export default {
      components: {
        childCom3
      },
      inheritAttrs: false,
      props: {
        boo: String
      },
      created() {
        console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
      }
    };
    </script>
     
     
    // childCom3.vue
    <template>
      <div class="border">
        <p>childCom3: {{ $attrs }}</p>
      </div>
    </template>
    <script>
    export default {
      props: {
        coo: String,
        title: String
      }
    };
    </script>
     
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    如上图所示 a t t r s 表示没有继承数据的对象,格式为属性名:属性值。 V u e 2.4 提供了 attrs表示没有继承数据的对象,格式为{属性名:属性值}。Vue2.4 提供了 attrs表示没有继承数据的对象,格式为属性名:属性值Vue2.4提供了attrs , $listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。

    简单来说: a t t r s 与 attrs与 attrslisteners 是两个对象, a t t r s 里存放的是父组件中绑定的非 P r o p s 属性, attrs 里存放的是父组件中绑定的非 Props 属性, attrs里存放的是父组件中绑定的非Props属性,listeners里存放的是父组件中绑定的非原生事件。
    方法五、provide/inject

    1. 简介

    Vue2.2.0 新增 API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。
    provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

    1. 举个例子

    假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件

    // A.vue
    export default {
      provide: {
        name: '浪里行舟'
      }
    }
     
     
     
    // B.vue
    export default {
      inject: ['name'],
      mounted () {
        console.log(this.name);  // 浪里行舟
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    可以看到,在 A.vue 里,我们设置了一个 provide: name,值为 浪里行舟,它的作用就是将 name 这个变量提供给它的所有子组件。而在 B.vue 中,通过 inject 注入了从 A 组件中提供的 name 变量,那么在组件 B 中,就可以直接通过 this.name 访问这个变量了,它的值也是 浪里行舟。这就是 provide / inject API 最核心的用法。

    需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的----vue 官方文档
    所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 浪里行舟。

    方法六、$parent / $children与 ref
    ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
    $parent / $children:访问父 / 子实例

    总结 常见使用场景可以分为三类:

    父子通信:
    父向子传递数据是通过 props,子向父是通过 events($emit);

    通过父链 / 子链也可以通信($parent / c h i l d r e n ); r e f 也可以访问组件实例 p r o v i d e / i n j e c t A P I ; children);ref 也可以访问组件实例provide / inject API; children);ref也可以访问组件实例provide/injectAPIattrs/$listeners

    兄弟通信:
    Bus;Vuex
    跨级通信:
    Bus;Vuex;provide / inject API、 a t t r s / attrs/ attrs/listeners

    39.渐进式框架的理解

    主张最少,也就是弱主张,他是在vue核心库(视图模板引擎)的基础上,去逐步添加所需要功能(如,组件系统、路由、状态机等)

    vue“渐进式”:是指先使用vue核心库,在vue核心库的基础上,根据自己需要再去逐渐增加功能。

    Vue的核心的功能,是一个视图模板引擎,但这不是说Vue就不能成为一个框架。

    在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统、客户端路由、大规模状态管理来构建一个完整的框架。

    更重要的是,这些功能相互独立,你可以在核心功能的基础上任意选用其他的部件,不一定要全部整合在一起。

    所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念。

    比如说,Angular,它两个版本都是强主张的,如果你用它,必须接受以下东西:

    • 必须使用它的模块机制
    • 必须使用它的依赖注入
    • 必须使用它的特殊形式定义组件(这一点每个视图框架都有,难以避免)

    比如React,它也有一定程度的主张,它的主张主要是函数式编程的理念。

    -比如说,你需要知道什么是副作用,什么是纯函数,如何隔离副作用

    40.Vue中双向数据绑定是如何实现的

    在这里插入图片描述

    • View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。
    • Vue采用数据劫持&发布-订阅模式的方式,通过ES5提供的Object.defineProperty() 方法来劫持 (监控) 各属性的getter、setter,并在数据(对象)发生变动时通知订阅者,触发相应的监听回调。并且,由于是在不同的数据上触发同步,可以精确的将变更发送给绑定的视图,而不是对所有的数据都执行一次检测。要实现Vue中的双向数据绑定,大
      致 可 以 划 分 三 个 模 块:Observer、Compile、Watcher,如图:
      在这里插入图片描述
    • Observer 数据监听器,负责对数据对象的所有属性进行监听(数据劫持),监听到数据发生变化后通知订阅者。
    • Compile 指令解析器,扫描模板,并对指令进行解析,然后绑定指定事件。
    • Watcher 订阅者,关联Observer和Complie,能够订阅并收到属性变动的通知,执行指令绑定的相应操作,更新视图。Updade()是它自身的一个方法,用于执行Compile中绑定的回调,更新视图。
      2.版本比较
      vue是基于依赖收集的双向绑定;
      3.0之前的版本使用 Object。defineProperty,3.0新版本使用Proxy

    1) 基于 数据劫持/依赖收集 双向绑定的优点

    不需要显示的调用,Vue利用数据劫持+发布订阅,可以直接通知变化并且驱动视图
    直接得到精确的变化数据,劫持了属性setter,当属性值改变我们可以精确的获取变化的 newVal,不需要额外的 diff 操作
    2)object.defineProperty的缺点

    不能监听数组; 因为数组没有getter和setter,因为数组长度不确定。如果太长性能负担太大。
    只能监听属性,而不是整个对象;需要遍历属性; 只能监听属性变化,不能监听属性的删减; 3)proxy好处

    可以监听数组; 监听整个对象不是属性 13种拦截方法,强的很多; 返回新对象而不是世界修改元对象,更符合immutable;
    4)proxy缺点

    兼容性不好,且无法用polyfill磨平;

    41.单页面应用和多页面应用区别及优缺点

    单页面应用(SPA),通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html, js,
    css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc端。

    多页面(MPA),就是指一个应用中有多个页面,页面跳转时是整页刷新

    单页面的优点:

    1,用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点spa对服务器压力较小

    2,前后端分离

    3,页面效果会比较炫酷(比如切换页面内容时的专场动画)

    单页面缺点:

    1,不利于seo

    2,导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理)

    3,初次加载时耗时多

    4,页面复杂度提高很多
    在这里插入图片描述

    42.vue中过滤器有什么作用及详解

    语法:

    <div>{{ name | capitalize }}</div>
    //或者
    <div v-bind:id="rawId | formatId"></div>
    
    • 1
    • 2
    • 3
    filters: {
      capitalize: function (value) {
        return value
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 过滤器函数中的value,就是 |前面的值,它相当于第一个参数
    • 在过滤器函数中一定要返回一个值,他就是我们对格式处理后的结果
    • 通俗的来讲,当为一个数据绑定一个过滤器后,每一次渲染这个数据的时候,他都会调用相应过滤器的函数

    43.谈一谈对Vue组件化的理解

    • 组件化开发能大幅提高应用开发效率、测试性、复用性等;
    • 常用的组件化技术:属性、自定义事件、插槽等
    • 降低更新范围,只重新渲染变化的组件
    • 组件的特点:高内聚、低耦合、单向数据流

    44.Vue为什么需要虚拟DOM

    • Virtual DOM就是用js对象来描述真实DOM,是对真实DOM的抽象
    • 由于直接操作DOM性能低但是js层的操作效率高,可以将DOM操作转化成对象操 作,最终通过diff算法比对差异进行更新DOM(减少了对真实DOM的操作)。
    • 虚拟DOM不依赖真实平台环境从而也可以实现跨平台。

    45.Vue.set方法是如何实现的

    • 我们给对象和数组本身都增加了 dep 属性
    • 当给对象新增不存在的属性则触发对象依赖的watcher去更新
    • 当修改数组索引时我们调用数组本身的splice方法去更新数组

    46.Vue.set视图更新

    参数:

    • {Object | Array} target
    • {string | number} propertyName/index
    • {any} value

    向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新
    property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty =
    ‘hi’)

    注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。

    47.Vue中diff算法原理

    Vue的 diff 算法是平级比较,不考虑跨级比较的情况。内部采用深度递归的方式 + 双指针的方式进行比较
    1.先比较是否是相同节点
    2.相同节点比较属性,并复用老节点
    3.比较儿子节点,考虑老节点和新节点儿子的情况
    4.优化比较:头头、尾尾、头尾、尾头
    5.比对查找进行复用

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

    • 响应式数据变化,Vue确实可以在数据发生变化时,响应式系统可以立刻得知。但是如果给每个属性都添加watcher用于更新的话,会产生大量的watcher从而降低性能。
    • 而且粒度过细也会导致更新不精准的问题,所以vue采用了组件级的watcher配合diff 来检测差异

    49.Vue的组件渲染流程

    产生组件虚拟节点 -> 创建组件的真实节点 -> 插入到页面中
    在这里插入图片描述

    50.Vue组件更新流程

    属性更新时会触发 patchVnode 方法 -> 组件虚拟节点会调用 prepatch 钩子 -> 更新 属性 -> 组件更新
    在这里插入图片描述

    51.Vue中异步组件原理

    默认渲染异步占位符节点 -> 组件加载完毕后调用 forceUpdate 强制更新

    在这里插入图片描述

    52.Vue中如何检测数组变化?

    • 数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择重写 数组( push,shift,pop,splice,unshift,sort,reverse )方法。
    • 数组中如果是对象数据类型也会进行递归劫持
    • 数组的索引和长度变化是无法监控到的

    53.如何理解Vue中模板编译原理

    问题核心:如何将template转换成render函数 ?
    1.将template模板转换成 ast 语法树 - parserHTML
    2.对静态语法做静态标记 - markUp
    3.重新生成代码 -codeGen

    baseCompile (   template: string,   options: CompilerOptions ):
    CompiledResult {   const ast = parse(template.trim(), options) //
    1.解析ast语法树   if (options.optimize !== false) {          
        optimize(ast, options)                    // 2.对ast树进行标记,标记 静态节点  }   const code = generate(ast, options)         // 3.生成代码   return {
        ast,
        render: code.render,
        staticRenderFns: code.staticRenderFns  } }) ```
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    54.v-if和v-for的优先级

    v-for比v-if先执行 但是不推荐他们两个一起使用

    v-for和v-if不应该一起使用,必要情况下应该替换成computed属性。原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。

    55.assets和static的区别

    assets和static两个都是用于存放静态资源文件。

    放在static中的文件不会进行构建编译处理,也就不会压缩体积,在打包时效率会更高,但体积更大在服务器中就会占据更大的空间

    放在assets中的文件会进行压缩体积、代码格式化,压缩后会放置在static中一同上传服务器。

    因此建议样式文件放在assets中进行打包,引入的第三方文件放到static中,因为引入的文件已经做过打包处理。

    56.vue常用的修饰符

    一、v-model修饰符
    1、.lazy:

    输入框改变,这个数据就会改变,lazy这个修饰符会在光标离开input框才会更新数据:

    在这里插入图片描述
    2、.trim:

    输入框过滤首尾的空格:
    在这里插入图片描述
    3、.number:

    先输入数字就会限制输入只能是数字,先字符串就相当于没有加number,注意,不是输入框不能输入字符串,是这个数据是数字:
    在这里插入图片描述
    二、事件修饰符

    4、.stop:

    阻止事件冒泡,相当于调用了event.stopPropagation()方法:
    在这里插入图片描述
    5、.prevent:

    阻止默认行为,相当于调用了event.preventDefault()方法,比如表单的提交、a标签的跳转就是默认事件:
    在这里插入图片描述
    6、.self:

    只有元素本身触发时才触发方法,就是只有点击元素本身才会触发。比如一个div里面有个按钮,div和按钮都有事件,我们点击按钮,div绑定的方法也会触发,如果div的click加上self,只有点击到div的时候才会触发,变相的算是阻止冒泡:
    在这里插入图片描述
    7、.once:

    事件只能用一次,无论点击几次,执行一次之后都不会再执行

    在这里插入图片描述
    8、.capture:

    事件的完整机制是捕获-目标-冒泡,事件触发是目标往外冒泡

    9、.sync

    对prop进行双向绑定

    10、.keyCode:

    监听按键的指令,具体可以查看vue的键码对应表

    57.数组更新检测

    Vue 包含两种观察数组的方法分别如下

    1.变异方法

    顾名思义,变异方法会改变被这些方法调用的原始数组,它们也将会触发视图更新,这些方法如下

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

    2.非变异方法

    非变异方法与变异方法的区别就是,非变异方法不会改变原始数组,总是返回一个新数组,

    当使用非变异方法时,可以用新数组替换旧数组,非变异方法大致有:filter(), concat() 和 slice()

    由于 JavaScript 的限制,Vue 不能检测以下变动的数组:

    1.当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue

    2.当你修改数组的长度时,例如:vm.items.length = newLength

    vue针对这两个问题给出了相应的解决办法,使用这两种方法,也会触发状态更新

    1.使用vue全局方法Vue.set() 或者使用vm.$set() 实例方法

    2.使用 splice,caoncat等修改数组

    58.自定义指令详解

    Vue.directive

    参数:

    {string} id
    {Function | Object} [definition]
    注册或获取全局指令。

    执行顺序:

    页面加载时
     
    bind
    inserted
    
    • 1
    • 2
    • 3
    • 4
    组件更新时
     
    update
    componentUpdated
    
    • 1
    • 2
    • 3
    • 4
    卸载组件时
     
    unbind
    
    • 1
    • 2
    • 3
    /*  自定义指 */
    import Vue from 'vue'
    /**
     * 模板
     * v-lang
     * 五个注册指令的钩子函数
     */
    Vue.directive('mydirective', {
      /**
       * 1.被绑定
       * 做绑定的准备工作
       * 比如添加事件监听器,或是其他只需要执行一次的复杂操作
       */
      bind: function(el, binding, vnode) {
        console.log('1 - bind');
      },
      // 2.绑定到节点
      inserted: function(el, binding, vnode) {
        console.log('2 - inserted');
      },
      /**
       * 3.组件更新
       * 根据获得的新值执行对应的更新
       * 对于初始值也会调用一次
       */
      update: function(el, binding, vnode, oldVnode) {
        console.log('3 - update');
      },
      // 4.组件更新完成
      componentUpdated: function(el, binding, vnode, oldVnode) {
        console.log('4 - componentUpdated');
      },
      /**
       * 5.解绑
       * 做清理操作
       * 比如移除bind时绑定的事件监听器
       */
      unbind: function(el, binding, vnode) {
        console.log('5 - bind');
      }
    })
    /**
    钩子函数
    1、bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个绑定时执行一次的初始化动作。
    2、inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)。
    3、update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
    4、componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
    5、unbind:只调用一次,指令与元素解绑时调用。
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    59.vue的两个核心点

    1、数据驱动: 在vue中,数据的改变会驱动视图的自动更新。传统的做法是需要手动改变DOM来使得视图更新,而vue只需要改变数据。

    2、组件 组件化开发,优点很多,可以很好的降低数据之间的耦合度。将常用的代码封装成组件之后(vue组件封装方法),就能高度的复用,提高代码的可重用性。一个页面/模块可以由多个组件所组成。

    60.vue和jQuery的区别

    jQuery是使用选择器 ( )选取 D O M 对象,对其进行赋值、取值、事件绑定等操作,其实和原生的 H T M L 的区别只在于可以更方便的选取和操作 D O M 对象,而数据和界面是在一起的。比如需要获取 l a b e l 标签的内容: ) 选取DOM对象,对其进行赋值、取值、事件绑定等操作,其实和原生的HTML的区别只在于可以更方便的选取和操作DOM对象,而数据和界面是在一起的。比如需要获取label标签的内容: )选取DOM对象,对其进行赋值、取值、事件绑定等操作,其实和原生的HTML的区别只在于可以更方便的选取和操作DOM对象,而数据和界面是在一起的。比如需要获取label标签的内容:(“lable”).val();,它还是依赖DOM元素的值。

    Vue则是通过Vue对象将数据和View完全分离开来了。对数据进行操作不再需要引用相应的DOM对象,可以说数据和View是分离的,他们通过Vue对象这个vm实现相互的绑定。这就是传说中的MVVM。

    61.引进组件的步骤

    1.导入组件

    2.注册组件

    3.使用组件

    62.Vue-cli打包命令是什么?打包后导致路径问题,应该在哪里修改

    vue.config.js

    63.三大框架的对比

    React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。它有以下的特性:

    1.声明式设计:React采用声明范式,可以轻松描述应用。

    2.高效:React通过对DOM的模拟,最大限度地减少与DOM的交互。

    3.灵活:React可以与已知的库或框架很好地配合。

    优点:

    1. 速度快:在UI渲染过程中,React通过在虚拟DOM中的微操作来实现对实际DOM的局部更新。

    2. 跨浏览器兼容:虚拟DOM帮助我们解决了跨浏览器问题,它为我们提供了标准化的API,甚至在IE8中都是没问题的。

    3. 模块化:为你程序编写独立的模块化UI组件,这样当某个或某些组件出现问题是,可以方便地进行隔离。

    4. 单向数据流:Flux是一个用于在JavaScript应用中创建单向数据层的架构,它随着React视图库的开发而被Facebook概念化。

    5. 同构、纯粹的javascript:因为搜索引擎的爬虫程序依赖的是服务端响应而不是JavaScript的执行,预渲染你的应用有助于搜索引擎优化。

    6.兼容性好:比如使用RequireJS来加载和打包,而Browserify和Webpack适用于构建大型应用。它们使得那些艰难的任务不再让人望而生畏。

    缺点:

    React本身只是一个V而已,并不是一个完整的框架,所以如果是大型项目想要一套完整的框架的话,基本都需要加上ReactRouter和Flux才能写大型应用。

    Vue是尤雨溪编写的一个构建数据驱动的Web界面的库,准确来说不是一个框架,它聚焦在V(view)视图层。

    它有以下的特性:

    1.轻量级的框架

    2.双向数据绑定

    3.指令

    4.插件化

    优点:

    1. 简单:官方文档很清晰,比 Angular 简单易学。

    2. 快速:异步批处理方式更新 DOM。

    3. 组合:用解耦的、可复用的组件组合你的应用程序。

    4. 紧凑:~18kb min+gzip,且无依赖。

    5. 强大:表达式 无需声明依赖的可推导属性 (computed properties)。

    6. 对模块友好:可以通过 NPM、Bower 或 Duo 安装,不强迫你所有的代码都遵循 Angular 的各种规定,使用场景更加灵活。

    缺点:

    1. 新生儿:Vue.js是一个新的项目,没有angular那么成熟。

    2. 影响度不是很大:google了一下,有关于Vue.js多样性或者说丰富性少于其他一些有名的库。

    3. 不支持IE8

    Angular是一款优秀的前端JS框架,已经被用于Google的多款产品当中。

    它有以下的特性:

    1.良好的应用程序结构

    2.双向数据绑定

    3.指令

    4.HTML模板

    5.可嵌入、注入和测试

    优点:

    1. 模板功能强大丰富,自带了极其丰富的angular指令。

    2. 是一个比较完善的前端框架,包含服务,模板,数据双向绑定,模块化,路由,过滤器,依赖注入等所有功能;

    3. 自定义指令,自定义指令后可以在项目中多次使用。

    4. ng模块化比较大胆的引入了Java的一些东西(依赖注入),能够很容易的写出可复用的代码,对于敏捷开发的团队来说非常有帮助。

    5. angularjs是互联网巨人谷歌开发,这也意味着他有一个坚实的基础和社区支持。

    缺点:

    1. angular 入门很容易 但深入后概念很多, 学习中较难理解.

    2. 文档例子非常少, 官方的文档基本只写了api, 一个例子都没有, 很多时候具体怎么用都是google来的, 或直接问misko,angular的作者.

    3. 对IE6/7 兼容不算特别好, 就是可以用jQuery自己手写代码解决一些.

    4. 指令的应用的最佳实践教程少, angular其实很灵活, 如果不看一些作者的使用原则,很容易写出 四不像的代码, 例如js中还是像jQuery的思想有很多dom操作.

    5. DI 依赖注入 如果代码压缩需要显示声明.

    64.跨组件双向数据绑定

    组件双向绑定应有以下2个特点:

    1. 父组件只传输 prop,不定义事件接收。

    2. 由子组件更新传下来的值。

    方法一:

    <template>
      <input type="text" v-bind="$attrs" v-on="$listeners">
    </template>
    
    • 1
    • 2
    • 3

    $attr: 向 子组件 传递,当前组件 没有接收的,父组件传递下来的 prop 。

    $listeners: 向父组件传递,当前组件没有接收的,子组件抛出的自定义事件。

    这样实现后,发现输入后会变成一个 [object InputEvent] 的事件对象。

    是因为 v-on=“input” 是内置事件,vue 判断 当前组件 的 tag 是 ‘input’ 类的表单元素,才会关联 value。

    当前组件 经过封装后,tag 已经改变,父组件实际处理的是 this.$emit(‘input’, el) 的传参,el 就是 [object InputEvent] 表单对象。

    解决方案:拦截内置的 input 事件,改变引用,向 父组件 传递最终值,如下。

    <template>
      <input type="text" v-bind="$attrs" @input="observerInput">
    </template>
     
    <script>
      export default {
        methods: {      
          // 拦截内置事件      
          observerInput (el) {
            this.$emit('input', el.target.value)
          },
        },  
      }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    方法二:通过watch

    方法三:通过计算属性

    65.delete和Vue.delete删除数组的区别

    delete 删除数组元素,只是被删除的数组元素变成了empty/undefined,其它元素不变,数组长度不变,键值对应也不变

      在这里插入图片描述
      vue.delete 删除数组元素,是直接删除了元素,改变了数组的键值 (很少用)

      console.log(arr2); ```
      
      ![在这里插入图片描述](https://img-blog.csdnimg.cn/70c732fa3b1d43c6a395534bae91e357.png)
      
      • 1
      • 2
      • 3

      66.SPA首屏加载慢如何解决

      1.减轻同一项目在服务器中的文件大小

      map文件生产环境没必要保留,在vue.config.js中通过进行配置

      /**
      productionSourceMap
      Type: boolean
      Default: true
      如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
      */
       
      productionSourceMap: false
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      2.vue-router 路由懒加载

      懒加载即组件的延迟加载,通常vue的页面在运行后进入都会有一个默认的页面,而其他页面只有在点击后才需要加载出来。

      使用懒加载可以将页面中的资源划分为多份,从而减少第一次加载的时候耗时。

      懒加载配置:
      在这里插入图片描述
      非懒加载路由配置:

      在这里插入图片描述
      路由懒加载打包后static–>js文件夹

      如图所示为通过非懒加载和懒加载后打包的js文件。而非懒加载的打包后一般只有一个app.js 文件。
      在这里插入图片描述
      懒加载打包后

      在这里插入图片描述
      这个1-10.js就相当于你的10个路由对应的文件我是这么理解的哈(使用路由的按需加载,可把app.js分隔成多个小的js文件)

      注意这个vendor.js文件很大这个怎么搞?每次打开会发现这个js很耗时,它是项目所有依赖的集成(包括vue vue-router axios echarts ele等)

      vendor.js一般是将所有引用的库打包在了一起,首先就需要确定是哪些库文件太大

      vue-cli打包优化之分析工具webpack-bundle-analyzer
      在这里插入图片描述

      // 1. 安装
      cnpm install webpack-bundle-analyzer --save-dev
       
      // 2. 在/build/webpack.prod.conf.js文件中引入
      const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
      // 然后配置一下:
      plugins: [
          new BundleAnalyzerPlugin()
      ]
       
      // 3. 在package.json文件中配置:
      "scripts": {
          "analyz": "NODE_ENV=production npm_config_report=true npm run build"
      }
       
      // 4. 运行(会自动在浏览器打开:http://127.0.0.1:8888/)
      npm run build
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      然后就会出现一张类似这样的图片

      在这里插入图片描述
      这几个英文

      stat size(统计尺寸)

      parsed size(解析大小)

      Gzipped size(压缩大小)

      vendorjs中是项目中所有引用的依赖包,即使用的vue、eleui、axios、echarts等等插件框架什么的都在这里边。解决办法

      # cdn引入插件

      # 打包时使用Gzip

      现在来解决它看能不能变小

      先来试一下压缩,

      webback.prod.config.js 中添加如下代码。(文件大小 10.5MB–>3.5MB)

      webpackConfig.plugins.push(
          new CompressionWebpackPlugin({
            asset: '[path].gz[query]',
            algorithm: 'gzip',
            test: new RegExp(
              '\\.(' +
              config.build.productionGzipExtensions.join('|') +
              ')$'
            ),
            threshold: 10240,
            // deleteOriginalAssets:true, //删除源文件
            minRatio: 0.8
          })
        )
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      使用CDN加载

      通过在index.html 中直接引入第三方资源来缓解我们服务器的压力,其原理是将我们的压力分给了其他服务器站点。

      包后需要去掉代码中的console.log ,防止打开f12 的时候输出日志(可webpack配置也可以手动删除)
      在这里插入图片描述
      我这个项目小所以vendeor 请求响应时间少,别个项目大的情况下 这个文件请求响应时间就长了如上图5秒,这是要死人的,慢

      解决方法

      1、这里我们把import导入的方式删掉
      在这里插入图片描述
      2、通过cdn方式去引入import需要导入的库(我们不是不用import,而是换了另一种方式去引入,这样可以减轻vendor.js的负担)
      在这里插入图片描述
      3、这一步配置非常重要,我们在目录build/webpack.base.conf.js文件中配置externals

      在这里插入图片描述
      externals(翻译:外部环境)的作用是从打包的bundle文件中排除依赖。

      换句话说就是让在项目中通过import引入的依赖在打包的时候不会打包到bundle包中去,而是通过script(CDN)的方式去访问这些依赖。

      67.Vue-router跳转和location.href有什么区别

      1.vue-router 中有两种模式 分别使用h5中history新增api来实现,静态跳转,页面不会重新加载,location.href会触发浏览器,页面重新加载一次

      2.vue-router使用diff算法,实现按需加载,减少dom操作

      3.vue-router是路由跳转或同一个页面跳转;location.href是不同页面间跳转;

      4.vue-router是异步加载this.$nextTick(()=>{获取url});location.href是同步加载

      68.vue slot

      官网:

      Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 元素作为承载分发内容的出口。

      通俗:

      vue提供了组件插槽的用法,插槽就是子组件提供给父组件的一个占位符,当父组件使用子组件需要填充一些代码片段或者内容的时候可以使用slot进行占位填充。父组件中的内容就会将子组件中的slot替换。

      1.简单举例
      子组件

      <template>
        <div>
          <h1>这是插槽的简单用法</h1>
          <div style="margin-top:30px;font-size:20px;">
          	<slot></slot>
          </div>
        </div>
      </template>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      父组件

      <template>
        <div>
      	<Child>
      	   <div>简单用法简单用法简单用法</div>
      	</Child>
        </div>
      </template>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      2.具名插槽(也就是带名称的插槽)
      在父组件中使用子组件时,可能有不止一个地方使用插槽,这个时候就要使用具名插槽。如果不带名称的话,就不知道对应的是哪个地方。

      子组件

      <template>
        <div>
          <div>
            <h1>这是子组件的头部插槽</h1>
            <div style="margin-top:20px;font-size:20px;">
              <slot name="header"></slot>
            </div>
          </div>
          <div style="margin-top:30px;">
            <h1>这是子组件的尾部插槽</h1>
            <div style="margin-top:20px;font-size:20px;">
              <slot name="footer"></slot>
            </div>
          </div>
        </div>
      </template>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

      父组件

      <template>
        <div>
      	<Child>
            <div v-slot="header">头部头部头部</div>
            <div v-slot="footer">尾部尾部尾部</div>
          </Child>
        </div>
      </template>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      3.默认插槽
      如果子组件中有一个插槽没有带名称,那么在父组件中使用时也不加名称的话,她默认是替换那个没有名称的插槽的。

      子组件

      <template>
        <div>
          <div>
            <h1>这是子组件的头部插槽</h1>
            <div style="margin-top:20px;font-size:20px;">
              <slot name="header"></slot>
            </div>
          </div>
          <div style="margin-top:30px;">
            <h1>这是默认插槽</h1>
            <div style="margin-top:20px;font-size:20px;">
              <slot></slot>
            </div>
          </div>
          <div style="margin-top:30px;">
            <h1>这是子组件的尾部插槽</h1>
            <div style="margin-top:20px;font-size:20px;">
              <slot name="footer"></slot>
            </div>
          </div>
        </div>
      </template>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22

      父组件

      <template>
        <div>
      	<Child>
            <div>默认默认默认</div>
            <div v-slot="header">头部头部头部</div>
            <div v-slot="footer">尾部尾部尾部</div>
          </Child>
        </div>
      </template>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      如果子组件中有多个默认插槽呢?

      子组件

       <template>
        <div>
          <div>
            <h1>这是子组件的头部插槽</h1>
            <div style="margin-top:20px;font-size:20px;">
              <slot name="header"></slot>
            </div>
          </div>
          <div style="margin-top:30px;">
            <h1>这是默认插槽</h1>
            <div style="margin-top:20px;font-size:20px;">
              <slot></slot>
            </div>
          </div>
          <div style="margin-top:30px;">
            <h1>这是默认插槽</h1>
            <div style="margin-top:20px;font-size:20px;">
              <slot></slot>
            </div>
          </div>
          <div style="margin-top:30px;">
            <h1>这是子组件的尾部插槽</h1>
            <div style="margin-top:20px;font-size:20px;">
              <slot name="footer"></slot>
            </div>
          </div>
        </div>
      </template>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28

      父组件

      <template>
        <div>
      	<Child>
            <div>默认默认默认</div>
            <div v-slot="header">头部头部头部</div>
            <div v-slot="footer">尾部尾部尾部</div>
          </Child>
        </div>
      </template>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      4.作用域插槽(slot-scope)
      当同一些数据需要不同的展示效果的时候你可以使用作用于插槽,也就是数据插槽。

      子组件

      <template>
        <div>
          <div>
            <slot :data="slotData"></slot>
          </div>
        </div>
      </template>
       
      <script>
      export default {
        data() {
          return {
            slotData: [
              {
                name: "王一",
                age: "15"
              },
              {
                name: "钱二",
                age: "16"
              },
            ]
          };
        }
      };
      </script>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26

      父组件

      <template>
      	<div>
            <h1>第一种</h1>
            <Child>
              <template slot-scope="user">
                {{ user.data }}
              </template>
            </Child>
            <h1>第二种</h1>
            <Child>
              <template slot-scope="user">
                <div v-for="item in user.data" :key="item">
                  <p>我叫{{ item.name }},今年{{ item.age }}岁。</p>
                </div>
              </template>
            </Child>
            <h1>第三种</h1>
            <Child>
              <template slot-scope="user">
                <div v-for="item in user.data" :key="item">
                  <p>{{ item.name }}{{ item.age }}</p>
                </div>
              </template>
            </Child>
          </div>
      </template>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26

      总结

      1. 如果父组件中使用的插槽名称在子组件不存在,那么内容将不会显示。
      2. 如果子组件中不存在默认插槽,父组件中使用不带名称的插槽,那么内容将不会显示。
      3. 子组件中若是有多个默认插槽,在父组件中使用时,她会填充到每一个默认插槽中。
      4. 父组件中使用名称插槽时显示位置时根据子组件的插槽位置来的,与父组件使用的位置无关。

      69.axios的特点有哪些

      • 从浏览器中创建 XMLHttpRequests
      • 从 node.js 创建 http 请求
      • 支持 Promise API
      • 拦截请求和响应
      • 转换请求数据和响应数据
      • 取消请求
      • 自动转换 JSON 数据
      • 客户端支持防御 XSRF

      axios中的发送字段的参数是data跟params两个 两者的区别在于params是跟请求地址一起发送的,data的作为一个请求体进行发送
      params一般适用于get请求 data一般适用于post put 请求

      70.请说下封装 vue 组件的过程

      ● 首先,使用Vue.extend()创建一个组件

      ● 然后,使用Vue.component()方法注册组件

      ● 接着,如果子组件需要数据,可以在props中接受定义

      ● 最后,子组件修改好数据之后,想把数据传递给父组件,可以使用emit()方法

      71.vue 各种组件通信方法(父子 子父 兄弟 爷孙 毫无关系的组件)

      一:父子组件通信

      props 和 $emit 父子组件通信

      父组件 $children 操作子组件

      子组件 $parent 访问父组件

      二:非父子组件通信

      中央事件总线

      三:provide/inject

      适用于祖先和后代关系的组件间的通信,祖先元素通过 provide 提供一个值,后代元素则通过 inject
      获取到这个值。这个值默认是非响应的,如果是对象那么则是响应式的

      四:$root 直接访问根组件

      根据官方的文档,我们可以通过 $root 来直接访问到 Vue 实例

      72.computed和watch区别

      computed和watch都是基于Watcher来实现的

      • computed属性是具备缓存的,依赖的值不发生变化,对其取值时计算属性方法不会 重新执行
      • watch则是监控值的变化,当值发生变化时调用对应的回调函数

      73.params和query的区别

      1.query传递参数会在url后面用?连接起来,且参数之间用&&符号连接然后显示在页面的url中;params传递参数不会显示在页面中;query有点像ajax中的get请求,而params像post请求。

      2.在vue中使用query要搭配path路径,而params只能由命名路由name来引入;
      总结
      query和params是两种传参方式
      使用params传参只能由name引入路由,如果写成path页面会显示undefined报错。
      使用query传参的话可以使用path也可以使用name引入路由,不过建议使用path引入路由。
      params是路由的一部分,一定要加路由后面添加参数,不添加刷新页面数据会丢失;而query是拼接在url后面的参数,路由后面不添加也没关系。

      74.vue更新数组时触发视图更新的方法

      push() pop() shift() unshift() splice() sort() reverse() filter(),
      concat()和 slice() 。这些不会改变原始数组,但总是返回一个新数组。当使用这些非变异方法时,可以用新数组替换旧数组

      vue中存在$set来监听数组长度及索引的变化来实现数组的响应式

      75.vue如何引进本地背景图片

      1.使用require的形式导入

      2.使用import 形式导入

      3.通过绑定vue变量操作

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

      Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

      它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

      使用:

      在main.js引入store,注入。新建了一个store目录,然后…… export 。

      state Vuex 使用单一状态树,即每个应用将仅仅包含一个store
      实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。 mutations
      mutations定义的方法动态修改Vuex 的 store 中的状态或数据。 getters 类似vue的计算属性,主要用来过滤一些数据。
      action
      actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过
      store.dispath 来分发 action。 modules
      项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

      77.vuex有哪几种属性

      有五种,分别是State , Getter , Mutation , Action , Module

      78.Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?

      在使用vuex时,异步执行的代码 写在actions

      79.Vuex中如何异步修改状态

      actions与mutations作用类似,都是可以对状态进行修改。不同的是actions是用来操作异步的。
      actions是可以调用Mutations里的方法的。

      在store.js中声明actions

          addAction(context){
              context.commit('add',10)
       
          },
       
          reduceAction({commit}){
              commit('reduce')
       
          }   } ```在actions里写了两个方法addAction和reduceAction,在方法体里,我们都用commit调用了Mutations里边的方法。
      
      context:上下文对象,这里你可以理解称store本身。
      {commit}:直接把commit对象传递过来,可以让方法体逻辑和代码更清晰明了。
      **模板中的使用** 需要在count.vue模板中编写代码,让actions生效。我们先复制两个以前有的按钮,并改成我们的actions里的方法名,分别是:addAction和reduceAction。
      
      ```bash <p>
          <button @click="addAction">+</button>
          <button @click="reduceAction">-</button>   </p> ```改造一下methods方法,首先还是用扩展运算符把mapMutations和mapActions加入。
      
      
      ```bash methods:{
          ...mapMutations([  
       
              'add','reduce'
       
          ]),
       
          ...mapActions(['addAction','reduceAction'])   }, ```
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27

      80.Vuex中actions和mutations的区别

      Mutations

      mutations 必须是同步函数,为什么?

      举个例子: 官方案例

      mutations: {
        someMutation (state) {
          api.callAsyncMethod(() => {
            state.count++
          })
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。
      然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:
      因为当 mutation 触发的时候,回调函数还没有被调用,
      devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的
      状态的改变都是不可追踪的。

      Actions

      vuex为了解决mutations只有同步的问题,提出了actions(异步),专门用来解决mutations只有同步无异步的问题.

      1、用于通过提交mutation改变数据

      2、会默认将自身封装为一个Promise

      3、可以包含任意的异步操作
      在这里插入图片描述

      81.请说一下Vue响应式数据的理解

      vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。

      利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)。
      通过Object.defineProperty()来劫持属性的,使用属性的时候触发getter函数,收集依赖;修改属性的时候触发setter函数,触发相应的回调。

      具体实现分四个模块
      Observer:对数据的属性进行递归遍历,使用Object.defineProperty劫持并监听所有属性,属性发生变化时通知订阅者
      Dep:收集订阅者,将Observer和watcher统一管理
      Watcher:订阅者,负责接收属性的变化通知并执行相应的方法,从而更新视图,是Observer和Compiler之间通信的桥梁
      自身实例化的时候,调用getter函数,向deps添加watch

      当数据修改时,调用setter函数,调用deps.notify,执行watch的update函数
      执行watch的update函数,重新生成虚拟DOM,并进行Diff对页面进行修改
      Compile:解析者,解析每个节点的相关指令,对模板数据和订阅器进行初始化 用于将模板编译为渲染函数,并渲染视图页面
      parse使用正则等方式解析template中的指令,class,style等数据,生成AST(抽象语法树)
      optimize进行优化,标记静态节点,该节点会跳过diff generate,把AST转化为渲染函数,渲染函数用于生成虚拟DOM

      对象内部通过 defineReactive 方法,使用 Object.defineProperty
      将属性进行劫持(只会劫持已经存在的属性),数组则是 通过重写数组方法来实现。 多层对象是通过递归来实现劫持

      src/core/observer/index.js:135

      key: string,   val: any,   customSetter?: ?Function,   shallow?:
      boolean ) {   const dep = new Dep()   // 如果不可以配置直接return   const
      property = Object.getOwnPropertyDescriptor(obj, key)   if (property &&
      property.configurable === false) {
          return   }
          // cater for pre-defined getter/setters   const getter = property && property.get   const setter = property && property.set   if
      ((!getter || setter) && arguments.length === 2) {
          val = obj[key]   }
          // 对数据进行观测   let childOb = !shallow && observe(val)   Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get: function reactiveGetter () { // 取数据时进行依赖收集
            const value = getter ? getter.call(obj) : val
            if (Dep.target) {
              dep.depend()
              if (childOb) { // 让对象本身进行依赖收集
                childOb.dep.depend() // {a:1} => {} 外层对象
                if (Array.isArray(value)) {
                  dependArray(value)
                }
              }
            }
            return value
          },
          set: function reactiveSetter (newVal) {
            const value = getter ? getter.call(obj) : val
            /* eslint-disable no-self-compare */
            if (newVal === value || (newVal !== newVal && value !== value)) {
              return
            }
            /* eslint-enable no-self-compare */
            if (process.env.NODE_ENV !== 'production' && customSetter) {
              customSetter()
            }
            // #7981: for accessor properties without setter
            if (getter && !setter) return
            if (setter) {
              setter.call(obj, newVal)
            } else {
              val = newVal
            }
            childOb = !shallow && observe(newVal)
            dep.notify()
          }   }) } ```
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
    • 相关阅读:
      LabVIEW以编程方式查找系统中DAQ设备的设备名称
      第一章 网络基础知识
      java性能优化案例——面试可能用得到
      昇思25天学习打卡营第8天|保存与加载
      python代码小tips-从HTML字符串中提取文本内容并去掉标签
      葡聚糖-炔基|Alkyne-葡聚糖|提供氨基/羧酸/马来酰亚胺/N-羟基琥珀酰亚胺/叠氮/炔基/巯基功能化葡聚糖
      阿里P7,一个女测试经理的成长之路
      LeetCode 160. 相交链表(C++)
      【目标检测】一文干翻xml文件的读取
      【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
    • 原文地址:https://blog.csdn.net/holily_/article/details/126603256