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

Vue监听数据对象变化源码

程序员文章站 2022-06-29 22:54:17
监听数据对象变化,最容易想到的是建立一个需要监视对象的表,定时扫描其值,有变化,则执行相应操作,不过这种实现方式,性能是个问题,如果需要监视的数据量大的话,每扫描一次全部的...

监听数据对象变化,最容易想到的是建立一个需要监视对象的表,定时扫描其值,有变化,则执行相应操作,不过这种实现方式,性能是个问题,如果需要监视的数据量大的话,每扫描一次全部的对象,需要的时间很长。当然,有些框架是采用的这种方式,不过他们用非常巧妙的算法提升性能,这不在我们的讨论范围之类。

vue 中数据对象的监视,是通过设置 es5 的新特性(es7 都快出来了,es5 的东西倒也真称不得新)object.defineproperty() 中的 set、get 来实现的。

目标

与官方文档第一个例子相似,不过也有简化,因为这篇只是介绍下数据对象的监听,不涉及文本解析,所以文本解析相关的直接舍弃了:

<div id="app"></div>
var app = new vue({
 el: 'app',
 data: {
 message: 'hello vue!'
 }
});

浏览器显示:

hello vue!

在控制台输入诸如:

app.message = 'changed!'

之类的命令,浏览器显示内容会跟着修改。

object.defineproperty

引用 mdn 上的定义:

object.defineproperty()方法会直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象。
与此相生相伴的还有一个 object.getownpropertydescriptor():

object.getownpropertydescriptor() 返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

下面的例子用一种比较简单、直观的方式来设置 setter、getter:

var dep = [];

function definereactive(obj, key, val) {
 // 有自定义的 property,则用自定义的 property
 var property = object.getownpropertydescriptor(obj, key);
 if(property && property.configurable === false) {
 return;
 }

 var getter = property && property.get;
 var setter = property && property.set;

 object.defineproperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function() {
  var value = getter ? getter.call(obj) : val;
  dep.push(value);
  return value;
 },
 set: function(newval) {
  var value = getter ? getter.call(obj) : val;
  // set 值与原值相同,则不更新
  if(newval === value) {
  return;
  }
  if(setter) {
  setter.call(obj, newval);
  } else {
  val = newval;
  }
  console.log(dep);
 }
 });
}
var a = {};
definereactive(a, 'a', 12);
// 调用 getter,12 被压入 dep,此时 dep 值为 [12]
a.a;
// 调用 setter,输出 dep ([12])
a.a = 24;
// 调用 getter,24 被压入 dep,此时 dep 值为 [12, 24]
a.a;

observer

简单说过 object.defineproperty 之后,就要开始扯 observer 了。observer,中文解释为“观察者”,观察什么东西呢?观察对象属性值的变化。故此,所谓 observer,就是给对象的所有属性加上 getter、setter,如果对象的属性还有属性,比如说 {a: {a: {a: 'a'}}},则通过递归给其属性的属性也加上 getter、setter:

function observer(value) {
 this.value = value;
 this.walk(value);
}
observer.prototype.walk = function(obj) {
 var keys = object.keys(obj);
 for(var i = 0; i < keys.length; i++) {
 // 给所有属性添加 getter、setter
 definereactive(obj, keys[i], obj[keys[i]]);
 }
};

var dep = [];

function definereactive(obj, key, val) {
 // 有自定义的 property,则用自定义的 property
 var property = object.getownpropertydescriptor(obj, key);
 if(property && property.configurable === false) {
 return;
 }

 var getter = property && property.get;
 var setter = property && property.set;

 // 递归的方式实现给属性的属性添加 getter、setter
 var childob = observe(val);
 object.defineproperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function() {
  var value = getter ? getter.call(obj) : val;
  dep.push(value);
  return value;
 },
 set: function(newval) {
  var value = getter ? getter.call(obj) : val;
  // set 值与原值相同,则不更新
  if(newval === value) {
  return;
  }
  if(setter) {
  setter.call(obj, newval);
  } else {
  val = newval;
  }
  // 给新赋值的属性值的属性添加 getter、setter
  childob = observe(newval);
  console.log(dep);
 }
 });
}

