浅谈实现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 相关文章推荐
一个判断email合法性的函数[非正则]
Dec 09 Javascript
关于Aptana Studio生成自动备份文件的解决办法
Dec 23 Javascript
js防止表单重复提交的两种方法
Sep 30 Javascript
ExtJs纵坐标值重复问题的解决方法
Feb 27 Javascript
jquery获取及设置outerhtml的方法
Mar 09 Javascript
浅析JavaScript作用域链、执行上下文与闭包
Feb 01 Javascript
javascript DIV实现跟随鼠标移动
Mar 19 Javascript
两种js监听滚轮事件的实现方法
May 13 Javascript
BootStrap selectpicker后台动态绑定数据
Jun 01 Javascript
webpack v4 从dev到prd的方法
Apr 02 Javascript
Vue 实现列表动态添加和删除的两种方法小结
Sep 07 Javascript
详解三种方式解决vue中v-html元素中标签样式
Nov 22 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实现转换ubb代码的方法
2015/06/18 PHP
php微信开发接入
2016/08/27 PHP
PHP中empty,isset,is_null用法和区别
2017/02/19 PHP
JavaScript 模仿vbs中的 DateAdd() 函数的代码
2007/08/13 Javascript
javascript dom 操作详解 js加强
2009/07/13 Javascript
JavaScript中几个重要的属性(this、constructor、prototype)介绍
2013/05/19 Javascript
javascript获取select的当前值示例代码(兼容IE/Firefox/Opera/Chrome)
2013/12/17 Javascript
js获取视频时长代码
2014/04/10 Javascript
js+jquery常用知识点汇总
2015/03/03 Javascript
自定义jQuery插件方式实现强制对象重绘的方法
2015/03/23 Javascript
jQuery原生的动画效果
2015/07/10 Javascript
JavaScript运动减速效果实例分析
2015/08/04 Javascript
jqueryMobile使用示例分享
2016/01/12 Javascript
jQuery异步提交表单的两种方式
2016/09/13 Javascript
BootStrap中关于Select下拉框选择触发事件及扩展
2016/11/22 Javascript
从零学习node.js之模块规范(一)
2017/02/21 Javascript
vue实现裁切图片同时实现放大、缩小、旋转功能
2018/03/02 Javascript
vue.js获得当前元素的文字信息方法
2018/03/09 Javascript
JS中getElementsByClassName与classList兼容性问题解决方案分析
2019/08/07 Javascript
keep-alive保持组件状态的方法
2020/12/02 Javascript
python中的hashlib和base64加密模块使用实例
2014/09/02 Python
python模拟enum枚举类型的方法小结
2015/04/30 Python
python中的错误处理
2016/04/10 Python
pyenv命令管理多个Python版本
2017/03/26 Python
Python实现简单过滤文本段的方法
2017/05/24 Python
Python 实现王者荣耀中的敏感词过滤示例
2019/01/21 Python
python实现视频分帧效果
2019/05/31 Python
python之拟合的实现
2019/07/19 Python
Python获取统计自己的qq群成员信息的方法
2019/11/15 Python
Python实现aes加密解密多种方法解析
2020/05/15 Python
python多线程爬取西刺代理的示例代码
2021/01/30 Python
艺术系应届生的自我评价
2013/10/19 职场文书
军训自我鉴定范文
2014/02/13 职场文书
2014年社区居委会主任重阳节讲话稿
2014/09/25 职场文书
Python爬虫之爬取最新更新的小说网站
2021/05/06 Python
GitHub上77.9K的Axios项目有哪些值得借鉴的地方详析
2021/06/15 Javascript