Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MVVM双向绑定原理与实现原理 #36

Open
Genluo opened this issue Aug 31, 2019 · 0 comments
Open

MVVM双向绑定原理与实现原理 #36

Genluo opened this issue Aug 31, 2019 · 0 comments

Comments

@Genluo
Copy link
Owner

Genluo commented Aug 31, 2019

首先明白单向绑定和双向绑定的区别

单向绑定和双向绑定

引用知乎的答案,只有UI组件才存在双向绑定,非UI组件不存在双向绑定

单行绑定优点是只有一份data,只有当data发生改变的时候才会更改页面,如果用户在页面做了变动,那么必须要收集起来(双向绑定是自动),然后统一更改数据data,然后反应到页面中,其实使用单向绑定可以监听相关事件从而实现双向绑定的效果,相比于双向绑定,单向绑定优点是对数据更强的控制,保证数据的每次更改都和UI组件无关

双向绑定的优点就是存在,当数据data发生改变的时候,页面也会自动的发生更新,但是也有的一个去缺点就是页面数据自动发生更新,这时候不知道data什么时候改变了,所以要使用watch进行监听。

两者关系两者关系可以理解为: 双向绑定 = 单向绑定 + UI事件监听,这样来说的话,双向绑定无非就是在单向绑定的基础上给可输入元素inputtextarea等添加了change(input),来动态的修改Model和view

数据绑定实现

在下面那个链接上,尤玉玺大佬说明了两者的区别:

表单的双向绑定,说到底不过是 (value 的单向绑定 + onChange 事件侦听)的一个语法糖,你如果不想用 v-model,像 React 那样处理也是完全可以的。另一方面,组件间的数据传递,Vue 默认是单向的,和 React 一样。

所以就不用纠结于单向数据绑定和双向数据绑定,只需要理解Model - > View 是如何处理的就行。

在react中,当每个组件的状态发生改变的时候,它会以该组件为根,重新渲染整个组件

在Vue中,组件的依赖是在渲染过程中自动追踪的,所以系统能精确知晓哪个组件确实需要被重渲染。你可以理解为每一个组件都已经自动获得了 shouldComponentUpdate,并且没有上述的子树问题限制。

因为这两种区别,这里我们就主要看下Vue中的实现方式,实现数据绑定的做法大概有以下几种:

  1. 发布-订阅者模式(backbone.js)
  2. 脏值检查(angular.js)
  3. 数据劫持(Vue.js)
  4. Proxy对象代理
发布订阅模式

发布订阅模式是一种比较优秀的设计模式,可以在这种的模式的基础上,实现异步更新,可以维护更新队列,但是有一个问题是就是更改属性比较麻烦,需要调用相应的方法才能实现,这种方式毕竟不是很方便和简洁,为了解决这个问题,于是又了下面几种方法,可以实现vm.property = value这种方式更新数据。

脏值检查

脏值检查是通过一种对比数据是否有变化来检测是否更新的数据绑定方法

数据劫持

数据劫持是实现双向绑定的基础,数据劫持是通过Object.defineProperty来实现或者通过Proxy实现。

Vue是数据劫持发布订阅结合的一种数据绑定方式,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调

Vue是如何实现请参照:https://github.com/DMQ/mvvm

自己的理解

整个架构主要分为

  • Observer

最重要的部分之一,实现data对象的数据的劫持,同时针对对象的每一个key值维护一个队列,将监听此对象的相关key的watcher放入其中,然后等待相关key值发生变化,调用绑定此key值的观察者,但是模板的对应的key值的watcher是如何插入到对应的对象的key值队列。

利用ProxyObject.defineProperty生成的Observer针对对象/对象的属性进行"劫持",在属性发生变化后通知订阅者

  • Compile

最重要的部分之一,实现对模板上相关节点的编译,根据节点生成对应的文档片段,然后根据文档片段来解析对应的模板里面的内容(因为里面涉及到DOM操作,所以使用文档片段的话可以降低DOM操作引起的性能损失),然后在编译的过程中找出对应的表达式,根据找出对应的对象表达式构建的回调函数生成一个新的Watcher,然后在watcher部分将新创建的Watcher插入到Observer的Dep中,作为对象的观察者

解析器Compile解析模板中的Directive(指令),收集指令所依赖的方法和数据,等待数据变化然后进行渲染

  • Watcher

实现Observer和Compoile通信的重要组成部分,可以理解为一个观察者,和Observer中的Dep结合来实现发布订阅模式

Watcher属于Observer和Compile桥梁,它将接收到的Observer产生的数据变化,并根据Compile提供的指令进行视图渲染,使得数据变化促使视图变化

使用数据劫持的优点
  • 无需显示进行调用
  • 可以精确知道变化的数据
使用数据劫持的缺点
  • 只能应用于简单对象
  • 对数组无效,所以需要包装数组方法
  • 属性劫持的出发点是变化,实现data.property = val,所以不能很好的实现immutable模式
使用Object.defineProperty和使用Proxy实现的有什么区别

Object.definProperty : 使用Object.defineProperty第一个缺陷是不能够监听数组的变化,第二个缺陷是只能监听对象的属性,不能直接监听对象,这样就导致需要遍历进行监听

Proxy:首先可以直接监听对象而不是属性,使用Proxy可以直接返回一个新对象,操作便利程度和底层功能上都远强于Object.defineProperty,第二个就是可以监听数组的变化了,不会存在数组失效的情况出现,但是使用Proxy有一个问题就是兼容性问题,并且不能通过polyfill解决

参考资料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant