vue实现双向绑定和依赖收集遇到的坑


Posted in Javascript onNovember 29, 2018

在掘金上买了一个关于解读vue源码的小册,因为是付费的,所以还比较放心

在小册里看到了关于vue双向绑定和依赖收集的部分,总感觉有些怪怪的,然后就自己跟着敲了一遍。 敲完后,发现完全无法运行,  坑啊,  写书人完全没有测试过。

然后自己完善代码, 越写越发现坑, 问题有些大。。。。。。

最后自己重新实现了一遍,代码较多。 用到观察订阅者模式实现依赖收集, Object.defineProperty() 实现双向绑定

/*
  自己写的代码, 实现vue的双向绑定和依赖收集
  场景: 多个子组件用到父组件data中的数据, 当父组件data中的此数据发生改变时, 
  所有依赖它的 子组件全部更新
  通常子组件的从父组件中拿取的数据不允许发生改变
*/
  //订阅者 Dep
  //一个订阅者只管理一个数据
  class Dep {
    constructor () {
      this.subs = []  //存放vue组件
    }
    addSubs (sub) {
      this.subs.push(sub)
      console.log('add watcher: ', sub._name)
    }
    notify () {
      this.subs.forEach( sub => {  //通知vue组件更新
        sub.update()
      })
    }
  }
  //监听者
  //一个vue实例包含一个Watcher实例
  class Watcher {
    // 在实例化Watcher时, 将Dep的target指向此实例, 在依赖收集中使用
    // 因为依赖收集是在组件初始化时触发的, 而数据变更后视图相应变更是在初始化后
    // 所以让Dep.target指向此实例, 当此vue实例初始化完成后, 再指向下一个正在初始化的vue实例完成依赖收集
    constructor (name) {
      Dep.target = this
      this._name = name
    }
    update () {
      // 这里模拟视图更新
      // 其实还应该让子组件的props相应值与父组件更新的数据同步
      console.log("子组件视图更新了..." + this._name)
    }
  }
  //对data中的数据设置读写监听, 并且创建订阅者, 用于收集子组件的依赖和发布
  function defineReactive (obj, key, value) {
    // 对vue实例中data对象的每一个属性都 设置一个订阅者Dep
    let dep = new Dep()
    // 第二个vue实例的监听 覆盖了第一个vue实例的监听, 因为引用的obj是同一个
    Object.defineProperty(obj, key, {
      configurable: true,
      enumerable: true,
      get () {  
      // 在读此属性时, 将当前 watcher 对象收集到此属性的 dep 对象中
      // 在实例化vue时将Dep.target指向当前Watcher
      // get()依赖收集的时候是vue组件初始化的时候, set()是在初始化后
        if (dep.subs.indexOf(Dep.target) === -1) {
          dep.addSubs(Dep.target)
        }
        //return obj[key]   此写法报错 提示栈溢出 原因是无限调用get()
        return value
      },
      set (newVal) {  // 此属性改变时, 通知所有视图更新
        if (newVal !== value) {
          value = newVal
          dep.notify()  
        }
      }
    })
  }
  //接收一个对象作为参数, 将该对象的所有属性调用defineReactive设置读写监听
  function observer (obj) {
    if (!obj || (typeof obj !== 'object')) {
      return 
    }
    Object.keys(obj).forEach( key => {
      defineReactive(obj, key, obj[key])
    }) 
  }
  // 构造函数, 监听 配置options中的data()方法返回的对象的所有属性 的读写
  class Vue {
    constructor (options) {
      this._name = options.name
      this._data = options.data
      // 每个vue组件都是一个vue实例, 在一个页面中有多个vue实例
      // 在初始化该vue实例时, new一个Watcher对象, 使Dep.target指向此实例
      new Watcher(options.name)
      // 给data中的数据挂载读写监听
      observer(this._data)
      //模拟vue解析template过程, 获取从父组件传递过来的props
      //在这里进行依赖收集
      this._props = options.props ? getProps() : {}
      // 实例化该组件的子组件
      this._children = options.render ? (options.render() || {}) : {}
    }
  }
  // 父组件数据
  let data = {
    first: "hello",
    second: 'world',
    third: ['啦啦啦']
  }
  let times = 0
  // 第一次调用返回的是第一个子组件的从父组件继承的数据(vue中props属性的值)
  // 第二次调用返回的是第二个子组件的从父组件继承的数据(vue中props属性的值)
  function getProps () {
    times++
    if (times == 1) {
      let obj = {first: "", second: ""}
      Object.keys(obj).forEach( key => {
        // 如果是对象, 则进行深拷贝
        // 这里使用到了父组件的数据, 触发依赖收集
        if (data[key] instanceof Object) {
          obj[key] = JSON.parse(JSON.stringify(data[key]))
        } else {
          obj[key] = data[key]
        } 
      })
      return obj
    } else if (times == 2) {
      let obj = {first: "", third: ""}
      Object.keys(obj).forEach( key => {
        if (data[key] instanceof Object) {
          obj[key] = JSON.parse(JSON.stringify(data[key]))
        } else {
          obj[key] = data[key]
        } 
      })
      return obj
    }  
  }
   let vue_root = new Vue({
     name: 'vue_root',
     data,
     //模拟编译template和实例化vue的过程 
     //在编译父组件 并且传递参数给子组件时, 将子组件的 watcher 添加进父组件的 dep
     render () {
       let vue_1 = new Vue({
         name: 'vue_1',
         data: {},
         props: true,
         render () {}
       }) 
       let vue_2 = new Vue({
         name: 'vue_2',
         data: {},
         props: true,
         render () {}
       }) 
       return {
         vue_1,
         vue_2
       }
     }
   })
  console.log(vue_root)
   vue_root._data.first = 'hello hello'  // vue_1 和 Vue_2 都依赖此数据, 都更新
   vue_root._data.third = "aaa"      // 只有 vue_2 依赖到了此数据, 更新

