浅谈实现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 相关文章推荐
extjs 学习笔记(一) 一些基础知识
Oct 13 Javascript
javascript(jquery)利用函数修改全局变量的代码
Nov 02 Javascript
ExtJs3.0中Store添加 baseParams 的Bug
Mar 10 Javascript
JavaScript高级程序设计(第3版)学习笔记5 js语句
Oct 11 Javascript
初识Node.js
Sep 03 Javascript
jstl中判断list中是否包含某个值的简单方法
Oct 14 Javascript
浅谈webpack编译vue项目生成的代码探索
Dec 11 Javascript
vue实现二级导航栏效果
Oct 19 Javascript
bootstrap实现嵌套模态框的实例代码
Jan 10 Javascript
Vue通过provide inject实现组件通信
Sep 03 Javascript
vue实现一个矩形标记区域(rectangle marker)的方法
Oct 28 Javascript
Nuxt.js 静态资源和打包的操作
Nov 06 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(1) php开发环境配置
2010/02/15 PHP
CI框架无限级分类+递归的实现代码
2016/11/01 PHP
CI框架(CodeIgniter)操作redis的方法详解
2018/01/25 PHP
一段效率很高的for循环语句使用方法
2007/08/13 Javascript
超棒的javascript页面顶部卷动广告效果
2007/12/01 Javascript
JS图片切换的具体方法(带缩略图版)
2013/11/12 Javascript
node.js中的buffer.write方法使用说明
2014/12/10 Javascript
js改变embed标签src值的方法
2015/04/10 Javascript
jquery实现的动态回到顶部特效代码
2015/10/28 Javascript
jQuery使用$.ajax进行即时验证的方法
2015/12/08 Javascript
jQuery通过deferred对象管理ajax异步
2016/05/20 Javascript
浅谈javascript运算符——条件,逗号,赋值,()和void运算符
2016/07/15 Javascript
AngularJS入门教程之服务(Service)
2016/07/27 Javascript
AngularJS基础 ng-readonly 指令简单示例
2016/08/02 Javascript
利用bootstrapValidator验证UEditor
2016/09/14 Javascript
JavaScript简单编程实例学习
2020/02/14 Javascript
JavaScript React如何修改默认端口号方法详解
2020/07/28 Javascript
JS实现炫酷轮播图
2020/11/15 Javascript
Vue常用API、高级API的相关总结
2021/02/02 Vue.js
Python脚本实现DNSPod DNS动态解析域名
2015/02/14 Python
python中pika模块问题的深入探究
2018/10/13 Python
Pycharm更换python解释器的方法
2018/10/29 Python
Python两台电脑实现TCP通信的方法示例
2019/05/06 Python
python中的RSA加密与解密实例解析
2019/11/18 Python
python字符串替换re.sub()实例解析
2020/02/09 Python
基于Python的OCR实现示例
2020/04/03 Python
Python参数传递实现过程及原理详解
2020/05/14 Python
HTML5 CSS3新的WEB标准和浏览器支持
2009/07/16 HTML / CSS
工程现场管理求职自荐信
2013/10/02 职场文书
手机促销活动方案
2014/02/05 职场文书
初三班主任寄语大全
2014/04/04 职场文书
伊索寓言教学反思
2014/05/01 职场文书
销售人员求职信
2014/07/22 职场文书
好人好事演讲稿
2014/09/01 职场文书
国庆65周年演讲稿:回首往昔,展望未来
2014/09/21 职场文书
运输公司工作总结
2015/08/11 职场文书