浅谈实现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 相关文章推荐
Jsonp 跨域的原理以及Jquery的解决方案
Jun 27 Javascript
浏览器兼容console对象的简要解决方案分享
Oct 24 Javascript
JSONP获取Twitter和Facebook文章数的具体步骤
Feb 24 Javascript
JavaScript中的条件判断语句使用详解
Jun 03 Javascript
基于JSON格式数据的简单jQuery幻灯片插件(jquery-slider)
Aug 10 Javascript
Vue.js自定义指令的用法与实例解析
Jan 18 Javascript
Vue2.0如何发布项目实战
Jul 27 Javascript
详解http访问解析流程原理
Oct 18 Javascript
基于js文件加载优化(详解)
Jan 03 Javascript
vue中如何让子组件修改父组件数据
Jun 14 Javascript
Angular Material Icon使用详解
Nov 07 Javascript
vue-router二级导航切换路由及高亮显示的实现方法
Jul 10 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
法兰绒滤网冲泡
2021/03/03 冲泡冲煮
杏林同学录(八)
2006/10/09 PHP
JoshChen_web格式编码UTF8-无BOM的小细节分析
2013/08/16 PHP
PHP 如何获取二维数组中某个key的集合
2014/06/03 PHP
php插件Xajax使用方法详解
2017/08/31 PHP
iframe 自适应高度[在IE6 IE7 FF下测试通过]
2009/04/13 Javascript
JavaScript Event学习第九章 鼠标事件
2010/02/08 Javascript
JavaScript中把数字转换为字符串的程序代码
2013/06/19 Javascript
JavaScript实现带箭头标识的多级下拉菜单效果
2015/08/27 Javascript
jquery的幻灯片图片切换效果代码分享
2015/09/07 Javascript
jquery插件ajaxupload实现文件上传操作
2015/12/09 Javascript
JS两种类型的表单提交方法实例分析
2016/11/28 Javascript
JS实现的表头列头固定页面功能示例
2017/01/10 Javascript
vue中for循环更改数据的实例代码(数据变化但页面数据未变)
2017/09/15 Javascript
webpack打包node.js后端项目的方法
2018/03/10 Javascript
Vue结合后台导入导出Excel问题详解
2019/02/19 Javascript
Vue 中获取当前时间并实时刷新的实现代码
2020/05/12 Javascript
jQuery实现带进度条的轮播图
2020/09/13 jQuery
[23:18]Spirit vs Liquid Supermajor小组赛A组 BO3 第二场 6.2
2018/06/03 DOTA
python读取word文档的方法
2015/05/09 Python
Python+opencv+pyaudio实现带声音屏幕录制
2019/12/23 Python
Flask中sqlalchemy模块的实例用法
2020/08/02 Python
python实现单机五子棋
2020/08/28 Python
欧洲最大的高尔夫零售商:American Golf
2019/09/02 全球购物
三星俄罗斯授权在线商店:Samsung俄罗斯
2019/09/28 全球购物
美国名牌手表折扣网站:Jomashop
2020/05/22 全球购物
丑小鸭教学反思
2014/02/03 职场文书
移风易俗倡议书
2014/04/15 职场文书
中学生国旗下讲话稿
2014/04/26 职场文书
新学期红领巾广播稿
2014/10/04 职场文书
2014年发展党员工作总结
2014/11/12 职场文书
2014年质检员工作总结
2014/11/18 职场文书
学校德育工作总结2015
2015/05/11 职场文书
2016企业先进集体事迹材料
2016/02/25 职场文书
五年级作文之学校的四季
2019/12/05 职场文书
JavaScript 去重和重复次数统计
2021/03/31 Javascript