面试题汇总

69 阅读5分钟
1. Vue中data为什么是函数不是对象
Vue实例的时候定义data既可以是一个对象,也可以是一个函数;如果采用HTML引用,可以是对象形式,如果是组件中定义data属性,只能是一个函数
 1. 组件是用来复用的,且JS对象是引用关系,如果组件中的data是对象,那么作用域没有隔离,组件中的data值会相互影响
 2. 如果组件中data是函数,那么每个实例可以维护一份被返回的对象的独立拷贝,组件实例之间的data值不会相互影响
 1. 因为组件可能被用来创建多个实例,如果data是一个对象,修改其中一个实例的数据其他实例的数据也会跟着改变
 2. 如果组件中data是函数,每一次创建实例之后的返回的数据内存地址不同,改变其中一个数据,不会影响其他的数据
2. 封装公共组件需要考虑到哪些因素
开发通用组件是很基础且重要的工作,通用组件必须具备高性能、低耦合的特性
一. 数据从父组件传入
 1. 为了解耦,子组件本身就不能生成数据。即使生成了,也只能在组件内部运行,不能传递出去
 2. 父对子传参,就需要用到props,但是通用组件的应用场景比较复杂,对props传递的参数应该添加一些验证规则
二. 在父组件处理事件
 1. 在通用组件中,通常会需要有各种事件,比如复选框的change事件,或者组件中某个按钮的click事件,这些事件的处理方法应尽量放在父组件中,通用组件本身只作为一个中转
三. 记得留一个slot
 1. 一个通用组件,往往不能够完美的适应所有应用场景,所以在封装组件的时候,只需要完成组件80%的功能,剩余的20%让父组件通过solt解决
四. 不要依赖Vuex
 1.父子组件之间是通过props和自定义事件来传参,非父子组件通常会采用Vuex传参,但是Vuex得设计初衷是用开管理组件状态,虽然可以用来传参,但并不推荐,因为Vuex类似于一个全局变量,会一直占用内存,在写入数据庞大的state的时候,就会产生内存泄漏
五. 合理运用scoped编写CSS
 1. 在编写组件的时候,可以在<style> 标签中添加 scoped,让标签中的样式只对当前组件生效,但是一味的使用 scoped,肯定会产生大量的重复代码,所以在开发的时候,应该避免在组件中写样式,当全局样式写好之后,再针对每个组件,通过 scoped 属性添加组件样式
3.Vue组件之间的通信有哪些
1. 父组件通过 `prop` 向子组件传递数据
2. 子组件通过`$emit`自定义事件向父组件传递数据
3. 父子组件通信 `$parent`/`$children`, `refs`
4. 隔代组件之间的通信`provide`/`inject`
5. 全局事件总线eventBus
6. 状态管理`vuex`
4.什么是高阶函数,什么是高阶组件
`高阶函数`:
 --接收的参数为函数或者返回值是一个函数
 --常见的高阶函数:定时器、Promise、数组的一些方法、函数对象的bind方法
`高阶组件`(一种特别的高阶函数):
 --接收的参数为组件并且返回一个新组件,新组件会向旧组件传递一些特定属性,用于扩展组件的功能
 --例:react-redux中的connect函数为高阶函数,调用connect会返回一个高阶组件
   这个高阶组件接收一个UI 组 件,返回一个容器组件,容器组件会向UI组件传递特定的属性。
`函数柯里化`:
   通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
5. v-if 和v-for能放在一起使用吗
能放在一起使用,但是尽量避免,特殊情况可以用<template>标签包一层或者使用`v-show`,或者利用`computed`过滤
`v-for``v-if`优先,即每一次都需要遍历数组,影响速度。
6. Vue响应式原理

41419ba65a96fdf5977037b9b39a860f.jpeg

`Vue2.X`中通过Object.defineProperty()方法把组件数据(data)设置为`getter``setter`的访问形式结合发布订阅模式进行依赖收集与视图更新
`Observe`,响应式原理的入口,根据数据类型处理观测逻辑,通过Object.keys()递归遍历深层对象属性
`Dep`,依赖收集器,属性都会有一个`Dep`,方便发生变化时能够找到对应的依赖触发更新
`Watcher`,用于执行更新渲染,组件会拥有一个渲染`Watcher`,我们常说的收集依赖,就是收集 `Watcher`
data返回的对象 新增/删除元素或者直接修改数组下标 Object.defineProperty() 方法无法监听到 需要用到 `$set`或者 `$delete` 



