关于vue.js:腾讯前端vue面试题合集

46次阅读

共计 16774 个字符,预计需要花费 42 分钟才能阅读完成。

能说下 vue-router 中罕用的 hash 和 history 路由模式实现原理吗?

(1)hash 模式的实现原理

晚期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简略,location.hash 的值就是 URL 中 # 前面的内容。比方上面这个网站,它的 location.hash 的值为 ‘#search’:

https://www.word.com#search

hash 路由模式的实现次要是基于上面几个个性:

  • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 局部不会被发送;

hash 值的扭转,都会在浏览器的拜访历史中减少一个记录。因而咱们能通过浏览器的回退、后退按钮管制 hash 的切换;

  • 能够通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会产生扭转;或者应用 JavaScript 来对 loaction.hash 进行赋值,扭转 URL 的 hash 值;
  • 咱们能够应用 hashchange 事件来监听 hash 值的变动,从而对页面进行跳转(渲染)。

(2)history 模式的实现原理

HTML5 提供了 History API 来实现 URL 的变动。其中做最次要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 能够在不进行刷新的状况下,操作浏览器的历史纪录。惟一不同的是,前者是新增一个历史记录,后者是间接替换以后的历史记录,如下所示:

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

history 路由模式的实现次要基于存在上面几个个性:

  • pushState 和 repalceState 两个 API 来操作实现 URL 的变动;
  • 咱们能够应用 popstate 事件来监听 url 的变动,从而对页面进行跳转(渲染);
  • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时咱们须要手动触发页面跳转(渲染)。

v-show 与 v-if 有什么区别?

v-if 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是 惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简略得多——不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的“display”属性进行切换。

所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景。

$nextTick 原理及作用

Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种利用。

nextTick 的外围是利用了如 Promise、MutationObserver、setImmediate、setTimeout 的原生 JavaScript 办法来模仿对应的微 / 宏工作的实现,实质是为了利用 JavaScript 的这些异步回调工作队列来实现 Vue 框架中本人的异步回调队列。

nextTick 不仅是 Vue 外部的异步队列的调用办法,同时也容许开发者在理论我的项目中应用这个办法来满足理论利用中对 DOM 更新数据机会的后续逻辑解决

nextTick 是典型的将底层 JavaScript 执行原理利用到具体案例中的示例,引入异步更新队列机制的起因∶

  • 如果是同步更新,则屡次对一个或多个属性赋值,会频繁触发 UI/DOM 的渲染,能够缩小一些无用渲染
  • 同时因为 VirtualDOM 的引入,每一次状态发生变化后,状态变动的信号会发送给组件,组件外部应用 VirtualDOM 进行计算得出须要更新的具体的 DOM 节点,而后对 DOM 进行更新操作,每次更新状态后的渲染过程须要更多的计算,而这种无用功也将节约更多的性能,所以异步渲染变得更加至关重要

Vue 采纳了数据驱动视图的思维,然而在一些状况下,依然须要操作 DOM。有时候,可能遇到这样的状况,DOM1 的数据产生了变动,而 DOM2 须要从 DOM1 中获取数据,那这时就会发现 DOM2 的视图并没有更新,这时就须要用到了 nextTick 了。

因为 Vue 的 DOM 操作是异步的,所以,在下面的状况中,就要将 DOM2 获取数据的操作写在 $nextTick 中。

