5

一、Vue核心面试高频知识点

1、vue计算属性的理解

模板内可以放表达式,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

所以,对于任何复杂逻辑,都应当使用计算属性。

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
computed计算属性实质上就是对模版语法的加强,可以让我们编写更为复杂的逻辑。

2、父组件如何向子组件传值

  • 传入一个静态属性

传入的静态属性会绑定在组件内最外层元素上

<blog-post title="My journey with Vue"></blog-post>
  • 传入任何形式的变量

数字、字符串、对象、数组等

<blog-post v-bind="post"></blog-post>
  • 传递自定义事件
<a @select="selectItem"></a>

3、如何在组件中共享全局常量

├── src
│   ├── const
│   │    ├── const.js
│   │    
│   └── main.js
└── ...

在 const.js 文件下,设置常量

// const.js
export default {
    install(Vue,options){

        Vue.prototype.global = {
            title:'全局',
            isBack: true,
            isAdd:  false,
        };
        
    }

 }

引入

//引入全局常量
import constant from './const/const.js'
Vue.use(constant);

然后我们就可以在任何地方使用了

//通过js方式使用:
this.global.title
//或在 html 结构中使用
{{global.title}}

4、计算属性的缓冲与方法调用的不同之处

1、计算属性必须返回值
2、计算属性是基于他的依赖来自动执行的,只有当依赖属性发生变化了,它才会重新计算
3、method可以不需要返回值,它依赖事件的调用,不会自动执行
4、当你在抉择的时候,你要注意你是否需要缓冲数据,如果需要那你就使用计算属性。

5、自定义指令你需要知道哪些?

通常我们自定义一些指令是满足我们对DOM操作的需求

Vue里面有许多内置的指令,比如v-if和v-show,这些丰富的指令能满足我们的绝大部分业务需求,不过在需要一些特殊功能时,我们仍然希望对DOM进行底层的操作,这时就要用到自定义指令。

自定义指令的几个钩子函数:

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

钩子函数的参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM 。
  • binding:一个对象,包含以下属性:

    name:指令名,不包括 v- 前缀。
    value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为2。
    oldValue:指令绑定的前一个值,仅在update和 componentUpdated钩子中可用。无论值是否改变都可用。
    expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
    arg:传给指令的参数,可选。例如 v-my-directive:foo中,参数为 "foo"。
    modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为{ foo: true, bar: true }。
    vnode:Vue 编译生成的虚拟节点。

  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
  bind: function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
      'name: '       + s(binding.name) + '<br>' +
      'value: '      + s(binding.value) + '<br>' +
      'expression: ' + s(binding.expression) + '<br>' +
      'argument: '   + s(binding.arg) + '<br>' +
      'modifiers: '  + s(binding.modifiers) + '<br>' +
      'vnode keys: ' + Object.keys(vnode).join(', ')
  }
})

new Vue({
  el: '#hook-arguments-example',
  data: {
    message: 'hello!'
  }
})

clipboard.png

6、面试必考之组件通信:

Vue面试必问,组件直接通信之超详细易懂

7、watch考点

Vue.js中watch的高级用法,深度和注销观察

vue技术分享-你可能不知道的7个秘密

二、vue-router路由面试知识点

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。

1、一个简单的路由实例,来分析$route和$router的不同

// router.js
import Vue from "vue";
import Router from "vue-router"; // 【1】引入router
import Home from "./views/Home.vue"; // 组件引入

Vue.use(Router); // 【2】使用

// 【3】new Router(param)
//  param 是一个路由配置对象
export default new Router({
  routes: [
    {
      path: "/",
      name: "home",
      component: Home
    },
    {
      path: "/about",
      name: "about",
      // 懒加载
      component: () =>
        import(/* webpackChunkName: "about" */ "./views/About.vue")
    }
  ]
});

引入路由配置

// main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router"; // 【1】引入

new Vue({
  router,  // 【2】绑定路由
  render: h => h(App)
}).$mount("#app");
// app.vue
<template>
  <div id="app">
      <router-link to="/">首页</router-link>
      <router-link to="/about">Abour</router-link>
      <p>
          <router-view></router-view>
      </p>
  </div>
</template>

<script>
    export default {
        name: "APP",
        data () {
            return {

            }
        }
    };
</script>

这样就可以实现简单的路由切换。

通过Home组件来区分$route和$router的区别:

<template>
  <div class="home">
    Home
    <button @click="goBack">返回</button>
  </div>
</template>

<script>

export default {
  name: "home",
  mounted () {
    console.log(this.$route);
  },
  methods: {
    goBack() {
      this.$router.go(-1);
    }
  }
};
</script>
this.$router来访问路由器,进行一些路由跳转等操作。this.$route可以拿到当前路由信息。

比如: this.$router.go(-1);来实现回退,console.log(this.$route);来查看当前路由。
我们可以在this.$route中拿到路由信息,比如一些参数和查询条件等。
clipboard.png

vue-router提供了params、query、meta三种页面间传递参数的方式。
// 字符串,不带参数 /home
this.$router.push('home') 

// 对象,不带参数 /home
this.$router.push({ path: 'home' })

