Vue2 常见面试题与答案

256 阅读5分钟

vue响应式原理

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

具体步骤:

第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter,这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化

第二步:compile 解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

第三步:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:

  1. 在自身实例化时往属性订阅器(dep)里面添加自己
  2. 自身必须有一个 update()方法
  3. 待属性变动 dep.notice()通知时,能调用自身的 update()方法,并触发 Compile 中绑定的回调,则功成身退。

第四步:MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。

讲一下event-loop

js分为主线程、调用栈和任务队列,主线程调用执行栈,先执行同步任务,再执行异步任务,等待异步回调结果,这些回调在任务队列里面等待主线程空闲时调用,先执行微任务回调,再执行下面的宏任务回调,微任务比较常见的就是Promise和MutationObserver(监听DOM变化),宏任务就是js执行代码,setTimeout,setInterval等等,dom渲染不在事件循环中,事件循环只有js

参考:zhuanlan.zhihu.com/p/55511602

说说为什么使用Vue.nextTick()及原理

因为vue数据更新不会立马更新dom,而是会把多次更新操作汇总一次更新,这时候就会导致拿最新的dom数据不一定是最新的,所以这么使用。 原理就是通过MutationObserver监听dom变化,回调拿到最新的dom数据。nexttick实现其实是看支持方式,支持哪个采用哪个,查看支持promise,查看支持mutationobserver,查看支持setimmediate,查看支持settimeout,最终执行传入的方法

为什么要使用vue的key

key可以保持高效渲染,但是不能使用index,要使用唯一值比如id,如果使用index,在进行增删操作的时候,在这个item下面的dom都会相应有变化,因为key都变了,所以被重新渲染,这会影响性能,如果是个select,还会导致因使用index导致的bug。

Vue性能优化你们是怎么做的?

  • 图片懒加载
  • 路由懒加载
  • 全局引入组件改为局部引入
  • 组件引入方式改为异步引入
  • cdn优化,在index.html引入vue、vue-router、axios等链接,在webpack的config文件修改externals
  • 开启gzip,productionGzip改为true
  • 关闭默认开启prefetch(预先加载模块)
  • 生产环境关闭sourcemap
  • webpack js css 压缩
  • webpack 对图片进行压缩
  • webpack 用splitChunks拆分大的chunk文件
  • 骨架屏
  • 利用vue分析工具分析出比较大的包,使用轻量级的工具库,比如将moment改为dayjs
  • 不修改的纯展示数据可以使用Object.freeze冻结数据
  • 服务器渲染

ES6 数组内对象去重

let person = [
     {id: 0, name: "小明"},
     {id: 1, name: "小张"},
     {id: 2, name: "小李"},
     {id: 3, name: "小孙"},
     {id: 1, name: "小周"},
     {id: 2, name: "小陈"},   
];

let obj = {};
let peon = person.reduce((cur,next) => {
    obj[next.id] ? "" : obj[next.id] = true && cur.push(next);
    return cur;
},[]) // 设置cur默认类型为数组,并且初始值为空的数组
console.log(peon)

// or

let obj = {}
let cur = []
person.map((v,i,a) => {
    obj[v.id] ? "" : obj[v.id] = true &&  cur.push(v)
})
console.log(cur)

vue中slot和slot-scope有什么区别?

slot是默认插槽,比如在子组件中想要自定义一些内容的话就需要使用到。

slot-scope是作用域插槽,"父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。",比如一个列表循环,有一个数据想要放到循环中的子组件中去,但是因为父组件作用域的关系,导致子组件里没法拿到item数据,所以这时候就需要通过传入item数据,再通过slot-scope拿到item数据。

vue自定义指令怎么使用,钩子函数有哪些?

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  • bind:只调用一次,指令第一次绑定到元素时调用
  • inserted:被绑定元素插入父节点时调用
  • update:所在组件的 VNode 更新时调用
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
  • unbind:只调用一次,指令与元素解绑时调用

钩子函数参数

  • el:指令所绑定的元素,可以用来直接操作 DOM
  • binding:一个对象,里面包含很多具体的信息
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用