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 相关文章推荐
鼠标经过的文本框textbox变色
May 21 Javascript
JavaScript对象、属性、事件手册集合方便查询
Jul 04 Javascript
Extjs4.0设置Ext.data.Store传参的请求方式(默认为GET)
Apr 02 Javascript
深入理解javascript中的立即执行函数(function(){…})()
Jun 12 Javascript
jQuery的ready方法详解
Nov 27 Javascript
使用jQuery给input标签设置默认值
Jun 20 Javascript
JavaScript基于原型链的继承
Jun 22 Javascript
微信小程序实现动态设置页面标题的方法【附源码下载】
Nov 29 Javascript
vue实现模态框的通用写法推荐
Feb 26 Javascript
解决JS表单验证只有第一个IF起作用的问题
Dec 04 Javascript
基于vue手写tree插件的那点事儿
Aug 20 Javascript
在vue中使用jsonp进行跨域请求接口操作
Oct 29 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
PHP file_get_contents 函数超时的几种解决方法
2009/07/30 PHP
PHP编程实现计算抽奖概率算法完整实例
2017/08/09 PHP
RR vs IO BO3 第二场2.13
2021/03/10 DOTA
Javascript学习笔记9 prototype封装继承
2010/01/11 Javascript
利用javascript的面向对象的特性实现限制试用期
2011/08/04 Javascript
基于jQuery实现的图片切换焦点图整理
2014/12/07 Javascript
基于BootStarp的Dailog
2016/04/28 Javascript
利用angular.copy取消变量的双向绑定与解析
2016/11/25 Javascript
Node.js用readline模块实现输入输出
2016/12/16 Javascript
input输入密码变黑点密文的实现方法
2017/01/09 Javascript
BootStrap组件之进度条的基本用法
2017/01/19 Javascript
js replace()去除代码中空格的实例
2017/02/14 Javascript
angular.js 路由及页面传参示例
2017/02/24 Javascript
Vue中使用vee-validate表单验证的方法
2018/05/09 Javascript
详解webpack4升级指南以及从webpack3.x迁移
2018/06/12 Javascript
vue解决弹出蒙层滑动穿透问题的方法
2018/09/22 Javascript
详解React项目中碰到的IE问题
2019/03/14 Javascript
JS 实现发送短信验证码的“59秒后重新发送验证短信”功能
2019/08/23 Javascript
在vue中根据光标的显示与消失实现下拉列表
2019/09/29 Javascript
Vue实现圆环进度条的示例
2021/02/06 Vue.js
[48:38]DOTA2亚洲邀请赛 3.31 小组赛 B组 Mineski vs Secret
2018/03/31 DOTA
[42:24]完美世界DOTA2联赛PWL S2 LBZS vs FTD.C 第三场 11.27
2020/12/01 DOTA
[01:48:04]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Elephant BO3 第一场 2月7日
2021/03/11 DOTA
Python中使用logging模块代替print(logging简明指南)
2014/07/09 Python
python Django模板的使用方法
2016/01/14 Python
Python对切片命名的实现方法
2018/10/16 Python
详解Python读取yaml文件多层菜单
2019/03/23 Python
使用Python打造一款间谍程序的流程分析
2020/02/21 Python
python 判断txt每行内容中是否包含子串并重新写入保存的实例
2020/03/12 Python
Django ModelForm操作及验证方式
2020/03/30 Python
python 批量下载bilibili视频的gui程序
2020/11/20 Python
英国知名奢侈品包包品牌:Milli Millu
2016/12/22 全球购物
公司委托书范本5篇
2014/09/20 职场文书
红楼梦读书笔记
2015/06/25 职场文书
老人节主持词
2015/07/04 职场文书
大学校园餐饮创业计划书
2019/08/07 职场文书