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

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

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

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

  1. /*
  2. 自己写的代码, 实现vue的双向绑定和依赖收集
  3. 场景: 多个子组件用到父组件data中的数据, 当父组件data中的此数据发生改变时,
  4. 所有依赖它的 子组件全部更新
  5. 通常子组件的从父组件中拿取的数据不允许发生改变
  6. */
  7.  
  8. //订阅者 Dep
  9. //一个订阅者只管理一个数据
  10. class Dep {
  11. constructor () {
  12. this.subs = [] //存放vue组件
  13. }
  14. addSubs (sub) {
  15. this.subs.push(sub)
  16. console.log('add watcher: ', sub._name)
  17. }
  18. notify () {
  19. this.subs.forEach( sub => { //通知vue组件更新
  20. sub.update()
  21. })
  22. }
  23. }
  24.  
  25. //监听者
  26. //一个vue实例包含一个Watcher实例
  27. class Watcher {
  28. // 在实例化Watcher时, 将Dep的target指向此实例, 在依赖收集中使用
  29. // 因为依赖收集是在组件初始化时触发的, 而数据变更后视图相应变更是在初始化后
  30. // 所以让Dep.target指向此实例, 当此vue实例初始化完成后, 再指向下一个正在初始化的vue实例完成依赖收集
  31. constructor (name) {
  32. Dep.target = this
  33. this._name = name
  34. }
  35. update () {
  36. // 这里模拟视图更新
  37. // 其实还应该让子组件的props相应值与父组件更新的数据同步
  38. console.log("子组件视图更新了..." + this._name)
  39. }
  40. }
  41.  
  42. //对data中的数据设置读写监听, 并且创建订阅者, 用于收集子组件的依赖和发布
  43. function defineReactive (obj, key, value) {
  44.  
  45. // 对vue实例中data对象的每一个属性都 设置一个订阅者Dep
  46. let dep = new Dep()
  47.  
  48. // 第二个vue实例的监听 覆盖了第一个vue实例的监听, 因为引用的obj是同一个
  49. Object.defineProperty(obj, key, {
  50. configurable: true,
  51. enumerable: true,
  52. get () {
  53. // 在读此属性时, 将当前 watcher 对象收集到此属性的 dep 对象中
  54. // 在实例化vue时将Dep.target指向当前Watcher
  55. // get()依赖收集的时候是vue组件初始化的时候, set()是在初始化后
  56. if (dep.subs.indexOf(Dep.target) === -) {
  57. dep.addSubs(Dep.target)
  58. }
  59. //return obj[key] 此写法报错 提示栈溢出 原因是无限调用get()
  60. return value
  61. },
  62. set (newVal) { // 此属性改变时, 通知所有视图更新
  63. if (newVal !== value) {
  64. value = newVal
  65. dep.notify()
  66. }
  67. }
  68. })
  69. }
  70.  
  71. //接收一个对象作为参数, 将该对象的所有属性调用defineReactive设置读写监听
  72. function observer (obj) {
  73. if (!obj || (typeof obj !== 'object')) {
  74. return
  75. }
  76. Object.keys(obj).forEach( key => {
  77. defineReactive(obj, key, obj[key])
  78. })
  79. }
  80.  
  81. // 构造函数, 监听 配置options中的data()方法返回的对象的所有属性 的读写
  82. class Vue {
  83. constructor (options) {
  84. this._name = options.name
  85. this._data = options.data
  86. // 每个vue组件都是一个vue实例, 在一个页面中有多个vue实例
  87. // 在初始化该vue实例时, new一个Watcher对象, 使Dep.target指向此实例
  88. new Watcher(options.name)
  89. // 给data中的数据挂载读写监听
  90. observer(this._data)
  91. //模拟vue解析template过程, 获取从父组件传递过来的props
  92. //在这里进行依赖收集
  93. this._props = options.props ? getProps() : {}
  94. // 实例化该组件的子组件
  95. this._children = options.render ? (options.render() || {}) : {}
  96. }
  97. }
  98.  
  99. // 父组件数据
  100. let data = {
  101. first: "hello",
  102. second: 'world',
  103. third: ['啦啦啦']
  104. }
  105.  
  106. let times =
  107. // 第一次调用返回的是第一个子组件的从父组件继承的数据(vue中props属性的值)
  108. // 第二次调用返回的是第二个子组件的从父组件继承的数据(vue中props属性的值)
  109. function getProps () {
  110. times++
  111. if (times == ) {
  112.  
  113. let obj = {first: "", second: ""}
  114. Object.keys(obj).forEach( key => {
  115. // 如果是对象, 则进行深拷贝
  116. // 这里使用到了父组件的数据, 触发依赖收集
  117. if (data[key] instanceof Object) {
  118. obj[key] = JSON.parse(JSON.stringify(data[key]))
  119. } else {
  120. obj[key] = data[key]
  121. }
  122. })
  123. return obj
  124.  
  125. } else if (times == ) {
  126.  
  127. let obj = {first: "", third: ""}
  128. Object.keys(obj).forEach( key => {
  129. if (data[key] instanceof Object) {
  130. obj[key] = JSON.parse(JSON.stringify(data[key]))
  131. } else {
  132. obj[key] = data[key]
  133. }
  134. })
  135. return obj
  136. }
  137. }
  138.  
  139. let vue_root = new Vue({
  140. name: 'vue_root',
  141. data,
  142. //模拟编译template和实例化vue的过程
  143. //在编译父组件 并且传递参数给子组件时, 将子组件的 watcher 添加进父组件的 dep
  144. render () {
  145. let vue_1 = new Vue({
  146. name: 'vue_1',
  147. data: {},
  148. props: true,
  149. render () {}
  150. })
  151. let vue_2 = new Vue({
  152. name: 'vue_2',
  153. data: {},
  154. props: true,
  155. render () {}
  156. })
  157. return {
  158. vue_1,
  159. vue_2
  160. }
  161. }
  162. })
  163. console.log(vue_root)
  164. vue_root._data.first = 'hello hello' // vue_1 和 Vue_2 都依赖此数据, 都更新
  165. vue_root._data.third = "aaa" // 只有 vue_2 依赖到了此数据, 更新