function observe(value) {
 if(!value || typeof value !== 'object') {
 return;
 }
 return new observer(value);
}

watcher

observer 通过设置数据对象的 getter、setter 来达到监听数据变化的目的。数据被获取,被设置、被修改,都能监听到,且能做出相应的动作。

现在还有一个问题就是,谁让你监听的?

这个发出指令的就是 watcher,只有 watcher 获取数据才触发相应的操作;同样,修改数据时,也只执行 watcher 相关操作。

那如何讲 observer、watcher 两者关联起来呢?全局变量!这个全局变量,只有 watcher 才做修改,observer 只是读取判断,根据这个全局变量的值不同而判断是否 watcher 对数据进行读取,这个全局变量可以附加在 dep 上:

dep.target = null;

根据以上所述,简单整理下,代码如下:

function watcher(data, exp, cb) {
 this.data = data;
 this.exp = exp;
 this.cb = cb;
 this.value = this.get();
}
watcher.prototype.get = function() {
 // 给 dep.target 置值,告诉 observer 这是 watcher 调用的 getter
 dep.target = this;
 // 调用 getter,触发相应响应
 var value = this.data[this.exp];
 // dep.target 还原
 dep.target = null;
 return value;
};
watcher.prototype.update = function() {
 this.cb();
};
function observer(value) {
 this.value = value;
 this.walk(value);
}
observer.prototype.walk = function(obj) {
 var keys = object.keys(obj);
 for(var i = 0; i < keys.length; i++) {
 // 给所有属性添加 getter、setter
 definereactive(obj, keys[i], obj[keys[i]]);
 }
};

var dep = [];
dep.target = null;

function definereactive(obj, key, val) {
 // 有自定义的 property,则用自定义的 property
 var property = object.getownpropertydescriptor(obj, key);
 if(property && property.configurable === false) {
 return;
 }

 var getter = property && property.get;
 var setter = property && property.set;

 // 递归的方式实现给属性的属性添加 getter、setter
 var childob = observe(val);
 object.defineproperty(obj, key, {
 enumerable: true,
 configurable: true,
 get: function() {
  var value = getter ? getter.call(obj) : val;
  // 如果是 watcher 监听的,就把 watcher 对象压入 dep
  if(dep.target) {
  dep.push(dep.target);
  }
  return value;
 },
 set: function(newval) {
  var value = getter ? getter.call(obj) : val;
  // set 值与原值相同,则不更新
  if(newval === value) {
  return;
  }
  if(setter) {
  setter.call(obj, newval);
  } else {
  val = newval;
  }
  // 给新赋值的属性值的属性添加 getter、setter
  childob = observe(newval);
  // 按序执行 dep 中元素的 update 方法
  for(var i = 0; i < dep.length; i++) {
  dep[i].update(); 
  }
 }
 });
}

function observe(value) {
 if(!value || typeof value !== 'object') {
 return;
 }
 return new observer(value);
}

var data = {a: 1};
new observer(data);
new watcher(data, 'a', function(){console.log('it works')});
data.a =12;
data.a =14;

上面基本实现了数据的监听,bug 肯定有不少,不过只是一个粗糙的 demo,只是想展示一个大概的流程,没有扣到非常细致。

dep

上面几个例子,dep 是个全局的数组,但凡 new 一个 watcher,dep 中就要多一个 watcher 实例,这时候不管哪个 data 更新,所有的 watcher 实例的 update 都会执行,这是不可接受的。

dep 抽象出来,单独搞一个构造函数,不放在全局,就能解决了:

function dep() {
 this.subs = [];
}
dep.prototype.addsub = function(sub) {
 this.subs.push(sub);
};
dep.prototype.notify = function() {
 var subs = this.subs.slice();
 for(var i = 0; i < subs.length; i++) {
 subs[i].update();
 }
}

利用 dep 将上面的代码改写下就好了(当然,此处的 dep 代码也不完全,只是一个大概的意思罢了)。

vue 实例代理 data 对象

官方文档中有这么一句话:

每个 vue 实例都会代理其 data 对象里所有的属性。

var data = { a: 1 };
var vm = new vue({data: data});

vm.a === data.a // -> true

// 设置属性也会影响到原始数据
vm.a = 2
data.a // -> 2

