浅谈实现vue2.0响应式的基本思路


Posted in Javascript onFebruary 13, 2018

最近看了vue2.0源码关于响应式的实现,以下博文将通过简单的代码还原vue2.0关于响应式的实现思路。

注意,这里只是实现思路的还原,对于里面各种细节的实现,比如说数组里面数据的操作的监听,以及对象嵌套这些细节本实例都不会涉及到,如果想了解更加细节的实现,可以通过阅读源码 observer文件夹以及instance文件夹里面的state文件具体了解。

首先,我们先定义好实现vue对象的结构

class Vue {
  constructor(options) {
    this.$options = options;
    this._data = options.data;
    this.$el = document.querySelector(options.el);
  }
}

第一步:将data下面的属性变为observable

使用Object.defineProperty对数据对象做属性get和set的监听,当有数据读取和赋值操作时则调用节点的指令,这样使用最通用的=等号赋值就可以触发了。

//数据劫持,监控数据变化
function observer(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: ()=>{
   return val
  },
  set: newVal => {
   if(newVal === val)
    return
   val = newVal
  }
 })
}

第二步:实现一个消息订阅器

很简单,我们维护一个数组,这个数组,就放订阅者,一旦触发notify,订阅者就调用自己的update方法

class Dep {
 constructor() {
  this.subs = []
 }
 add(watcher) {
  this.subs.push(watcher)
 }
 notify() {
  this.subs.forEach((watcher) => watcher.cb())
 }
}

每次set函数,调用的时候,我们触发notify,实现更新

那么问题来了。谁是订阅者。对,是Watcher。。一旦 dep.notify()就遍历订阅者,也就是Watcher,并调用他的update()方法

function defineReactive(obj, key, val, cb) {
 const dep = new Dep()
 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: ()=>{
   return val
  },
  set: newVal => {
   if(newVal === val)
    return
   val = newVal
   dep.notify()
  }
 })
}

第三步:实现一个 Watcher

Watcher的实现比较简单,其实就是执行数据变化时我们要执行的操作

class Watcher {
 constructor(vm, cb) {
  this.cb = cb
  this.vm = vm
 }
 update(){
  this.run()
 }
 run(){
  this.cb.call(this.vm)
 } 
}

第四步:touch拿到依赖

上述三步,我们实现了数据改变可以触发更新,现在问题是我们无法将watcher与我们的数据联系到一起。

我们知道data上的属性设置defineReactive后,修改data 上的值会触发 set。那么我们取data上值是会触发 get了。所以可以利用这一点,先执行以下render函数,就可以知道视图的更新需要哪些数据的支持,并把它记录为数据的订阅者。

function defineReactive(obj, key, val, cb) {
 const dep = new Dep()
 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: ()=>{
   if(Dep.target){
    dep.add(Dep.target)
   }
   return val
  },
  set: newVal => {
   if(newVal === val)
    return
   val = newVal
   dep.notify()
  }
 })
}

最后我们来看用一个代理实现将我们对data的数据访问绑定在vue对象上

_proxy(key) {
  const self = this
  Object.defineProperty(self, key, {
   configurable: true,
   enumerable: true,
   get: function proxyGetter () {
    return self._data[key]
   },
   set: function proxySetter (val) {
    self._data[key] = val
   }
  })
}

Object.keys(options.data).forEach(key => this._proxy(key))

下面就是整个实例的完整代码

class Vue {
 constructor(options) {
  this.$options = options;
  this._data = options.data;
  this.$el =document.querySelector(options.el);
  Object.keys(options.data).forEach(key => this._proxy(key))
  observer(options.data)
  watch(this, this._render.bind(this), this._update.bind(this))
 }
 _proxy(key) {
  const self = this
  Object.defineProperty(self, key, {
   configurable: true,
   enumerable: true,
   get: function proxyGetter () {
    return self._data[key]
   },
   set: function proxySetter (val) {
    self._data[key] = val
   }
  })
 }
 _update() {
  console.log("我需要更新");
  this._render.call(this)
 }
 _render() {
  this._bindText();
 }

 _bindText() {
  let textDOMs=this.$el.querySelectorAll('[v-text]'),
  bindText;
  for(let i=0;i<textDOMs.length;i++){
    bindText=textDOMs[i].getAttribute('v-text');
    let data = this._data[bindText];
    if(data){
     textDOMs[i].innerHTML=data;
    }   
  }
 }
}

function observer(value, cb){
 Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
}

function defineReactive(obj, key, val, cb) {
 const dep = new Dep()
 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: ()=>{
   if(Dep.target){
    dep.add(Dep.target)
   }
   return val
  },
  set: newVal => {
   if(newVal === val)
    return
   val = newVal
   dep.notify()
  }
 })
}
function watch(vm, exp, cb){
 Dep.target = new Watcher(vm,cb);
 return exp()
}

 class Watcher {
 constructor(vm, cb) {
  this.cb = cb
  this.vm = vm
 }
 update(){
  this.run()
 }
 run(){
  this.cb.call(this.vm)
 } 
}

