>>分享Web前端开发技术,并对孙卫琴的《精通Vue.js:Web前端开发技术详解》提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 15861 个阅读者 刷新本主题
 * 贴子主题:  vue中监听object数据变化的基本原理 回复文章 点赞(0)  收藏  
作者:flybird    发表时间:2021-02-04 07:11:10     消息  查看  搜索  好友  邮件  复制  引用

    # 简略版+自己的注释
// 判断一个变量是否是对象
function isObject(obj) {
  return obj.constructor === Object
}
class Observer {
  constructor(value) {
    this.value = value;
    if (!arr.isArray(value)) {
      this.walk(value);
    }
  }
  walk(obj) {
    const keys = Object.keys(obj);
    // 循环将obj中的每一个属性转换成getter/setter进行变化追踪
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]]);
    }
  }
}
function defineReactive(data, key, val) {
  if (isObject(val)) {
    new Observer(val); // 进行递归调用
  }
  let dep = new Dep();
  Object.defineProperty(data, key, {
    configurable: true,
    enumerable: true,
    get: function () {
      dep.depend();
      return val;
    },
    set: function (newVal) {
      if (val === newVal) return
      // 如果赋值的新值也是一个对象 也需要进行侦测
      if (isObject(newVal)) {
        new Observer(val); // 进行递归调用
      }
      val = newVal;
      dep.notify(); // 通知所有的订阅者,数据要被修改了,做出相应的行为(也就是执行对应的回调函数)
    }
  })
}
class Dep {
  constructor() {
    this.subs = [] // 这个里面存放的是Watch实例对象
  }
  addSub(sub) {
    this.subs.push(sub); // 在这个地方收集订阅者
  }
  removeSub(sub) {
    remove(this.subs, sub);
  }
  depend() {
    if (window.target) {
      this.addSub(window.target); // 在这个地方触发depend方法,进行收集订阅者
    }
  }
  notify() {
    const subs = this.subs.slice();
    for (let i = 0; i < subs.length; i++) {
      subs[i].update(); // 在这个地方执行回调函数
    }
  }
}
function remove(arr, item) {
  if (arr.length) {
    const index = arr[item];
    if (index > -1) {
      return arr.splice(index, 1);
    }
  }
}
class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm;
    this.getter = parsePath(expOrFn);
    this.cb = cb;
    this.value = this.get(); // 获取expOrFn中的值 在这个内部同时会触发getter,从而在dep中添加自己
  }
  get() {
    window.target = this; // 将watch实例对象赋值给全局的target变量上
    let value = this.getter.call(this.vm, this.vm); // 该代码的作用很关键
    // 如果expOrFn直接是一个表达式不是一个函数 eg: 'name', 'age' 假设只有一个属性 不是这种的 'name.a.b'
    // 我们就可以直接下面这样写,只是为了测试理解,vue.js源码处理更加全面
    // let value = this.vm // vm就是要监听的数据,当然expOrFn要在constructor中挂在到this身上
    window.target = undefined;
    return value;
  }
  update() {
    const oldValue = this.value;
    this.value = this.get();
    this.cb.call(this.vm, this.value, oldValue);
  }
}
// 该方法的作用是将 'name.a.b'这种表达式的值取出来
// 也就是通过该方法返回的是 obj['name']['a']['b]的值
function parsePath(path) {
  const bailRE = /[^\w.$]/;
  const segments = path.split('.')
  // 闭包
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]] // 取出属性的值 所以这个地方会执行getter
    }
    return obj; // 返回属性的值
  }
}
<script>
let btns = document.getElementsByTagName('button');
// 定义数据
let person = {};
// 定义响应式数据的属性
defineReactive(person, 'name', '乔峰')
// 监听数据
let w = new Watcher(person, 'name', function (newVal, oldVal) {
  console.log('数据发生变化了')
  console.log(newVal, oldVal);
})
// 1.取值操作
person.name
// 2.改变数据
btns[0].onclick = function () {
  person.name = '小龙女';
}

  # 总结

1.在实例化一个Watcher对象时,其get方法中会触发defineReactive中的getter访问器,在其getter访问器中会执行dep.depend()方法,dep.depend()方法会调用this.subs.push(sub)方法,从而收集依赖,也就是在subs 中存放的是当前的Watcher实例对象

2.Watch实例对象自身必须有一个update方法

3.当数据发生变化时,会触发defineReactive中的setter访问器,在其setter访问器中会调用subs[i].update()方法,其中的每一个subs[i]就是watch实例对象,从而执行了update方法,在update方法中执行了this.cb.call(this.vm, this.value, oldValue)回调函数

3.Observer 类是用来将传递进来的数据遍历转换成响应式数据,也就是转换成getter/setter的形式进行侦测

4.在使用的时候,我们首先需要调用defineReactive方法,来创建一个响应式数据,然后再调用Watcher方法来监听这个响应式数据的变化,然后就能做到数据驱动,或者叫响应式数据

______如有不对,请指正_______

                          

----------------------------
原文链接:https://blog.51cto.com/11871779/2507844

程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 flybird 在 2021-02-18 10:13:09 重新编辑]
  Java面向对象编程-->图形用户界面(下)
  JavaWeb开发-->使用Session(Ⅰ)
  JSP与Hibernate开发-->映射一对多关联关系
  Java网络编程-->用Swing组件展示HTML文档
  精通Spring-->虚拟DOM和render()函数
  Vue3开发-->虚拟DOM和render()函数
  webpack常用插件
  vue3.0-基本特性和用法
  JS 闭包的 9 大经典使用场景
  JavaScript Promise对象的用法
  创建vue cli 插件
  JavaScript实现睡眠函数
  拯救React的hooks:react的问题和hooks的作用
  HTML表单元素的用法
  一个AJAX入门范例
  JavaScript中的HTML DOM Label 对象
  JavaScript的HTML DOM Input Search 对象
  CSS 伪类
  CSS Border(边框)
  HTML中插入脚本的用法
  JavaScript Array(数组)对象
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。