// ... 反之亦然
data.a = 3
vm.a // -> 3

这种代理看起来很麻烦,其实也是可以通过 object.defineproperty 来实现的:

function vue(options) {
 var data = this.data = options.data;

 var keys = object.keys(data);
 var i = keys.length;
 while(i--) {
 proxy(this, keys[i];
 }
}
function proxy(vm, key) {
 object.defineproperty(vm, key, {
 configurable: true,
 enumerable: true,
 // 直接获取 vm.data[key] 的值
 get: function() {
  return vm.data[key];
 },
 // 设置值的时候直接设置 vm.data[key] 的值
 set: function(val) {
  vm.data[key] = val;
 }
 };
}

捏出一个 vue,实现最初目标

var vue = (function() {
 var watcher = function watcher(vm, exp, cb) {
  this.vm = vm;
  this.exp = exp;
  this.cb = cb;
  this.value = this.get();
 };
 watcher.prototype.get = function get() {
  dep.target = this;
  var value = this.vm._data[this.exp];
  dep.target = null;
  return value;
 };
 watcher.prototype.adddep = function adddep(dep) {
  dep.addsub(this);
 };
 watcher.prototype.update = function update() {
  this.run();
 };
 watcher.prototype.run = function run() {
  this.cb.call(this.vm);
 }

 var dep = function dep() {
  this.subs = [];
 };
 dep.prototype.addsub = function addsub(sub) {
  this.subs.push(sub);
 };
 dep.prototype.depend = function depend() {
  if(dep.target) {
   dep.target.adddep(this);
  }
 };
 dep.prototype.notify = function notify() {
  var subs = this.subs.slice();
  for(var i = 0; i < subs.length; i++) {
   subs[i].update();
  }
 };

 dep.target = null;

 var observer = function observer(value) {
  this.value = value;
  this.dep = new dep();

  this.walk(value);
 };
 observer.prototype.walk = function walk(obj) {
  var keys = object.keys(obj);

  for(var i = 0; i < keys.length; i++) {
   definereactive(obj, keys[i], obj[keys[i]]);
  }
 };

 function definereactive(obj, key, val) {
  var dep = new dep();

  var property = object.getownpropertydescriptor(obj, key);
  if(property && property.configurable === false) {
   return;
  }

  var getter = property && property.get;
  var setter = property && property.set;

  var childob = observe(val);
  object.defineproperty(obj, key, {
   enumerable: true,
   configurable: true,
   get: function reactivegetter() {
    var value = getter ? getter.call(obj) : val;

    if(dep.target) {
     dep.depend();
     if(childob) {
      childob.dep.depend();
     }
    }
    return value;
   },
   set: function reactivesetter(newval) {
    var value = getter ? getter.call(obj) : val;
    if(newval === value) {
     return;
    }
    if(setter) {
     setter.call(obj, newval);
    } else {
     val = newval;
    }
    childob = observe(newval);
    dep.notify();
   }
  });
 }
 function observe(value) {
  if(!value || typeof value !== 'object') {
   return;
  }
  return new observer(value);
 }

 function vue(options) {
  var vm = this;
  this._el = options.el;
  var data = this._data = options.data;

  var keys = object.keys(data);
  var i = keys.length;
  while(i--) {
   proxy(this, keys[i]);
  }
  observe(data);

  var elem = document.getelementbyid(this._el);
  elem.innerhtml = vm.message;

  new watcher(this, 'message', function() {
   elem.innerhtml = vm.message;
  });

 }
 function proxy(vm, key) {
  object.defineproperty(vm, key, {
   configurable: true,
   enumerable: true,
   get: function proxygetter() {
    return vm._data[key];
   },
   set: function proxysetter(val) {
    vm._data[key] = val;
   }
  });
 }
 return vue;
})();

<!doctype html>
<html lang="en">
<head>
 <meta charset="utf-8">
 <title>document</title>
 <script type="text/javascript" src="vue.js"></script>
</head>
<body>
 <div id="app"></div>
 <script type="text/javascript">
  var app = new vue({
   el: 'app',
   data: {
    message: 'aaaaaaaaaaaaa'
   }
  });
 </script>
</body>
</html>


参考资料:


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。