欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

剖析Vue实现原理 - 如何实现双向绑定mvvm

程序员文章站 2022-03-30 11:52:32
...

原文:https://github.com/DMQ/mvvm (包含原文源码)

由于源码的注释比较少,我自己加了注释的地址:https://github.com/zengqingxiao/MVVM/tree/master

几种实现双向绑定的做法

  • 发布者-订阅者模式(backbone.js)
  • 脏值检查(angular.js)
  • 数据劫持(vue.js)

发布者-订阅者模式: 一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是 vm.set('property', value)

脏值检查: angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 setInterval() 定时轮询检测数据变动,当然Google不会这么low,(用一些监听)angular只有在指定的事件触发时进入脏值检测,大致如下

  • DOM事件,譬如用户输入文本,点击按钮等。( ng-click )
  • XHR响应事件 ( $http )
  • 浏览器Location变更事件 ( $location )
  • Timer事件( $timeout , $interval )
  • 执行 $digest() 或 $apply()

数据劫持: vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调。因此可以vm.property = value 这种方式更新数据,同时自动更新视图,于是有了下面两种方式

发现:react和微信小程序好像就是用的发布者-订阅者模式,而vue用的是数据劫持,我认为其主要原因是vue用了Object.defineProperty()给data对象中的每一个值添加了访问器属性(get,set),但是是放弃了对IE8的兼容,而React为了兼容IE,所以只可以吧修改修改的写到一个方法中,而不是像vue一样改变data去自动响应(通过对data添加的set访问器属性)

VUE的数据劫持是实现过程

1. 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者 (Watcher)

2. 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数(Updater)

3. 实现一个Watcher,作为连接Observer和Compile的桥梁,

      3-1:在初始化的时候通过Compile(解析指令)来绑定和订阅器Dep的关系

      3-2:在数据变化的时候能够并收订阅到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图 

4. mvvm入口函数,整合以上三者 

如图

剖析Vue实现原理 - 如何实现双向绑定mvvm

1.数据代理

实现vm.aa 就可以修改vm.data.aa,获取vm.aa,就可以找到vm.data.aa的值

方便了vue的使用值少写一些代码,注意:这里的(set,get是绑定在vm.data这个整体上面的,而data中的每一个属性值也要绑定访问器属性,但是目的是为了完成数据劫持)

function MVVM(options) {
    this.$options = options;
    var data = this._data = this.$options.data;
    observe(data, this);
    this.$compile = new Compile(options.el || document.body, this)
}
function MVVM(options) {
    this.$options = options;
    var data = this._data = this.$options.data, me = this;
    // 属性代理,实现 vm.xxx -> vm._data.xxx
    Object.keys(data).forEach(function(key) {
        me._proxy(key);
    });
    observe(data, this);
    this.$compile = new Compile(options.el || document.body, this)
}

MVVM.prototype = {
	_proxy: function(key) {
		var me = this;
        Object.defineProperty(me, key, {
            configurable: false,
            enumerable: true,
            get: function proxyGetter() {
                return me._data[key];
            },
            set: function proxySetter(newVal) {
                me._data[key] = newVal;
            }
        });
	}
};

对data数据处理

按照vue代码也可以从上面的图我们可以知道代码会先执行Observer来对data中的每一个数据进行Object.defineProperty()来添加get和set访问器属性,目的就是了当我们访问data或者修改data的时候我们对其做出响应的操作,例如:set的时候我们去修改页面,那么我们如果修改页面我们后面来讲解,并且每一个data中的数据都有一个Dep对象,这个对象中包含了他们的ID和一个Subs数组,这个数组是来放这个data值和那些wacther对象的,

页面初始化和监听变化

通过Compile来初始化页面对node中的文本节点和元素节点进行解析,通过Updater方法来对node进行渲染,在初始化的过程中我们对那些文本节点和元素节点进行Watcher监听,构造相关Watcher对象(包括了当前表达式exp对应的值,和vm对象,和回调函数),而在构架watcher的时候我们会对表达式中的值读取一遍,为什么呢?其实就是为了触发我们前面在observer中对每一个data设置的访问器属性set,作用就是吧当前watcher,通过原型链放入到相关联的Dap的sunbs数组中,(每一个data都触发一个函数这个函数中定义了相关Dep和访问器函数,那么触发git访问器函数的时候就会找到相关Dep),那么我们每一个data就有一个Dep,而这个Dep中又包含了subs数组中包含了Watcher,当某一个值发生改变遍历subs,找到和data相关的watcher,而这个watcher在触发他相关的回调函数,这个回调函数中通过就会去找到相关的node去和改变表达式的值

注意:例如一个表达式{{A.B.C}}我们只是获取c的值但是这个c这个值相关的watcher分别会放入到a,a.b,a.b.c的Dpe中的subs数组中,主要当a修改那么a.b.c相关的watcher就会被触发,那么相关的回调函数触发修改node,那么是如果做到的呢,其实就是我们如果要获取a.b.c要先获取到a的值,在获取到a.b的值,那么在获取那么值的时候触发了相应的Dep,而中国Dpe就会把当前表达式{{a.b.c}}所关联的Watcher放入到对应的Dep的subs数组中

ubdater:里面主要是放的的通过表达式来修改页面的方法

 

 

相关标签: vue