class Dep {
 constructor() {
  this.subs = []
 }
 add(watcher) {
  this.subs.push(watcher)
 }
 notify() {
  this.subs.forEach((watcher) => watcher.cb())
 }
}
Dep.target = null; 
var demo = new Vue({
 el: '#demo',
 data: {
 text: "hello world"
 }
 })
 
setTimeout(function(){
 demo.text = "hello new world"
 
}, 1000)

 <body>
  <div id="demo">
    <div v-text="text"></div>
  </div>
 </body>

上面就是整个vue数据驱动部分的整个思路。如果想深入了解更细节的实现,建议深入去看vue这部分的代码。

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

Javascript 相关文章推荐
js读取本地excel文档数据的代码
Nov 11 Javascript
jQuery之日期选择器的深入解析
Jun 19 Javascript
javascript间隔刷新的简单实例
Nov 14 Javascript
JavaScript判断变量是对象还是数组的方法
Aug 28 Javascript
javascript计时器编写过程与实现方法
Feb 29 Javascript
微信JSAPI支付操作需要注意的细节
Jan 10 Javascript
Ajax跨域实现代码(后台jsp)
Jan 21 Javascript
微信小程序 Buffer缓冲区的详解
Jul 06 Javascript
Angular.js ng-file-upload结合springMVC的使用教程
Jul 10 Javascript
JS中实现隐藏部分姓名或者电话号码的代码
Jul 17 Javascript
2分钟实现一个Vue实时直播系统的示例代码
Jun 05 Javascript
JavaScript实现前端网页版倒计时
Mar 24 Javascript
JS实现的文字间歇循环滚动效果完整示例
Feb 13 #Javascript
React中的refs的使用教程
Feb 13 #Javascript
tween.js缓动补间动画算法示例
Feb 13 #Javascript
基于Node.js实现压缩和解压缩的方法
Feb 13 #Javascript
Vue打包后出现一些map文件的解决方法
Feb 13 #Javascript
nginx部署访问vue-cli搭建的项目的方法
Feb 12 #Javascript
vue2.0实现前端星星评分功能组件实例代码
Feb 12 #Javascript
You might like
PHP异常Parse error: syntax error, unexpected T_VAR错误解决方法
2014/05/06 PHP
php中preg_replace正则替换用法分析【一次替换多个值】
2017/01/17 PHP
PHP时间戳和日期相互转换操作实例小结
2018/12/18 PHP
PHP读取文件,解决中文乱码UTF-8的方法分析
2020/01/22 PHP
利用jquery写的左右轮播图特效
2014/02/12 Javascript
解决jquery实现的radio重新选中的问题
2015/07/03 Javascript
js简单网速测试方法完整实例
2015/12/15 Javascript
jquery 动态合并单元格的实现方法
2016/08/26 Javascript
详解vue.js组件化开发实践
2016/12/14 Javascript
纯JS实现表单验证实例
2016/12/24 Javascript
详解angular中如何监控dom渲染完毕
2017/01/03 Javascript
详解Layer弹出层样式
2017/08/21 Javascript
Bootstrap 树控件使用经验分享(图文解说)
2017/11/06 Javascript
vue实现导航栏效果(选中状态刷新不消失)
2017/12/13 Javascript
详解vue后台系统登录态管理
2019/04/02 Javascript
详解vue开发中调用微信jssdk的问题
2019/04/16 Javascript
layuiAdmin循环遍历展示商品图片列表的方法
2019/09/16 Javascript
JS如何生成随机验证码
2020/03/02 Javascript
微信小程序实现搜索框功能及踩过的坑
2020/06/19 Javascript
解决VUE-Router 同一页面第二次进入不刷新的问题
2020/07/22 Javascript
JavaScript 中的六种循环方法
2021/01/06 Javascript
[01:13]2015国际邀请赛线下观战现场
2015/08/08 DOTA
使用python绘制常用的图表
2016/08/27 Python
解决python2.7用pip安装包时出现错误的问题
2017/01/23 Python
Python 自动化表单提交实例代码
2017/06/08 Python
Python3实现爬取指定百度贴吧页面并保存页面数据生成本地文档的方法
2018/04/22 Python
详解Python 协程的详细用法使用和例子
2018/06/15 Python
python儿童学游戏编程知识点总结
2019/06/03 Python
美国乡村商店:Plow & Hearth
2016/09/12 全球购物
新英格兰最大的特色礼品连锁店:The Paper Store
2018/07/23 全球购物
《小猫刮胡子》教学反思
2014/02/21 职场文书
操行评语大全
2014/04/30 职场文书
自强之星事迹材料
2014/05/12 职场文书
妈妈活动方案
2014/08/15 职场文书
2015年学校图书室工作总结
2015/05/19 职场文书
2015年法务工作总结范文
2015/05/23 职场文书