this.$nextTick(() => {    // 获取数据的操作...})

所以,在以下状况下,会用到 nextTick:

  • 在数据变动后执行的某个操作,而这个操作须要应用随数据变动而变动的 DOM 构造的时候,这个操作就须要办法在 nextTick() 的回调函数中。
  • 在 vue 生命周期中,如果在 created()钩子进行 DOM 操作,也肯定要放在 nextTick() 的回调函数中。

因为在 created()钩子函数中,页面的 DOM 还未渲染,这时候也没方法操作 DOM,所以,此时如果想要操作 DOM,必须将操作的代码放在 nextTick() 的回调函数中。

为什么 Vuex 的 mutation 中不能做异步操作?

  • Vuex 中所有的状态更新的惟一路径都是 mutation,异步操作通过 Action 来提交 mutation 实现,这样能够不便地跟踪每一个状态的变动,从而可能实现一些工具帮忙更好地理解咱们的利用。
  • 每个 mutation 执行实现后都会对应到一个新的状态变更,这样 devtools 就能够打个快照存下来,而后就能够实现 time-travel 了。如果 mutation 反对异步操作,就没有方法晓得状态是何时更新的,无奈很好的进行状态的追踪,给调试带来艰难。

Computed 和 Watch 的区别

对于 Computed:

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

对于 Watch:

  • 它不反对缓存,数据变动时,它就会触发相应的操作
  • 反对异步监听
  • 监听的函数接管两个参数,第一个参数是最新的值,第二个是变动之前的值
  • 当一个属性发生变化时,就须要执行相应的操作
  • 监听数据必须是 data 中申明的或者父组件传递过去的 props 中的数据,当发生变化时,会触发其余操作,函数有两个的参数:

    • immediate:组件加载立刻触发回调函数
    • deep:深度监听,发现数据外部的变动,在简单数据类型中应用,例如数组中的对象发生变化。须要留神的是,deep 无奈监听到数组和对象外部的变动。

当想要执行异步或者低廉的操作以响应一直的变动时,就须要应用 watch。

总结:

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

使用场景:

  • 当须要进行数值计算, 并且依赖于其它数据时,应该应用 computed,因为能够利用 computed 的缓存个性,防止每次获取值时都要从新计算。
  • 当须要在数据变动时执行异步或开销较大的操作时,应该应用 watch,应用 watch 选项容许执行异步操作 (拜访一个 API),限度执行该操作的频率,并在失去最终后果前,设置中间状态。这些都是计算属性无奈做到的。

谈谈对 keep-alive 的理解

keep-alive 能够实现组件的缓存,当组件切换时不会对以后组件进行卸载。罕用的 2 个属性 include/exclude,2 个生命周期 activated deactivated

参考 前端进阶面试题具体解答

Vue 路由的钩子函数

首页能够管制导航跳转,beforeEachafterEach等,个别用于页面 title 的批改。一些须要登录能力调整页面的重定向性能。

  • beforeEach次要有 3 个参数tofromnext
  • toroute行将进入的指标路由对象。
  • fromroute以后导航正要来到的路由。
  • nextfunction肯定要调用该办法 resolve 这个钩子。执行成果依赖 n ext办法的调用参数。能够管制网页的跳转

v-model 的原理?

咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:

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

以 input 表单元素为例:

<input v-model='something'>

相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:<ModelChild v-model="message"></ModelChild>

子组件:<div>{{value}}</div>

props:{value: String},
methods: {test1(){this.$emit('input', '小红')
  },
},

理解 nextTick 吗?

异步办法,异步渲染最初一步,与 JS 事件循环分割严密。次要应用了宏工作微工作(setTimeoutpromise那些),定义了一个异步办法,屡次调用 nextTick 会将办法存入队列,通过异步办法清空以后队列。

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

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

vue 中应用了哪些设计模式

1. 工厂模式 – 传入参数即可创立实例

虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode

2. 单例模式 – 整个程序有且仅有一个实例

vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉

3. 公布 - 订阅模式 (vue 事件机制)

4. 观察者模式 (响应式数据原理)

5. 装璜模式: (@装璜器的用法)

6. 策略模式 策略模式指对象有某个行为, 然而在不同的场景中, 该行为有不同的实现计划 - 比方选项的合并策略

Vue 中 v -html 会导致哪些问题

  • 可能会导致 xss 攻打
  • v-html 会替换掉标签外部的子元素
let template = require('vue-template-compiler'); 
let r = template.compile(`<div v-html="'<span>hello</span>'"></div>`) 

// with(this){return _c('div',{domProps: {"innerHTML":_s('<span>hello</span>')}})} 
console.log(r.render);

// _c 定义在 core/instance/render.js 
// _s 定义在 core/instance/render-helpers/index,js
if (key === 'textContent' || key === 'innerHTML') {if (vnode.children) vnode.children.length = 0 
    if (cur === oldProps[key]) continue // #6601 work around Chrome version <= 55 bug where single textNode // replaced by innerHTML/textContent retains its parentNode property 
    if (elm.childNodes.length === 1) {elm.removeChild(elm.childNodes[0]) 
    } 
}

Composition API 与 Options API 有什么不同

剖析

Vue3最重要更新之一就是 Composition API,它具备一些列长处,其中不少是针对Options API 裸露的一些问题量身打造。是 Vue3 举荐的写法,因而把握好 Composition API 利用对把握好 Vue3 至关重要

What is Composition API?(opens new window)

  • Composition API呈现就是为了解决 Options API 导致雷同性能代码扩散的景象

体验

Composition API能更好的组织代码,上面用 composition api 能够提取为useCount(),用于组合、复用

compositon api 提供了以下几个函数:

  • setup
  • ref
  • reactive
  • watchEffect
  • watch
  • computed
  • toRefs
  • 生命周期的hooks

答复范例

  1. Composition API是一组 API,包含:Reactivity API 生命周期钩子 依赖注入 ,使用户能够通过导入函数形式编写vue 组件。而 Options API 则通过申明组件选项的对象模式编写组件
  2. Composition API最次要作用是可能简洁、高效复用逻辑。解决了过来 Options APImixins的各种毛病;另外 Composition API 具备更加麻利的代码组织能力,很多用户喜爱 Options API,认为所有货色都有固定地位的选项搁置代码,然而单个组件增长过大之后这反而成为限度,一个逻辑关注点扩散在组件各处,造成代码碎片,保护时须要重复横跳,Composition API 则能够将它们无效组织在一起。最初 Composition API 领有更好的类型推断,对 ts 反对更敌对,Options API在设计之初并未思考类型推断因素,尽管官网为此做了很多简单的类型体操,确保用户能够在应用 Options API 时取得类型推断,然而还是没方法用在 mixinsprovide/inject
  3. Vue3首推 Composition API,然而这会让咱们在代码组织上多花点心理,因而在抉择上,如果咱们我的项目属于中低复杂度的场景,Options API 仍是一个好抉择。对于那些大型,高扩大,强保护的我的项目上,Composition API会取得更大收益

可能的诘问

  1. Composition API是否和 Options API 一起应用?

能够在同一个组件中应用两个 script 标签,一个应用 vue3,一个应用 vue2 写法,一起应用没有问题

<!-- vue3 -->
<script setup>
  // vue3 写法
</script>

<!-- 降级 vue2 -->
<script>
  export default {data() {},
    methods: {}}
</script>

vue-router 中如何爱护路由

剖析

路由爱护在利用开发过程中十分重要,简直每个利用都要做各种路由权限治理,因而相当考查使用者基本功。

体验

全局守卫:

const router = createRouter({...})
​
router.beforeEach((to, from) => {
  // ...
  // 返回 false 以勾销导航
  return false
})

路由独享守卫:

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

组件内的守卫:

const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {// 在渲染该组件的对应路由被验证前调用},
  beforeRouteUpdate(to, from) {// 在以后路由扭转,然而该组件被复用时调用},
  beforeRouteLeave(to, from) {// 在导航来到渲染该组件的对应路由时调用},
}

答复

  • vue-router中爱护路由的办法叫做路由守卫,次要用来通过跳转或勾销的形式守卫导航。
  • 路由守卫有三个级别:全局 路由独享 组件级。影响范畴由大到小,例如全局的router.beforeEach(),能够注册一个全局前置守卫,每次路由导航都会通过这个守卫,因而在其外部能够退出管制逻辑决定用户是否能够导航到指标路由;在路由注册的时候能够退出单路由独享的守卫,例如beforeEnter,守卫只在进入路由时触发,因而只会影响这个路由,管制更准确;咱们还能够为路由组件增加守卫配置,例如beforeRouteEnter,会在渲染该组件的对应路由被验证前调用,管制的范畴更准确了。
  • 用户的任何导航行为都会走 navigate 办法,外部有个 guards 队列按程序执行用户注册的守卫钩子函数,如果没有通过验证逻辑则会勾销原有的导航。

原理

runGuardQueue(guards)链式的执行用户在各级别注册的守卫钩子函数,通过则持续下一个级别的守卫,不通过进入 catch 流程勾销本来导航

// 源码
runGuardQueue(guards)
  .then(() => {
    // check global guards beforeEach
    guards = []
    for (const guard of beforeGuards.list()) {guards.push(guardToPromiseFn(guard, to, from))
    }
    guards.push(canceledNavigationCheck)

    return runGuardQueue(guards)
  })
  .then(() => {
    // check in components beforeRouteUpdate
    guards = extractComponentsGuards(
      updatingRecords,
      'beforeRouteUpdate',
      to,
      from
    )

    for (const record of updatingRecords) {
      record.updateGuards.forEach(guard => {guards.push(guardToPromiseFn(guard, to, from))
      })
    }
    guards.push(canceledNavigationCheck)

    // run the queue of per route beforeEnter guards
    return runGuardQueue(guards)
  })
  .then(() => {
    // check the route beforeEnter
    guards = []
    for (const record of to.matched) {
      // do not trigger beforeEnter on reused views
      if (record.beforeEnter && !from.matched.includes(record)) {if (isArray(record.beforeEnter)) {for (const beforeEnter of record.beforeEnter)
            guards.push(guardToPromiseFn(beforeEnter, to, from))
        } else {guards.push(guardToPromiseFn(record.beforeEnter, to, from))
        }
      }
    }
    guards.push(canceledNavigationCheck)

    // run the queue of per route beforeEnter guards
    return runGuardQueue(guards)
  })
  .then(() => {// NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>

    // clear existing enterCallbacks, these are added by extractComponentsGuards
    to.matched.forEach(record => (record.enterCallbacks = {}))

    // check in-component beforeRouteEnter
    guards = extractComponentsGuards(
      enteringRecords,
      'beforeRouteEnter',
      to,
      from
    )
    guards.push(canceledNavigationCheck)

    // run the queue of per route beforeEnter guards
    return runGuardQueue(guards)
  })
  .then(() => {
    // check global guards beforeResolve
    guards = []
    for (const guard of beforeResolveGuards.list()) {guards.push(guardToPromiseFn(guard, to, from))
    }
    guards.push(canceledNavigationCheck)

    return runGuardQueue(guards)
  })
  // catch any navigation canceled
  .catch(err =>
    isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED)
      ? err
      : Promise.reject(err)
  )

源码地位(opens new window)

如何监听 pushState 和 replaceState 的变动呢?

利用自定义事件 new Event() 创立这两个事件,并全局监听:

<body>
  <button onclick="goPage2()"> 去 page2</button>
  <div>Page1</div>
  <script>
    let count = 0;
    function goPage2 () {history.pushState({ count: count++}, `bb${count}`,'page1.html')
      console.log(history)
    }
    // 这个不能监听到 pushState
    // window.addEventListener('popstate', function (event) {//   console.log(event)
    // })
    function createHistoryEvent (type) {var fn = history[type]
      return function () {
        // 这里的 arguments 就是调用 pushState 时的三个参数汇合
        var res = fn.apply(this, arguments)
        let e = new Event(type)
        e.arguments = arguments
        window.dispatchEvent(e)
        return res
      }
    }
    history.pushState = createHistoryEvent('pushState')
    history.replaceState = createHistoryEvent('replaceState')
    window.addEventListener('pushState', function (event) {// { type: 'pushState', arguments: [...], target: Window, ... }
      console.log(event)
    })
    window.addEventListener('replaceState', function (event) {console.log(event)
    })
  </script>
</body>

Vue 模版编译原理晓得吗,能简略说一下吗?

简略说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经验以下阶段:

  • 生成 AST 树
  • 优化
  • codegen

首先解析模版,生成AST 语法树(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最初一步是 将优化后的 AST 树转换为可执行的代码

怎么缓存以后的组件?缓存后怎么更新

缓存组件应用 keep-alive 组件,这是一个十分常见且有用的优化伎俩,vue3keep-alive 有比拟大的更新,能说的点比拟多

思路

  • 缓存用keep-alive,它的作用与用法
  • 应用细节,例如缓存指定 / 排除、联合 routertransition
  • 组件缓存后更新能够利用 activated 或者beforeRouteEnter
  • 原理论述

答复范例

  1. 开发中缓存组件应用 keep-alive 组件,keep-alivevue 内置组件,keep-alive包裹动静组件 component 时,会缓存不流动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,避免反复渲染DOM
<keep-alive>
  <component :is="view"></component>
</keep-alive>
  1. 联合属性 includeexclude能够明确指定缓存哪些组件或排除缓存指定组件。vue3中联合 vue-router 时变动较大,之前是 keep-alive 包裹 router-view,当初须要反过来用router-view 包裹keep-alive
<router-view v-slot="{Component}">
  <keep-alive>
    <component :is="Component"></component>
  </keep-alive>
</router-view>
  1. 缓存后如果要获取数据,解决方案能够有以下两种
  2. beforeRouteEnter:在有 vue-router 的 我的项目,每次进入路由的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){
  next(vm=>{console.log(vm)
    // 每次进入路由执行
    vm.getData()  // 获取数据})
},
  • actived:在 keep-alive 缓存的组件被激活的时候,都会执行 actived 钩子
activated(){this.getData() // 获取数据
},
  1. keep-alive是一个通用组件,它外部定义了一个 map,缓存创立过的组件实例,它返回的渲染函数外部会查找内嵌的component 组件对应组件的 vnode,如果该组件在map 中存在就间接返回它。因为 componentis属性是个响应式数据,因而只有它变动,keep-aliverender 函数就会从新执行

理论工作中,你总结的 vue 最佳实际有哪些

从编码格调、性能、平安等方面说几条:

编码格调方面:

  • 命名组件时应用“多词”格调防止和 HTML 元素抵触
  • 应用“细节化”形式定义属性而不是只有一个属性名
  • 属性名申明时应用“驼峰命名”,模板或 jsx 中应用“肉串命名”
  • 应用 v-for 时务必加上 key,且不要跟v-if 写在一起

性能方面:

  • 路由懒加载缩小利用尺寸
  • 利用 SSR 缩小首屏加载工夫
  • 利用 v-once 渲染那些不须要更新的内容
  • 一些长列表能够利用虚构滚动技术防止内存适度占用
  • 对于深层嵌套对象的大数组能够应用 shallowRefshallowReactive升高开销
  • 防止不必要的组件形象

平安:

  • 不应用不可信模板,例如应用用户输出拼接模板:template: <div> + userProvidedString + </div>
  • 防止应用 v-html:url:style 等,防止htmlurl、款式等注入

Vue 我的项目中有封装过 axios 吗?次要是封装哪方面的?

一、axios 是什么

axios 是一个轻量的 HTTP客户端

基于 XMLHttpRequest 服务来执行 HTTP 申请,反对丰盛的配置,反对 Promise,反对浏览器端和 Node.js 端。自Vue2.0 起,尤大发表勾销对 vue-resource 的官网举荐,转而举荐 axios。当初 axios 曾经成为大部分 Vue 开发者的首选

个性

  • 从浏览器中创立 XMLHttpRequests
  • node.js 创立 http申请
  • 反对 Promise API
  • 拦挡申请和响应
  • 转换申请数据和响应数据
  • 勾销申请
  • 主动转换JSON 数据
  • 客户端反对进攻XSRF

根本应用

装置

// 我的项目中装置
npm install axios --S
// cdn 引入
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

导入

import axios from 'axios'

发送申请

axios({        
  url:'xxx',    // 设置申请的地址
  method:"GET", // 设置申请办法
  params:{      // get 申请应用 params 进行参数凭借, 如果是 post 申请用 data
    type: '',
    page: 1
  }
}).then(res => {  
  // res 为后端返回的数据
  console.log(res);   
})

并发申请axios.all([])

function getUserAccount() {return axios.get('/user/12345');
}

function getUserPermissions() {return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
    .then(axios.spread(function (res1, res2) { 
    // res1 第一个申请的返回的内容,res2 第二个申请返回的内容
    // 两个申请都执行实现才会执行
}));

二、为什么要封装

axios 的 API 很敌对,你齐全能够很轻松地在我的项目中间接应用。

不过随着我的项目规模增大,如果每发动一次 HTTP 申请,就要把这些比方设置超时工夫、设置申请头、依据我的项目环境判断应用哪个申请地址、错误处理等等操作,都须要写一遍

这种重复劳动不仅浪费时间,而且让代码变得冗余不堪,难以保护。为了进步咱们的代码品质,咱们应该在我的项目中二次封装一下 axios 再应用

举个例子:

axios('http://localhost:3000/data', {
  // 配置代码
  method: 'GET',
  timeout: 1000,
  withCredentials: true,
  headers: {
    'Content-Type': 'application/json',
    Authorization: 'xxx',
  },
  transformRequest: [function (data, headers) {return data;}],
  // 其余申请配置...
})
.then((data) => {
  // todo: 真正业务逻辑代码
  console.log(data);
}, (err) => {
  // 错误处理代码  
  if (err.response.status === 401) {// handle authorization error}
  if (err.response.status === 403) {// handle server forbidden error}
  // 其余错误处理.....
  console.log(err);
});

如果每个页面都发送相似的申请,都要写一堆的配置与错误处理,就显得过于繁琐了

这时候咱们就须要对 axios 进行二次封装,让应用更为便当

三、如何封装

  • 封装的同时,你须要和 后端协商好一些约定,申请头,状态码,申请超时工夫 …….
  • 设置接口申请前缀:依据开发、测试、生产环境的不同,前缀须要加以辨别
  • 申请头 : 来实现一些具体的业务,必须携带一些参数才能够申请(例如:会员业务)
  • 状态码: 依据接口返回的不同status,来执行不同的业务,这块须要和后端约定好
  • 申请办法:依据 getpost 等办法进行一个再次封装,应用起来更为不便
  • 申请拦截器: 依据申请的申请头设定,来决定哪些申请能够拜访
  • 响应拦截器:这块就是依据 后端 ` 返回来的状态码断定执行不同业务

设置接口申请前缀

利用 node 环境变量来作判断,用来辨别开发、测试、生产环境

if (process.env.NODE_ENV === 'development') {axios.defaults.baseURL = 'http://dev.xxx.com'} else if (process.env.NODE_ENV === 'production') {axios.defaults.baseURL = 'http://prod.xxx.com'}

在本地调试的时候,还须要在 vue.config.js 文件中配置 devServer 实现代理转发,从而实现跨域

devServer: {
    proxy: {
      '/proxyApi': {
        target: 'http://dev.xxx.com',
        changeOrigin: true,
        pathRewrite: {'/proxyApi': ''}
      }
    }
  }

设置申请头与超时工夫

大部分状况下,申请头都是固定的,只有少部分状况下,会须要一些非凡的申请头,这里将普适性的申请头作为根底配置。当须要非凡申请头时,将非凡申请头作为参数传入,笼罩根底配置

const service = axios.create({
    ...
    timeout: 30000,  // 申请 30s 超时
      headers: {
        get: {
          'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
          // 在开发中,个别还须要单点登录或者其余性能的通用申请头,能够一并配置进来
        },
        post: {
          'Content-Type': 'application/json;charset=utf-8'
          // 在开发中,个别还须要单点登录或者其余性能的通用申请头,能够一并配置进来
        }
  },
})

封装申请办法

先引入封装好的办法,在要调用的接口从新封装成一个办法裸露进来

// get 申请
export function httpGet({
  url,
  params = {}}) {return new Promise((resolve, reject) => {
    axios.get(url, {params}).then((res) => {resolve(res.data)
    }).catch(err => {reject(err)
    })
  })
}

// post
// post 申请
export function httpPost({
  url,
  data = {},
  params = {}}) {return new Promise((resolve, reject) => {
    axios({
      url,
      method: 'post',
      transformRequest: [function (data) {
        let ret = ''
        for (let it in data) {ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
        }
        return ret
      }],
      // 发送的数据
      data,
      // url 参数
      params

    }).then(res => {resolve(res.data)
    })
  })
}

把封装的办法放在一个 api.js 文件中

import {httpGet, httpPost} from './http'
export const getorglist = (params = {}) => httpGet({url: 'apps/api/org/list', params})

页面中就能间接调用

// .vue
import {getorglist} from '@/assets/js/api'

getorglist({id: 200}).then(res => {console.log(res)
})

这样能够把 api 对立治理起来,当前保护批改只须要在 api.js 文件操作即可

申请拦截器

申请拦截器能够在每个申请里加上 token,做了对立解决后保护起来也不便

// 申请拦截器
axios.interceptors.request.use(
  config => {
    // 每次发送申请之前判断是否存在 token
    // 如果存在,则对立在 http 申请的 header 都加上 token,这样后盾依据 token 判断你的登录状况,此处 token 个别是用户实现登录后贮存到 localstorage 里的
    token && (config.headers.Authorization = token)
    return config
  },
  error => {return Promise.error(error)
  })

响应拦截器

响应拦截器能够在接管到响应后先做一层操作,如依据状态码判断登录状态、受权

// 响应拦截器
axios.interceptors.response.use(response => {
  // 如果返回的状态码为 200,阐明接口申请胜利,能够失常拿到数据
  // 否则的话抛出谬误
  if (response.status === 200) {if (response.data.code === 511) {// 未受权调取受权接口} else if (response.data.code === 510) {// 未登录跳转登录页} else {return Promise.resolve(response)
    }
  } else {return Promise.reject(response)
  }
}, error => {
  // 咱们能够在这里对异样状态作对立解决
  if (error.response.status) {
    // 解决申请失败的状况
    // 对不同返回码对相应解决
    return Promise.reject(error.response)
  }
})

小结

  • 封装是编程中很有意义的伎俩,简略的 axios 封装,就能够让咱们能够领略到它的魅力
  • 封装 axios 没有一个相对的规范,只有你的封装能够满足你的我的项目需要,并且用起来不便,那就是一个好的封装计划

Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题?你能说说如下代码的实现原理么?

1)Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题

  1. Vue 应用了 Object.defineProperty 实现双向数据绑定
  2. 在初始化实例时对属性执行 getter/setter 转化
  3. 属性必须在 data 对象上存在能力让 Vue 将它转换为响应式的(这也就造成了 Vue 无奈检测到对象属性的增加或删除)

所以 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)

2)接下来咱们看看框架自身是如何实现的呢?

Vue 源码地位:vue/src/core/instance/index.js

export function set (target: Array<any> | Object, key: any, val: any): any {
  // target 为数组  
  if (Array.isArray(target) && isValidArrayIndex(key)) {// 批改数组的长度, 防止索引 > 数组长度导致 splcie()执行有误
    target.length = Math.max(target.length, key)
    // 利用数组的 splice 变异办法触发响应式  
    target.splice(key, 1, val)
    return val
  }
  // key 曾经存在,间接批改属性值  
  if (key in target && !(key in Object.prototype)) {target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // target 自身就不是响应式数据, 间接赋值
  if (!ob) {target[key] = val
    return val
  }
  // 对属性进行响应式解决
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

咱们浏览以上源码可知,vm.$set 的实现原理是:

  1. 如果指标是数组,间接应用数组的 splice 办法触发相应式;
  2. 如果指标是对象,会先判读属性是否存在、对象是否是响应式,
  3. 最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决

defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法

正文完
 0