理解vue2中的watch监听

文章详细描述了Vue组件中watch方法的初始化流程,包括createWatcher函数的执行以及watcher实例的创建。同时对比了watch和computed的特点,强调了watch的灵活性、实时性及深度监听功能,适用于数据变更后的自定义逻辑和深层结构变化监听。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. vue实例组件初始化过程中,在执行initState(vm)方法初始化状态时,判断options.watch有值时会进行initWatch(vm, options.watch)处理,然后对watch对象中的每个watch属性执行createWatcher方法
function initState(vm) {
  // 传入的watch
  if (options.watch) { 
    initWatch(vm, options.watch)
  }
}
function initWatch(vm, watch) {
  for (let key in watch) {
    let handler = watch[key]
    // 这里不写数组的情况了,平时没用过
    createWatcher(vm, key, handler)
  }
}
  1. 执行createWatcher方法,拿到watch属性的具体回调函数,再执行vm.$watch方法
function createWatcher(vm, expOrFn, handler, options) {
  // 对象
  if (handler.toString() === '[object Object]') {
    options = handler
    handler = handler.handler
  }
  // 监听的方法是methods里的方法
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}
  1. 执行$watch方法,给属性创建一个对应的watcher观察者实例,用来依赖收集。如果设置了immediate:true标识,表示立即执行一次回调函数
Vue.prototype.$watch = function (expOrFn, cb, options) {
  options = options || {}
  // 用户自己设置的watch
  options.user = true 
  let watcher = new Watcher(this, expOrFn, cb, options)
  // 立即执行
  if (options.immediate) {
    pushTarget();
    cb.apply(this, [watcher.value])
    popTarget();
  }
}
  1. 创建watcher实例,进行依赖收集。如果watch的属性是定义在data中的,此watcher实例会被push进data中对应的属性的Dep对象的subs数组中。如果watch的属性是计算属性,此watcher实例会被push进计算属性中所有响应式数据的Dep对象的subs数组中
class Watcher {
  constructor(vm, expOrFn, cb, options) {
    //...
    if (options) {
      this.user = !!options.user;
      this.deep = !!options.deep; // 是否深度监听
      this.lazy = !!options.lazy;
    }
    this.vm = vm;
    this.cb = cb; // 回调函数
    this.active = true;
    // 获取watch属性在vm实例上对应的值
    this.getter = parsePath(expOrFn); 
    this.value = this.lazy ? undefined : this.get();
  }
  get() {
    pushTarget(this);
    let vm = this.vm;
    let value = this.getter.call(vm, vm);
    // 深度监听
    if (this.deep) {
      traverse(value);
    }
    popTarget();
    return value;
  }
  update() {
  	// 进入异步队列,这个之前讲过,会触发this.run()
    queueWatcher(this);
  }
  run() {
    let value = this.get();
    if (value !== this.value || isObject(value) || this.deep) {
      let oldValue = this.value;
      this.value = value;
      if (this.user) {
        // 执行回调函数
        this.cb.apply(this.vm, [value, oldValue]);
      }
    }
  }
}

// 获取watch属性在vm实例上对应的值
// 可以是data中定义的属性,也可以是计算属性
function parsePath(path) {
  let segments = path.split(".");
  return function (obj) {
    if (!obj) return;
    for (let i = 0; i < segments.length; i++) {
      obj = obj[segments[i]];
    }
    return obj;
  };
}
function isObject(obj) {
  return obj !== null && typeof obj === "object";
}

// 递归处理触发每一个属性的getter,形成深度依赖收集
let seenObjects = new Set();
function traverse(val) {
  _traverse(val, seenObjects);
  seenObjects.clear(); // 清空
  return val;
}
function _traverse(val, seen) {
  let i;
  let keys;
  if (val.__ob__) {
    var depId = val.__ob__.dep.id;
    // 处理循环引用的情况
    if (seen.has(depId)) {
      return;
    }
    seen.add(depId);
  }
  if (Array.isArray(val)) {
    i = val.length;
    while (i--) _traverse(val[i], seen);
  } else {
    keys = Object.keys(val);
    i = keys.length;
    // 触发响应式数据的getter
    while (i--) _traverse(val[keys[i]], seen);
  }
}
  1. 当监听的响应式数据发生变化时,触发watcher.update()方法,最终执行watcher.run()方法,执行回调函数。