vue的双向绑定和依赖收集的更多相关文章

  1. 【学习笔记】剖析MVVM框架,简单实现Vue数据双向绑定

    前言: 学习前端也有半年多了,个人的学习欲望还比较强烈,很喜欢那种新知识在自己的演练下一点点实现的过程.最近一直在学vue框架,像网上大佬说的,入门容易深究难.不管是跟着开发文档学还是视频教程,按步骤 ...

  2. 撸一个vue的双向绑定

    1.前言 说起双向绑定可能大家都会说:Vue内部通过Object.defineProperty方法属性拦截的方式,把data对象里每个数据的读写转化成getter/setter,当数据变化时通知视图更 ...

  3. vue的双向绑定原理及实现

    前言 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图 ...

  4. Vue数据双向绑定原理及简单实现

    嘿,Goodgirl and GoodBoy,点进来了就看完点个赞再go. Vue这个框架就不简单介绍了,它最大的特性就是数据的双向绑定以及虚拟dom.核心就是用数据来驱动视图层的改变.先看一段代码. ...

  5. vue的双向绑定原理解析(vue项目重构二)

    现在的前端框架 如果没有个数据的双向/单向绑定,都不好意思说是一个新的框架,至于为什么需要这个功能,从jq或者原生js开始做项目的前端工作者,应该是深有体会. 以下也是个人对vue的双向绑定原理的一些 ...

  6. vue 之 双向绑定原理

    一.实现双向绑定 详细版: 前端MVVM实现双向数据绑定的做法大致有如下三种: 1.发布者-订阅者模式(backbone.js) 思路:使用自定义的data属性在HTML代码中指明绑定.所有绑定起来的 ...

  7. 探讨vue的双向绑定原理及实现

    1.vue的实现原理 vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属 ...

  8. 【Vue】vue的双向绑定原理及实现

    vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,那么vue是如果进行数据劫持的,我们可以先来看一下通过控制台输出一个定义在vue初始化数据上的对象是个什么东西. 代码: var ...

  9. 【Vue源码学习】依赖收集

    前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...

随机推荐

  1. hdu3715 Go Deeper[二分+2-SAT]/poj2723 Get Luffy Out[二分+2-SAT]

    这题转化一下题意就是给一堆形如$a_i + a_j \ne c\quad (a_i\in [0,1],c\in [0,2])$的限制,问从开头开始最多到哪条限制全是有解的. 那么,首先有可二分性,所以 ...

  2. vulkan asynchronous compute

    https://www.youtube.com/watch?v=XOGIDMJThto https://www.khronos.org/assets/uploads/developers/librar ...

  3. opengles reference card

    https://www.khronos.org/files/opengles31-quick-reference-card.pdf https://www.khronos.org/opengles/s ...

  4. MySQL 下载与安装使用教程

    MySQL 官网地址:https://www.mysql.com/ 等待下载完成 双击运行 如果有需要 我们可以新增一个用户出来 点击 Add User,不需要的话 直接 点击 next 默认的MyS ...

  5. 权势二进制(51Nod 1413)

    一个十进制整数被叫做权势二进制,当他的十进制表示的时候只由0或1组成.例如0,1,101,110011都是权势二进制而2,12,900不是. 当给定一个n的时候,计算一下最少要多少个权势二进制相加才能 ...

  6. Codeforces 1254C/1255F Point Ordering (交互题)

    题目链接 http://codeforces.com/contest/1254/problem/C 题解 sb题. 第一次,通过\((n-2)\)次询问2确定\(p[2]\),也就是从\(1\)来看& ...

  7. ACM之路(15)—— 字典树入门练习

    刷的一套字典树的题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=120748#overview 个人喜欢指针的字典树写法,但是大力 ...

  8. HTTP之简析

    1. 简介 HTTP 协议是 Hyper Text Transfer Protocol(超文本传传输协议)的缩写,是用于从万维网服务器传输超文本到本地浏览器的传送协议.HTTP 通常架构在 TCP 传 ...

  9. 【互联网运营P1】

    一.导论 [运营]是什么 二.运营的职业分工和职能发展 三.转化型文案 4个高转化率短文案的常见姿势 2个短文案写作的核心要则 中长型转化文案的写作 针对所有问题点依次进行详细解读 四.第三方推广 常 ...

  10. WebView加载html实现网页上传本地文件(图片,拍照,语音等)

    前言: 这里有两个方案,第一个使用Andorid客户端和JavaScript互相调用方法来实现,这种方法极力不推荐,它会增加服务端和客户端的开发成本. 第二种就是继承WebViewChromeClie ...