总结

以上所述是小编给大家介绍的vue的双向绑定和依赖收集遇到的坑,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript 原型与继承说明
Jun 09 Javascript
Confirmer JQuery确认对话框组件
Jun 09 Javascript
Firefox中autocomplete="off" 设置不起作用Bug的解决方法
Mar 25 Javascript
js中如何复制一个对象并获取其所有属性和属性对应的值
Oct 24 Javascript
Javascript技术难点之apply,call与this之间的衔接
Dec 04 Javascript
基于BootStrap与jQuery.validate实现表单提交校验功能
Dec 22 Javascript
用原生js做单页应用
Jan 17 Javascript
浅析vue中常见循环遍历指令的使用 v-for
Apr 18 Javascript
Javascript中绑定click事件的四种方式介绍
Oct 26 Javascript
jQuery cookie的公共方法封装和使用示例
Jun 01 jQuery
解决vue无法侦听数组及对象属性的变化问题
Jul 17 Javascript
JavaScript实现点击自制菜单效果
Feb 02 Javascript
js中this的指向问题归纳总结
Nov 28 #Javascript
基于vue实现移动端圆形旋钮插件效果
Nov 28 #Javascript
VUE2.0 ElementUI2.0表格el-table自适应高度的实现方法
Nov 28 #Javascript
Vue触发式全局组件构建的方法
Nov 28 #Javascript
Vue axios全局拦截 get请求、post请求、配置请求的实例代码
Nov 28 #Javascript
jQuery实现购物车的总价计算和总价传值功能
Nov 28 #jQuery
vue与原生app的对接交互的方法(混合开发)
Nov 28 #Javascript
You might like
解决GD中文乱码问题
2007/02/14 PHP
PHP采集利器 Snoopy 试用心得
2011/07/03 PHP
PHP运行SVN命令显示某用户的文件更新记录的代码
2014/01/03 PHP
Parse正式发布开源PHP SDK
2014/08/11 PHP
javaScript 数值型和字符串型之间的转换
2009/07/25 Javascript
基于JQuery的列表拖动排序实现代码
2013/10/01 Javascript
继续学习javascript闭包
2015/12/03 Javascript
js阻止浏览器默认行为触发的通用方法(推荐)
2016/05/15 Javascript
值得分享的轻量级Bootstrap Table表格插件
2016/05/30 Javascript
基于hover的用法实例(推荐)
2017/07/04 Javascript
使用D3.js制作图表详解
2017/08/13 Javascript
[36:17]DOTA2上海特级锦标赛 - VGL音乐会全集
2016/03/06 DOTA
[55:25]2018DOTA2亚洲邀请赛3月29日 小组赛A组 VG VS OG
2018/03/30 DOTA
Python中的字典与成员运算符初步探究
2015/10/13 Python
在CentOS上配置Nginx+Gunicorn+Python+Flask环境的教程
2016/06/07 Python
Python中的异常处理相关语句基础学习笔记
2016/07/11 Python
python机器学习之决策树分类详解
2017/12/20 Python
django2 快速安装指南分享
2018/01/05 Python
python的concat等多种用法详解
2018/11/28 Python
python中使用 xlwt 操作excel的常见方法与问题
2019/01/13 Python
python  ceiling divide 除法向上取整(或小数向上取整)的实例
2019/12/27 Python
Python reshape的用法及多个二维数组合并为三维数组的实例
2020/02/07 Python
TensorFlow2.0矩阵与向量的加减乘实例
2020/02/07 Python
Python+Django+MySQL实现基于Web版的增删改查的示例代码
2020/05/13 Python
Html5实现单张、多张图片上传功能
2019/04/28 HTML / CSS
澳大利亚拥有最佳跳伞降落点和最好服务的跳伞项目运营商:Skydive Australia
2018/03/05 全球购物
车间班长岗位职责
2013/11/30 职场文书
小学生开学第一课活动方案
2014/03/27 职场文书
法律专业自荐信
2014/06/03 职场文书
财会专业大学生求职信
2014/09/26 职场文书
发展党员工作情况汇报
2014/10/28 职场文书
庐山导游词
2015/02/03 职场文书
用电申请报告范文
2015/05/18 职场文书
反邪教学习心得体会
2016/01/15 职场文书
2019餐饮行业创业计划书!
2019/06/27 职场文书
Python机器学习应用之工业蒸汽数据分析篇详解
2022/01/18 Python