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 相关文章推荐
jquery 表格的增行删行实现思路
Mar 21 Javascript
document.write()及其输出内容的样式、位置控制
Aug 12 Javascript
JavaScript 语言基础知识点总结(思维导图)
Nov 10 Javascript
JS调用页面表格导出excel示例代码
Mar 18 Javascript
20条学习javascript的编程规范的建议
Nov 28 Javascript
JavaScript实现把数字转换成中文
Jun 29 Javascript
jQuery中(function($){})(jQuery)详解
Jul 15 Javascript
javascript实现表单验证
Jan 29 Javascript
js断点调试心得分享(必看篇)
Dec 08 Javascript
JavaScript面试出现频繁的一些易错点整理
Mar 29 Javascript
使用jquery Ajax实现上传附件功能
Oct 23 jQuery
解决vue的touchStart事件及click事件冲突问题
Jul 21 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
Http 1.1 Etag 与 Last-Modified提高php效率
2008/01/10 PHP
php pcntl_fork和pcntl_fork 的用法
2009/04/13 PHP
在WAMP环境下搭建ZendDebugger php调试工具的方法
2011/07/18 PHP
php中filter函数验证、过滤用户输入的数据
2014/01/13 PHP
让CodeIgniter的ellipsize()支持中文截断的方法
2014/06/12 PHP
javascript入门基础之私有变量
2010/02/23 Javascript
FileUpload上传图片(图片不变形)
2010/08/05 Javascript
分享27个jQuery 表单插件集合推荐
2011/04/25 Javascript
用JavaScript仿PS里的羽化效果代码
2011/12/20 Javascript
兼容ie、firefox的图片自动缩放的css跟js代码分享
2013/08/12 Javascript
js编码、解码函数介绍及其使用示例
2013/09/05 Javascript
javascript读取Xml文件做一个二级联动菜单示例
2014/03/17 Javascript
jQuery与getJson结合的用法实例
2015/08/07 Javascript
探寻JavaScript中this指针指向
2016/04/23 Javascript
EasyUi 打开对话框后控件赋值及赋值后不显示的问题解决办法
2017/01/19 Javascript
jQuery动态生成不规则表格(前后端)
2017/02/21 Javascript
详解react-router如何实现按需加载
2017/06/15 Javascript
微信小程序 自定义消息提示框
2017/08/06 Javascript
vue-cli和v-charts实现可视化图表过程解析
2019/10/08 Javascript
JavaScript获取当前url路径过程解析
2019/12/27 Javascript
[44:15]国士无双DOTA2 6.82版本详解(上)
2014/09/28 DOTA
[04:16]DOTA2全国高校联赛16强抽签
2018/05/02 DOTA
详细解析Python中__init__()方法的高级应用
2015/05/11 Python
python中常用的九种预处理方法分享
2016/09/11 Python
pygame游戏之旅 计算游戏中躲过的障碍数量
2018/11/20 Python
Django 大文件下载实现过程解析
2019/08/01 Python
python实现简单井字棋小游戏
2020/03/05 Python
Python Tkinter实例——模拟掷骰子
2020/10/24 Python
python中的列表和元组区别分析
2020/12/30 Python
中国首家奢侈品O2O网购平台:第五大道奢侈品网
2017/12/14 全球购物
彪马英国官网:PUMA英国
2019/02/11 全球购物
Spartoo美国:欧洲排名第一的在线时装零售商
2019/12/12 全球购物
职工趣味运动会方案
2014/02/10 职场文书
班级旅游计划书
2014/05/03 职场文书
Feign调用传输文件异常的解决
2021/06/24 Java/Android
「回转企鹅罐」10周年纪念展「輪るピングドラム展」海报公开
2022/03/22 日漫