2025年01月09日德美医疗前端面试

时间:2025-05-07 08:24:24

目录

  1. vue2 的双向绑定的原理
  2. vue3 的双向绑定原理
  3. vue 的生命周期
  4. vue 子组件为何不能修改父组件的值
  5. js delete 删除数组的某一个值会怎么样
  6. vue 和 react 的 diff 算法
  7. 什么是闭包
  8. 原型链
  9. this指向

vue2 的双向绑定的原理

以下是 Vue 2 双向绑定的原理:

1. 核心概念

Vue 2 的双向绑定是通过数据劫持和发布订阅模式实现的。它允许数据和视图之间的双向通信,即数据的改变可以更新视图,视图的操作也可以更新数据。

2. 实现步骤

数据劫持

Vue 2 使用 Object.defineProperty 对数据对象的属性进行劫持,在属性被访问或修改时添加自定义的行为。

function defineReactive(obj, key, value) {
  let dep = new Dep(); // 创建一个空的订阅者列表
  Object.defineProperty(obj, key, {
    enumerable: true, // 允许属性被枚举
    configurable: true, // 允许属性被修改
    get: function() {
      if (Dep.target) {
        dep.addSub(Dep.target);
      }
      return value;
    },
    set: function(newVal) {
      if (value!== newVal) {
        value = newVal;
        dep.notify();
      }
    }
  });
}

在这个函数中:

  • defineReactive 函数使用 Object.definePropertyobjkey 属性进行定义。
  • get 方法:当该属性被访问时,如果 Dep.target 存在(在 Vue 中,Dep.target 通常是当前正在编译的 Watcher),将其添加到 dep 的订阅者列表中。
  • set 方法:当该属性被修改时,如果新值与旧值不同,更新属性值并通知 dep 的订阅者列表中的所有订阅者。

依赖收集

每个组件实例都有一个 Watcher 对象,它是一个订阅者,当组件的模板中使用到某个数据时,会触发该数据的 get 方法,将该 Watcher 添加到该数据的订阅者列表中。

class Watcher {
  constructor(vm, exp, cb) {
    this.vm = vm;
    this.exp = exp;
    this.cb = cb;
    this.value = this.get();
  }
  get() {
    Dep.target = this;
    let value = this.vm[this.exp];
    Dep.target = null;
    return value;
  }
  update() {
    let newValue = this.vm[this.exp];
    let oldValue = this.value;
    if (newValue!== oldValue) {
      this.value = newValue;
      this.cb.call(this.vm, newValue, oldValue);
    }
  }
}

在这个类中:

  • Watcherget 方法会将自己设置为 Dep.target,并访问数据,触发数据的 get 方法,从而将自己添加到该数据的订阅者列表中。
  • update 方法会在数据更新时被调用,调用回调函数 cb 更新视图。

发布订阅模式

Dep 类是一个简单的发布者,它维护一个订阅者列表,并在数据变化时通知订阅者。

class Dep {
  constructor() {
    this.subs = [];
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  removeSub(sub) {
    this.subs = this.subs.filter(s => s!== sub);
  }
  notify() {
    this.subs.forEach(sub => sub.update());
  }
}

在这个类中:

  • constructor 方法创建一个空的订阅者列表。
  • addSub(sub) 方法添加一个订阅者。
  • removeSub(sub) 方法移除一个订阅者。
  • notify() 方法通知所有订阅者更新。
3. 整体流程
  • 当 Vue 实例化时,会对 data 属性中的数据进行遍历,使用 defineReactive 进行数据劫持。
  • 当编译模板时,会创建 Watcher 对象,在使用数据时触发数据的 get 方法,将 Watcher 添加到该数据的订阅者列表中。
  • 当数据发生变化时,触发数据的 set 方法,通知 Watcher 进行更新,Watcher 调用 update 方法更新视图。
4. 示例代码
<div id="app">
  <input v-model="message">
  {{ message }}
</div>
function Vue(options) {
  this.data = options.data;
  observe(this.data);
  new Compile('#app', this);
}

function observe(data) {
  if (!data || typeof data!== 'object') return;
  Object.keys(data).forEach(key => {
    defineReactive(data, key, data[key]);
  });
}

function Compile(el, vm) {
  this.vm = vm;
  this.el = document.querySelector(el);
  this.compileElement(this.el);
}

Compile.prototype.compileElement = function (el) {
  let childNodes = el.childNodes;
  Array.from(childNodes).forEach(node => {
    if (node.nodeType === 1) {
      // 元素节点
      this.compileElement(node);
    } else if (node.nodeType === 3) {
      // 文本节点
      this.compileText(node);
    }
  });
};

Compile.prototype.compileText = function (node) {
  let reg = /\{\{(.*?)\}\}/g;
  let value = node.textContent;
  if (reg.test(value)) {
    let exp = RegExp.$1.trim();
    node.textContent = value.replace(reg, this.vm.data[exp]);
    new Watcher(this.vm, exp, function (newVal) {
      node.textContent = value.replace(reg, newVal);
    });
  }
};

let app = new Vue({
  data: {
    message: 'Hello, Vue!'
  }
});
代码解释

Vue 函数

  • 接收 options,初始化 data 属性,并调用 observe 对数据进行观察。
  • 调用 Compile 进行模板编译。

observe 函数

  • 遍历 data 对象,对每个属性使用 defineReactive 进行数据劫持。

Compile

  • 编译模板元素,对于文本节点,如果存在 {{...}} 插值表达式,使用 Watcher 进行数据监听和更新。

Watcher

  • 在实例化时,会将自己添加到数据的订阅者列表中,并在更新时更新视图。

Dep

  • 作为发布者,管理订阅者列表,在数据更新时通知订阅者。
面试回答示例

“Vue 2 的双向绑定是通过数据劫持和发布订阅模式实现的。首先,使用 Object.defineProperty 对数据对象的属性进行劫持,在属性的 get 方法中进行依赖收集,将使用该数据的 Watcher 订阅者添加到该数据的订阅者列表中,在 set 方法中,当数据发生变化时通知订阅者列表中的 WatcherWatcher 是一个订阅者,负责更新视图,它会在实例化时将自己添加到数据的订阅者列表中,并在更新时调用回调函数更新视图。Dep 类是一个发布者,负责维护订阅者列表并通知订阅者更新。当 Vue 实例化时,会对 data 中的数据进行劫持,在模板编译时,会创建 Watcher 进行依赖收集,从而实现数据和视图的双向绑定。这样,当数据变化时,视图会更新;当用户操作视图(如通过 v-model)时,会触发数据的更新,形成双向绑定的效果。”

通过这样的解释,可以向面试官展示你对 Vue 2 双向绑定原理的深入理解,包括数据劫持、依赖收集、发布订阅模式的使用以及整体的工作流程。

2. vue3 的双向绑定原理

Vue 3 的双向绑定机制相比 Vue 2 有了显著改进,主要通过 组合式 APIProxy 实现。以下是完整解析:

一、核心机制演进
特性 Vue 2 Vue 3
响应式基础 Object.defineProperty Proxy
检测范围 对象属性 完整对象
数组检测 需特殊方法 原生支持
性能 递归转换属性 惰性代理
二、响应式系统核心实现
1. reactive() - 对象响应化
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track(target, key) // 依赖收集
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      Reflect.set(target, key, value, receiver)
      trigger(target, key) // 触发更新
      return true
    }
  })
}
2. ref() - 原始值响应化
function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return value
    },
    set value(newVal) {
      value = newVal
      trigger(refObject, 'value')
    }
  }
  return refObject
}
三、依赖收集与触发流程
  1. 依赖收集阶段

    组件渲染
    读取响应式数据
    触发getter
    将当前effect存入dep
  2. 触发更新阶段

    数据变更
    触发setter
    从dep取出effect
    执行effect重新渲染
四、v-model 双向绑定实现
组件示例:
<CustomInput v-model="searchText" />

<!-- 等价于 -->
<CustomInput 
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>
组件实现:
defineProps(['modelValue'])
defineEmits(['update:modelValue'])

const emitUpdate = (e) => {
  emit('update:modelValue', e.target.value)
}
五、性能优化策略
  1. 编译时优化

    • 静态节点提升(Hoist Static)
    • 补丁标志(Patch Flags)
    • 树结构拍平(Tree Flattening)
  2. 响应式优化

    • 依赖关系缓存(effect缓存)
    • 批量异步更新(nextTick合并)
  3. 源码结构优化

    // 惰性代理示例
    function shallowReactive(obj) {
      const proxy = new Proxy(obj, handlers)
      // 不立即递归代理嵌套对象
      return proxy 
    }
    
六、与 Vue 2 的对比升级
  1. 数组处理改进

    // Vue 2 需要特殊处理
    this.$set(this.items, index, newValue)
    
    // Vue 3 直接操作
    state.items[index] = newValue // 自动触发更新
    
  2. 动态属性检测

    // Vue 2 无法检测新增属性
    this.$set(this.obj, 'newProp', value)
    
    // Vue 3 自动检测
    state.newProp = value // 自动响应
    
七、开发注意事项
  1. 响应式丢失场景

    // 解构会导致响应式丢失
    const { x, y } = reactive({ x: 1, y: 2 })
    
    // 正确做法
    const pos = reactive({ x: 1, y: 2 })
    const { x, y } = toRefs(pos)
    
  2. 性能敏感操作

    // 大数据量使用shallowRef/shallowReactive
    const bigList = shallowRef([])
    
    // 非响应式数据使用markRaw
    const foo = markRaw({ complex: object })
    

Vue 3 的双向绑定通过 Proxy 实现了更精细的依赖跟踪,配合编译时优化,在保证开发体验的同时提供了更好的运行时性能。理解其原理有助于编写更高效的 Vue 代码。

3. vue 的生命周期

一、Vue 2 和 Vue 3 生命周期对比
生命周期钩子对照表
Vue 2 选项式API Vue 3 组合式API 触发时机描述
beforeCreate 无直接对应 实例初始化前,data/methods未初始化
created setup() 实例创建完成,data/methods可用
beforeMount onBeforeMount 挂载开始前,DOM尚未生成
mounted onMounted 挂载完成,DOM已生成
beforeUpdate onBeforeUpdate 数据变化导致DOM更新前
updated onUpdated 数据变化导致DOM更新后
beforeDestroy onBeforeUnmount 实例销毁前(vue3改名更准确)
destroyed onUnmounted 实例销毁后(vue3改名更准确)
activated onActivated keep-alive组件激活时
deactivated onDeactivated keep-alive组件停用时
errorCaptured onErrorCaptured 捕获子孙组件错误时
二、生命周期完整流程图示