总结:watch与computed的区别

  1. watch无缓存功能,所监听的响应式属性值发生变化时,都会立即触发回调函数;
  2. watch更灵活,可以监听任何响应式数据或表达式的变更;
  3. watch支持深度监听,可支持在回调函数中执行异步操作,如网络请求等等。适用场景:数据变更后执行自定义逻辑,如网络请求、DOM操作。需要监听深层对象结构变化时。
  4. computed依赖其他数据属性进行计算,并返回计算结果,必须return出去一个值;
  5. computed具有缓存功能,计算结果进行缓存,提高性能。适用场景:所需的值是要根据多个响应式属性计算得出,并且会被频繁使用的值;
<think>好的,我现在需要解决用户的问题:在Vue2中使用watch监听会话存储(sessionStorage)的变化,并提供示例。首先,我得回顾一下用户提到的引用内容,虽然这些引用主要是关于Vue监听storage的原因和方法,但用户现在明确询问的是Vue2的具体实现。 首先,用户可能已经知道在Vue中,直接使用Vuex可能过于繁琐,所以转而使用sessionStorage来共享数据,但需要监听其变化。根据引用[1]和[2],用户可能已经了解在Vue监听storage的必要性,但需要具体的Vue2示例。 接下来,我需要考虑Vue2的响应式系统。Vue2的核心是使用Object.defineProperty来实现数据劫持,但sessionStorage并不是响应式的,因此直接watch sessionStorage的某个键值变化是不可行的。必须通过其他方式触发响应式更新。 可能的解决方案包括: 1. 使用自定义事件监听storage事件,当sessionStorage变化时触发回调。不过,需要注意的是,storage事件只在同源下的不同窗口或标签页之间触发,同一页面内的修改不会触发。因此,这种方法可能不适用于单页面内的修改,需要结合其他方法。 2. 创建一个响应式的变量,将其与sessionStorage的某个键值绑定,并在需要修改sessionStorage时,同时更新这个响应式变量,从而触发Vuewatch。 例如,在组件的data中定义一个变量,如sessionData,然后在需要修改sessionStorage的时候,同时更新this.sessionData,这样watch就可以监听sessionData的变化。但这种方法需要在每次修改sessionStorage时手动同步数据,可能不够自动。 或者,可以重写sessionStorage的方法,比如setItem,在调用时触发自定义事件,然后在Vue组件中监听这个事件,从而更新响应式变量。这种方法可以自动捕获所有对sessionStorage的修改,包括来自其他组件的调用。 另外,用户提供的引用[3]是关于Vue3的watch用法,但用户现在需要的是Vue2的示例,所以需要注意Vue2Vue3在组合式API上的差异,Vue2使用的是选项式API,如watch选项或this.$watch方法。 结合上述思路,具体的实现步骤可能如下: 1.Vue组件的data中定义一个响应式变量,用于同步sessionStorage中的值。 2. 在created或mounted钩子中,初始化这个变量,从sessionStorage读取初始值。 3. 重写sessionStorage的setItem方法,在每次设置新值时,触发一个自定义事件,比如sessionStorageChange。 4. 在组件中监听这个自定义事件,当事件触发时,更新响应式变量,从而触发watch。 或者,另一种方法是在每次修改sessionStorage时,手动更新响应式变量,这样watch就可以捕捉到变化。但这种方法需要确保所有修改sessionStorage的地方都同步更新变量,可能在代码维护上存在问题。 考虑到同一页面内修改sessionStorage不会触发storage事件,所以必须通过自定义事件或手动触发更新。因此,比较可行的方法是在组件内部维护一个响应式变量,并在修改sessionStorage时更新该变量,或者在组件中定时轮询检查sessionStorage的变化,但轮询效率较低。 综合来看,最佳实践可能是结合响应式变量和重写sessionStorage的方法。例如,在Vue的原型上扩展方法,或者在组件内部重写setItem,以便在调用时更新响应式变量。 现在,我需要将上述思路转化为具体的代码示例,确保在Vue2中有效。例如,在组件中: data() { return { storedValue: sessionStorage.getItem('key') || '' } }, watch: { storedValue(newVal) { // 处理变化 } }, methods: { updateStoredValue() { this.storedValue = sessionStorage.getItem('key'); } }, created() { // 重写setItem方法 const originalSetItem = sessionStorage.setItem; sessionStorage.setItem = (key, value) => { originalSetItem.call(sessionStorage, key, value); if (key === 'targetKey') { this.storedValue = value; } }; // 监听storage事件(跨页面) window.addEventListener('storage', (e) => { if (e.key === 'targetKey') { this.storedValue = e.newValue; } }); } 不过,这种方法中的重写setItem可能只能捕获通过setItem方法进行的修改,直接对sessionStorage的属性赋值(如sessionStorage.key = 'value')可能无法捕获。此外,在同一个页面中修改sessionStorage时,storage事件不会被触发,因此必须依赖自定义的重写方法。 因此,在Vue2中,监听sessionStorage的变化需要结合这两种方式:同一页面内的修改通过重写setItem来触发更新,而跨页面的修改则通过storage事件。同时,确保响应式变量storedValue被正确更新,从而触发watch。 最后,需要提醒用户注意,直接修改sessionStorage可能不会触发Vue的响应式系统,因此必须通过封装的方法来确保同步更新响应式变量。</think>在Vue2监听`sessionStorage`的变化需要通过响应式变量结合自定义事件实现。由于`sessionStorage`本身不具备响应式特性,以下是具体实现方法: --- ### 实现步骤 #### 1. 创建响应式变量 在组件的`data`中定义与`sessionStorage`关联的变量: ```javascript data() { return { sessionData: sessionStorage.getItem('yourKey') || '' } } ``` #### 2. 监听变量变化 使用`watch`监听响应式变量: ```javascript watch: { sessionData(newVal) { console.log('sessionStorage值变化为:', newVal); // 在此处执行相关操作 } } ``` #### 3. 重写sessionStorage方法 在`created`生命周期中重写`sessionStorage.setItem`方法,使其触发响应式更新: ```javascript created() { const originalSetItem = sessionStorage.setItem; sessionStorage.setItem = (key, value) => { originalSetItem.call(sessionStorage, key, value); if (key === 'yourKey') { this.sessionData = value; // 触发watch } }; // 监听跨页面事件(可选) window.addEventListener('storage', (e) => { if (e.key === 'yourKey') { this.sessionData = e.newValue; } }); } ``` --- ### 完整示例 ```html <template> <div> <p>当前值: {{ sessionData }}</p> <button @click="updateStorage">修改sessionStorage</button> </div> </template> <script> export default { data() { return { sessionData: sessionStorage.getItem('demoKey') || '' } }, watch: { sessionData(newVal) { console.log('检测到变化:', newVal); } }, methods: { updateStorage() { const newValue = Date.now().toString(); sessionStorage.setItem('demoKey', newValue); // 触发响应式更新 } }, created() { const originalSetItem = sessionStorage.setItem; sessionStorage.setItem = (key, value) => { originalSetItem.call(sessionStorage, key, value); if (key === 'demoKey') { this.sessionData = value; } }; window.addEventListener('storage', (e) => { if (e.key === 'demoKey') { this.sessionData = e.newValue; } }); } } </script> ``` --- ### 关键说明 1. **重写`setItem`**:通过拦截`sessionStorage.setItem`方法,在修改值时同步更新响应式变量`sessionData`[^1]。 2. **跨页面监听**:`storage`事件仅在其他浏览器标签页修改`sessionStorage`时触发,同一页面需通过重写方法实现响应式[^2]。 3. **直接赋值不生效**:若直接通过`sessionStorage.key = 'value'`修改值,需改用`setItem`方法。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值