资讯专栏INFORMATION COLUMN

实现一个简单的双向绑定

elisa.yang / 699人阅读

摘要:下图展示了实现双向绑定的流程实现一个简单的双向绑定双向绑定最最最初级进阶版操作是非常耗时和好性能,所以在优化过程中先从操作入手。

接触Vue有一段时间了,但是对于其双向绑定的实现一直是似懂非懂,今天看到一篇写的比较好的文章 传送门1 根据原作者的指导自己也去实现了一遍简单的 demo (本文的demo均基于Object.defineProperty 实现数据劫持,利用了对Vue.js实现双向绑定的思想)

[注]本文所有图片均来自于:传送门2

前言 几种主流的双向绑定

1.发布-订阅模式
2.脏值检测

通过对比数据是否有变更,来决定是否更新视图。最简单的可以通过定时轮询去检测数据的变动。当然Google不会这么low, Angular 只有在指定事件触发时进入脏值检测:

DOM事件,比如用户输入文本点击按钮等(ng-click)

XHR响应事件

浏览器 Location 变更

Timer事件

执行 $digidt() 或 $apply()

3.数据劫持
Vue.js 采用的是 数据劫持+发布/订阅模式 的方式,通过 Object.defineProperty() 来劫持各个属性的 setter/getter, 在数据变动时发布消息给订阅者(Wacther), 触发相应的监听回调。下图展示了Vue实现双向绑定的流程

实现一个简单的双向绑定
  1. 双向绑定最最最初级demo
进阶版demo

DOM操作是非常耗时和好性能,所以在优化过程中先从DOM操作入手。因为遍历解析过程中有多次DOM操作,为了提高性能和效率,需要一种方法来避免对DOM元素的直接封装操作。在Vue使用 DocumentFragment 作为替代容器。
DocumentFragment 接口表示一个没有父级文件的最小文档对象。它被当做一个轻量版本的Document 使用。所以使用 DocumentFragment 代替DOM直接处理,可以提高性能和速度。

封装DOM节点为 DocumentFragment

</>code

  1. //将传入 node 的子节点进行劫持,经过处理后重新挂载回目标节点
  2. function convertNode(node,vm){
  3. var fragment = document.createDocumentFragment(),
  4. child
  5. while(child = node.firstChild){
  6. //将原生节点拷贝到 fragment,并删除之前的child节点
  7. fragment.appendChild(child)
  8. }
  9. return fragment
  10. }
  11. var dom = convertNode(document.getElementById("app"))
  12. document.getElmentById("app").appendChild(dom)
实现Complie解析模板指令

Complie 主要做的事情就是解析模板指令,将模板中的变量替换为数据。所以要遍历整个DOM树,进行扫描解析编译,调用对应的指令渲染函数进行渲染,并调用对应的指令更新函数进行绑定

</>code

  1. {{text}}

