首页 > 编程 > JavaScript > 正文

vue双向绑定及观察者模式详解

2019-11-19 11:58:12
字体:
来源:转载
供稿:网友

在Vue中,使用了Object.defineProterty()这个函数来实现双向绑定,这也就是为什么Vue不兼容IE8

1 响应式原理

让我们先从相应式原理开始。我们可以通过Object.defineProterty()来自定义Object的getter和setter 从而达到我们的目的。

代码如下

function observe(value, cb) { Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))}function defineReactive (obj, key, val, cb) { Object.defineProperty(obj, key, {  enumerable: true,  configurable: true,  get: ()=>{   /*....依赖收集等....*/   /*Github:https://github.com/answershuto*/   return val  },  set:newVal=> {   val = newVal;   cb();/*订阅者收到消息的回调*/  } })}class Vue { constructor(options) {  this._data = options.data;  observe(this._data, options.render) }}let app = new Vue({ el: '#app', data: {  text: 'text',  text2: 'text2' }, render(){  console.log("render"); }})

通过observe函数对app.data上的每一个key和value都设定getter和setter。当value改变的时候触发setter,就会触发render这个函数。响应式的目的就达成,如果是视图更新的话我们通过监听dom的input事件来触发数据更新
但是现在我们只有在改变vue._data.text的时候才会触发他们的setter,但是我想偷懒,只改变vue.text就能触发到setter怎么做呢?

我们使用代理的方法

_proxy.call(this, options.data);/*构造函数中*//*代理*/function _proxy (data) { const that = this; Object.keys(data).forEach(key => {  Object.defineProperty(that, key, {   configurable: true,   enumerable: true,   get: function proxyGetter () {    return that._data[key];   },   set: function proxySetter (val) {    that._data[key] = val;   }  }) });}

依赖收集

让我们再来看看下面的代码

new Vue({ template:   `<div>   <span>text1:</span> {{text1}}   <span>text2:</span> {{text2}}  <div>`, data: {  text1: 'text1',  text2: 'text2',  text3: 'text3' }});

当你的text3变化的时候,实际上text3并没有被渲染,但是也会触发一次render函数,这显然是不对的。所以我们需要收集依赖。

我们只需要在初始化的时候渲染一遍,那所有渲染所依赖的数据都会被触发getter,这时候我们只要把这个数据放到一个列表里就好啦!

我们先来认识一下Dep(dependencies)这个类,下图是一个最简单的Dep类。我们可以把他理解为发布者(这点很重要!!)

class Dep { constructor () {  this.subs = []; } addSub (sub: Watcher) {  this.subs.push(sub) } removeSub (sub: Watcher) {  remove(this.subs, sub) } /*Github:https://github.com/answershuto*/ notify () {  // stabilize the subscriber list first  const subs = this.subs.slice()  for (let i = 0, l = subs.length; i < l; i++) {   subs[i].update()  } }}function remove (arr, item) { if (arr.length) {  const index = arr.indexOf(item)  if (index > -1) {   return arr.splice(index, 1) }}

我们每次触发getter的时候,只要把触发的对象放到dep.sub里面就好啦!
但是现在问题来了,我们用什么来装这个触发的'对象',也可以说式订阅者呢?

我们使用Watcher这个类

class Watcher { constructor (vm, expOrFn, cb, options) {  this.cb = cb;  this.vm = vm;  /*在这里将观察者本身赋值给全局的target,只有被target标记过的才会进行依赖收集*/  Dep.target = this;  /*Github:https://github.com/answershuto*/  /*触发渲染操作进行依赖收集*/  this.cb.call(this.vm); } update () {  this.cb.call(this.vm); }}

vm即是vue实例, expOrFn就是{{a+b}}里面的a+b, cb就是回调函数就是return a+b, options是一些配置项。

Vue在第一次渲染列表的时候如果碰到{{xxx}}这样的表达式,就会new Watcher()。解析里面的函数,然后把当前的watcher实例赋给Dep.target(Dep.target是全局的,一次性只能有一个存在,因为Vue一次只处理一个依赖)。然后执行回调函数。(这里看似是执行回调函数渲染,其实又触发了一次getter,然后就会把当前的依赖添加到sub里去)

接下来开始依赖收集

class Vue { constructor(options) {  this._data = options.data;  observer(this._data, options.render);  let watcher = new Watcher(this, ); }}function defineReactive (obj, key, val, cb) { /*在闭包内存储一个Dep对象*/ const dep = new Dep(); Object.defineProperty(obj, key, {  enumerable: true,  configurable: true,  get: ()=>{   if (Dep.target) {    /*Watcher对象存在全局的Dep.target中*/    dep.addSub(Dep.target);   }  },  set:newVal=> {   /*只有之前addSub中的函数才会触发*/   dep.notify();  } })}Dep.target = null; //防止依赖重复添加

这儿我们通过示例来讲解

<template> <div> {{a+b}} </div> <div> {{a-c}} </div></template><script>let app = new Vue( { data :{  a: 1,  b: 2,  c: 3 } })

我们编译到{{a+b}},会去实例化一个对应的Watcher对象,Watcher的构造函数中有这么一句

this.cb.call(this.vm);this.cb指的是function(){return a+b};this.vm指的是这个vue对象,这样就会触发vue.a和vue.b的getter方法,a,b都有自己的dep对象,我们通过Dep.target将这个Watcher对象就加到dep的subs数组中去了,当我们变更a或者b是就会触发setter,进而触发subs数组中的update方法,视图中的a+b就会更新

有个小知识点:我们新建一个属性对象时必须通过Vue.set的方法去实现,而不能直接通过=实现,这样会检测不到,因为我们在初始化时就通过defineProperty重构了这个对象属性的getter和setter方法,新建的属性则没有所以不会被检测到

下图为Vue框架在数据初始化中使用观察者模式的示意图:

以上所述是小编给大家介绍的vue双向绑定及观察者模式详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对武林网网站的支持!

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表