`Vue3.X`中通过new Proxy()
const p = new Proxy(target, handler);
// target 目标对象 (可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
// handler 一个对象 其属性是(当执行一个操作时定义代理的行为的函数)
-   get(target, propKey, receiver) 拦截对象属性的读取 比如p.age,p[age];
-   set(target, propKey, receiver) 拦截对象属性的设置 比如 p.age = 18,返回一个布尔值
-   has(target, propKey) 拦截 propKey in proxy 的操作,返回一个布尔值。
-   deleteProperty(target, propKey) 拦截删除属性 delete proxy[foo] 和 delete proxy.foo 以及Reflect.deleteProperty() 返回一个布尔值
Reflect是内置对象,为操作对象而提供的新API,将Object对象的属于语言内部的方法放到Reflect对象上,即从Reflect对象上拿Object对象内部方法。 将用 源Object方法 报错的情况,改为返回false
const obj =  { name: 'peak' };
const p = new Proxy(obj, {
    get:(target, key, receiver)=>{
        console.log(`getting ${key}!`);
        return Reflect.get(target, key, receiver);
    },
    set:(target, key, value, receiver)=>{
        console.log(target, key, value, receiver);
        return Reflect.set(target, key, value, receiver);
    }
});
p.name //'peak' 'getting name' 触发get方法
p.age = 18 //触发set方法
7. Vue diff算法原理是什么,为什么要用虚拟dom
diff算法的鼻祖是snabbdom.js
- h(type, data, children),返回 Virtual DOM 树
- patch(oldVnode, newVnode),比较新旧 Virtual DOM 树并更新
1. diff算法比较新旧节点的时候,比较只会在同层级进行,不会跨级比较
2. patch函数接收两个参数 `oldVnode` 和 `newVnode` 分别代表之前的旧节点和新的节点

function patch (oldVnode, newVnode) {
 if(sameVnode(oldVnode, newVnode)) {
    patchVnode(oldVnode, newVnode)
 } else {
    const oEl = oldVnode.el // 当前oldVnode对应的真实元素节点
    let parentEle = api.parentNode(oEl)// 父元素
    createEle(newVnode)  // 根据Vnode生成新元素
    if (parentEle !==  null) {
      api.insertBefore(parentEle, newVnode.el, api.nextSibling(oEl))  // 将新元素添加进父元素
      api.removeChild(parentEle, oldVnode.el)  // 移除以前的旧元素节点`
      oldVnode =  null
   }
 }
 return vnode
}

//比较是否为同一节点
function sameVnode (a, b) { 
 return (
  a.key === b.key &&  // key值
  a.tag === b.tag &&  // 标签名
  a.isComment === b.isComment &&  // 是否为注释节点
  // 是否都定义了data,data包含一些具体信息,例如onclick , style
  isDef(a.data) === isDef(b.data) &&
  sameInputType(a, b)  // 当标签是<input>的时候,type必须相同
 )
}
采用双向比较来移动节点
--`新前``旧前`
--`新后``旧后`
--`新后``旧前`
--`新前``旧后`
如果都没有命中,就需要用循环来寻找了,如果`oldVnodeChild`有剩余则`remove`节点,如果`newVnodeChild`有剩余则`append`节点

Vue3 新增了静态标记法(Vue2中的虚拟DOM是全量对比,每个节点都会比较)
Vue3 新增了静态标记`patch flag`,在与上次虚拟节点对比时,只对比带有`patch flag`的节点(动态数据所在的节点)


虚拟DOMVue中主要做了两件事情:
1、提供与真实DOM节点所对应的虚拟节点VNode
2、将虚拟节点VNode和旧虚拟节点`oldVNode`进行对比,然后更新视图
具备`跨平台优势`,由于Virtual DOM 是以JavaScript对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台、WeexNode等。
Vue采用虚拟DOM是在各方面平衡的结果,既降低了用户的学习成本与使用成本(写声明式代码),还保证了应用程序的性能下限,让应用程序的性能不至于太差,甚至尽可能地逼近命令式代码的性能
即`声明式代码的更新消耗 = 找出差异的性能消耗 + 直接修改的性能消耗(命令式)`。
各方案性能由高到低如下:  
-   原生JavaScript(心智负担大、可维护性差、性能高)
-   虚拟DOM(心智负担小、可维护性强、性能不错)
-   innerHTML模板(心智负担中等、性能差)