</>code

  1. function convertNode(node,vm){
  2. //...
  3. while(child = node.firstChild){
  4. Compile(child,vm)
  5. fragment.appendChild(child)
  6. }
  7. return fragment
  8. }
  9. function Compile(node,vm){
  10. var reg = /{{(.*)}}/
  11. if(node.nodeType===1){
  12. var attr = node.attributes //对所有属性进行解析
  13. for(var i=0;i
  14. ViewModel 层向 View 层的数据绑定
  15. 接下来实现一个 Xin 构造器,通过 Compile 来解析模板指令,通过 Observer 监听属性数据的变化实现 Model 层向 View 层的数据绑定

  16. </>code

    1. function Xin(options){
    2. this.data = options.data
    3. Observer(this.data,this)
    4. var id = options.el
    5. var dom = convertNode(document.getElementById(id),this)
    6. document.getElementById(id).appendChild(dom)
    7. }
  17. 新建一个 vm 实例来测试一下 Model --> View 的绑定情况

  18. </>code

    1. var vm = new Xin({
    2. el:"app",
    3. data:{
    4. text:"Hello MVVM"
    5. }
    6. })
  19. View 层向 viewModel 层的数据绑定
  20. 实际上,在 Observer 中我们已经通过 数据劫持 实现了监听每个数据的变化,在控制台打印 console.log(val) 就可以实时看到数据的变化。所以接下来实现的关键就是怎么用监听到的数据去更新视图。
    在这里,我们去实现一个 Wacther 可以将它理解为观察者 ,他的作用是能够接收从 Observer 发过来的属性变动通知, 然后根据属性的变动更新视图 update

  21. 在监听过程中,为所有的 data 属性生成一个主题对象 Dep,Dep中包含需要维护的观察者列表。每当主题对象状态发生变化时,其相关依赖都会得到通知,并且被自动更新(数据变动会触发notify,再调用订阅者的update() 方法)

  22. </>code

    1. function Dep(){
    2. this.subs=[] //订阅者队列
    3. }
    4. Dep.prototype={
    5. addSub:function(sub){
    6. this.subs.push(sub)
    7. },
    8. notify:function(){
    9. this.subs.forEach(function(sub){
    10. sub.update()
    11. })
    12. }
    13. }
    14. funcion Watcher(vm,node,bindName){
    15. //将全局Dep.target设置为当前页面元素node
    16. Dep.target = this
    17. //完成watcher的初始化
    18. this.name = bindName
    19. this.node = node
    20. this.vm = vm
    21. this.update() //初次绑定时进行更新
    22. Dep.target = null //保证Dep.target唯一
    23. }
    24. Watcher.prototype = {
    25. get:function(){
    26. this.value = this.vm.data[this.name]
    27. },
    28. update:function(){
    29. this.get()
    30. this.node.nodeValue = this.value
    31. }
    32. }
    33. function Observer(obj,vm){
    34. //...
    35. Object.defineProperty(obj,prop,{
    36. get:function(){...},
    37. set:function(newVal){
    38. if(val == newVal) return
    39. val = newVal
    40. //data属性被修改,由dep触发view层更新
    41. dep.notify()
    42. }
    43. })
    44. }
  23. 考虑这样一个问题,什么时候会有双向绑定? viewModel --> view 可能会发生在所有类型的DOM节点上,而 view --> viewModel 只能发生在 input, select, textarea 等交互控件上。所以将文本节点包装成 Watcher , 添加相关元素的观察者列表中,Watcher 负责更新页面元素

  24. </>code

    1. function Compile(node,vm){
    2. //...
    3. if(node.nodeType ===3){ //文本节点类型
    4. if(reg.test(node.nodeValue)){
    5. var bindName = RegExp.$1.trim()
    6. new Watcher = (vm,node,bindName) //为该页面元素node生产watcher
    7. }
    8. }
    9. }
  25. 更新
  26. 本文中实现模板渲染的方法借鉴了 Vue 1.x 中实现模板渲染的方法。
    Vue 2.x 模板渲染 方法借鉴React 中的 VirtualDOM,基于 VirtualDOMVue 2.x 还支持服务端渲染SSR

  27. 资料参考
  28. 1.https://github.com/DMQ/mvvm#_2

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91898.html

相关文章

  • 最近老是有兄弟问我,Vue双向绑定原理,以及简单原生js写出来实现,我就来一个简单双向绑定

    摘要:废话不多说直接看效果图代码很好理解,但是在看代码之前需要知道双向绑定的原理其实就是基于实现的双向绑定官方传送门这里我们用官方的话来说方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。 废话不多说直接看效果图 showImg(https://segmentfault.com/img/bVbiYY5?w=282&h=500); 代码很好理解,但是在看代码之前...

    zhangfaliang 评论0 收藏0
  • 最近老是有兄弟问我,Vue双向绑定原理,以及简单原生js写出来实现,我就来一个简单双向绑定

    摘要:废话不多说直接看效果图代码很好理解,但是在看代码之前需要知道双向绑定的原理其实就是基于实现的双向绑定官方传送门这里我们用官方的话来说方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。 废话不多说直接看效果图 showImg(https://segmentfault.com/img/bVbiYY5?w=282&h=500); 代码很好理解,但是在看代码之前...

    aristark 评论0 收藏0
  • JavaScript数据双向绑定简单演示

    摘要:对于前端,有时候需要实现视图层和数据层的双向绑定例如当前流行的各种框架和类库。为代表前端数据劫持。参考资料实现数据双向绑定的三种方式谈谈中的双向数据绑定非常简单的双向数据绑定框架三 对于前端,有时候需要实现视图层和数据层的双向绑定(two-way-binding), 例如当前流行的各种框架和类库:Vue.js、Angular.js、React.js。 然而,他们最原始的实现方式其实都相...

    snifes 评论0 收藏0

发表评论

0条评论

elisa.yang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<