vue

vue 作为近几年很流行的一个前端框架,现在国内有大量的前端项目基于vue开发,因此求职面试一般都会问一些与Vue相关的问题。

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

在当前组件的style中加入scoped:<style scoped>,scoped 有限制在某范围的含义,如果不设置,容易引起样式错乱!

v-if 和v-show的异同?

  • v-if指令是直接销毁和重建DOM达到让元素显示和隐藏的效果,在切换时开销更高,更适合不经常切换的场景;
  • v-show指令是通过修改元素的display CSS属性让其显示或者隐藏,在初始渲染时有更高的开销,但是切换开销很小,更适合于频繁切换的场景。

computed 和 watch 区别?

  • watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作,一般在监听到值的变化需要做一些复杂业务逻辑的情况下使用;
  • computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容,一般在需要依赖别的属性来动态获得值的时候使用。

组件之间的如何传值?

  • 父子组件通信
    • 父组件通过标签上面定义传值
    • 子组件通过props方法接受数据
    • 子组件通过$emit方法传递参数
  • 兄弟组件通信:通过查找父组件中的子组件实现,也就是 this.$parent.$children,在 $children 中可以通过组件 name 查询到需要的组件实例,然后进行通信。
  • 任意组件通信:可以通过 Vuex 或者 Event Bus 解决。

vue-cli中怎样使用自定义的组件?

  • 在components目录新建你的组件文件;
  • 在需要用的页面(组件)中导入;
  • 注入到vue的组件的components属性上面;
  • 在template视图view中使用。

请谈谈Vue生命周期钩子函数?

  • vue 实例从创建到销毁的过程,就是生命周期;
  • 创建前/后: 在beforeCreate阶段,vue实例的挂载元素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结构依然存在;
  • vue生命周期中的事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

为什么避免 v-if 和 v-for 用在一起?

当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。通过v-if 移动到容器元素,不会再重复遍历列表中的每个值。取而代之的是,我们只检查它一次,且不会在 v-if 为否的时候运算 v-for。

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

  • 根据官网的定义,vue-loader 是 webpack 的一个 loader,用于处理 .vue 文件。
  • 使用vue-cli脚手架,作者已经配置好了基本的配置,开箱及用,你需要做的就是npm install 安装下依赖,然后就可以开发业务代码了。

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

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

vue数据双向绑定的实现原理

双向绑定是对表单来说的,表单的双向绑定,说到底不过是 value 的单向绑定 + onChange 事件侦听的一个语法糖。

利用数据劫持结合发布订阅模式实现的数据双向绑定:

  • observer用来对初始数据通过Object.defineProperty添加setter和getter,当取数据(即调用get)的时候添加订阅对象(watcher)到数组里, 当给数据赋值(即调用set)的时候就能知道数据的变化,此时调用发布订阅中心的notify,从而遍历当前这个数据的订阅数组,执行里面所有的watcher,通知变化update。
  • compiler是用来把data编译到dom中;
  • watcher是oberver和compiler之间通信的桥梁;

vue 为何用 proxy代替 defineProperty

Object.defineProperty是ES5中的方法,它可以直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

vue2利用Object.defineProperty来劫持data数据的getter和setter操作,动态更新绑定的template模块。

然而Object.defineProperty有先天缺陷——无法监听数组变化和只能劫持对象的属性,所以对于数组的监听需要hack它的8种方法,而当属性值也是对象则需要深度遍历,对性能损耗很大。

而proxy一是可以直接监听数组的变化;二是可以直接监听对象而非属性,同时,Proxy作为新标准将受到浏览器厂商重点持续的性能优化,唯一的劣势就是兼容性问题,而且无法用polyfill实现。

通过 Proxy 实现vue 的数据响应

Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作,在 Vue3.0 中已经通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

let p = new Proxy(target, handler)

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

接下来我们通过 Proxy 来实现一个数据响应式

const onWatch = (obj, setLog, getLog) => {
  const handler = {
    get(target, property, receiver) {
      getLog(target, property)
      return Reflect.get(target, property, receiver);
    },
    set(target, property, value, receiver) {
      setLog(value, property);
      return Reflect.set(target, property, value);
    }
  }
  return new Proxy(obj, handler);
}

const obj = { a: 1 }
const p = onWatch(
  obj,
  (v, property) => {
    console.log(`监听到属性${property}改变为${v}`)
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`)
  }
)

我们通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要我们在 get 中收集依赖,在 set 派发更新。

vue3 中为何使用function base api 而不是class api?

  • 更灵活的逻辑复用能力;
  • 更好的TS类型推导;
  • 更好的性能;
  • tree-shaking 更好
  • 代码更容易被压缩

总结

以上面试题,我都在面试过程中问过很多人,而自己去面试也经常被面试官问到,因为自己面试的岗位基本都是架构师或负责人岗位,所以vue的双向绑定原理被问到的最多。