// params(推荐):命名的路由,params 必须和 name 搭配使用 /user/123
this.$router.push({ name:'user',params: { userId: 123 }})

// 这里的 params 不生效
this.$router.push({ path:'/user',params: { userId: 123 }})

// query:带查询参数,变成 /register?plan=private
this.$router.push({ path: 'register', query: { plan: 'private' }})

//meta方式:路由元信息
export default new Router({
    routes: [
        {
            path: '/user',
            name: 'user',
            component: user,
            meta:{
                title:'个人中心'
            }
        }
    ]
})
//通过 $route 对象获取,注意是route,么有r
this.$route.params

this.$route.query

this.$route.meta

2、动态路由

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。这时候我们需要设置动态路由参数来实现:

{
    // // 动态路径参数 以冒号开头
    path: '/user/:id',
    name: 'user',
    component: User
}

一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。于是,我们可以更新 User 的模板,输出当前用户的 ID:

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

你可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params 中。例如:

clipboard.png

注意点

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

使用watch监听:

const User = {
  template: '...',
  watch: {
    '$route' (to, from) {
      // 对路由变化作出响应...
    }
  }
}
  • 解决办法二:
<template>
    <div class="user">
        <h1>This is an user page</h1>
        <p>{{$route.params.id}}</p>
        <router-link to="/user/22">去22</router-link>
    </div>
</template>

<script>
    export  default {
        created() {
            console.log("created");
        },
        // 使用导航守卫
        beforeRouteUpdate (to, from, next) {
            // react to route changes...
            // don't forget to call next()
            console.log(to, from , next);
        }
    }
</script>

3、嵌套路由(子路由)

如果我们想实现/user/12/more或者是user/userother这类的路由,而且要在user组件下去动态匹配内容的显示。这时候就可以使用我们的嵌套路由。

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
          // 当 /user/:id/profile 匹配成功,
          // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /user/:id/posts 匹配成功
          // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

这时候对我们的User组件也需要做一定的处理,添加一个<router-view></router-view>

const User = {
  template: `
    <div class="user">
      <h2>User {{ $route.params.id }}</h2>
      <router-view></router-view>
    </div>
  `
}

这时候我们匹配/user/12/profile这些都是可以的了~

触发跳转可以是:

<router-link :to="'/userprofile'+ $route.params.id +'/'"></router-link>
// 或者是

<router-link :to="profile" append></router-link>

如果没匹配到,我们想要让他显示一个提示性的页面的时候,在怎么做呢?

定义一个空白子路由,如果匹配不到路由会匹配该路由。

const router = new VueRouter({
  routes: [
    {
      path: '/user/:id', component: User,
      children: [
        // 当 /user/:id 匹配成功,
        // UserHome 会被渲染在 User 的 <router-view> 中
        { path: '', component: UserHome },

        // ...其他子路由
      ]
    }
  ]
})

4、编程式路由

有时候我们需要js去动态的跳转路由,编程式路由很合适。

在 Vue 实例内部,你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。

clipboard.png

// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由,提供路由的名称
router.push({ name: 'user', params: { userId: 123 }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
注意点: 如果提供了 path,params 会被忽略,上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path:
const userId = 123
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123

// 这里的 params 不生效,所以不要这么写
router.push({ path: '/user', params: { userId }}) // -> /user

router.replace(location, onComplete?, onAbort?)

跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。之后回退是不会回退回去的,因为history队列中已经没了

router.go(n)

这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。

// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)

// 后退一步记录,等同于 history.back()
router.go(-1)

// 前进 3 步记录
router.go(3)

// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)

clipboard.png

5、路由的两种模式(前端路由)

vue-router路由提供了两种路由模式:hash模式和history模式。

hash模式

它是利用hash来模拟完整的Url,通过hashChange来监听hash变化,当页面变化的时候不会刷新页面。

history模式

如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

//设置mode属性,设置路由模式
const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

6、路由导航钩子

vue-router提供了一些导航守卫来,控制我们的路由跳转和守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。

全局守卫【全局前置守卫】

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

导航守卫是异步的,因此导航在守卫完成之前都是等待状态,因此需要 我们手动resolve.

  • to: 要进入的目标路由
  • from: 要离开的路由
  • next: 一定要调用该方法来 resolve 这个钩子

    next();一定要调用该方法来 resolve 这个钩子
    next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前导航中断。

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

路由独享的守卫

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

参数同全局前置守卫。

组件内守卫

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

使用场景:

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}
// 路由发生变化的时候
beforeRouteUpdate (to, from, next) {
  // just use `this`
  this.name = to.params.name
  next()
}

这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。

beforeRouteLeave (to, from , next) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (answer) {
    next()
  } else {
    next(false)
  }
}

7、完整的导航解析流程

导航被触发。
在失活的组件里调用离开守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

好长呀,感觉记不住:

我们来简化以下,记住一些关键步骤。

本组件调用离开守卫 -- 全局的beforeEach -- 全局的before Resolve -- 导航确认,-- 调用全局的afterEach -- DOM更新 --


Meils
1.6k 声望157 粉丝

前端开发实践者