Vue数据驱动模拟实现1


Posted in Javascript onJanuary 11, 2017

一、前言

Vue有一核心就是数据驱动(Data Driven),允许我们采用简洁的模板语法来声明式的将数据渲染进DOM,且数据与DOM是绑定在一起的,这样当我们改变Vue实例的数据时,对应的DOM元素也就会改变了。

如下:

<!DOCTYPE html>
<head>
 <meta charset="utf-8">
</head>
 <body>
  <div id="test">
   {{name}}
  </div>
  <script src="https://unpkg.com/vue/dist/vue.min.js"></script>
  <script>
   var vm = new Vue({
    el: '#test',
    data: {
     name: 'Monkey'
    }
   });
  </script>  
 </body>
</html>

当我们在chrome控制台,更改vm.name时,页面中的数据也随之改变,但我们并没有与DOM直接接触,效果如下:

Vue数据驱动模拟实现1

好了,今儿的核心就是模拟上述Demo中的数据驱动。

二、模拟Vue之数据驱动

通过粗浅地走读Vue的源码,发现达到这一效果的核心思路其实就是利用ES5的defineProperty方法,监听data数据,如果数据改变,那么就对页面做相关操作。

有了大体思路,那么我们就开始一步一步实现一个简易版的Vue数据驱动吧,简称SimpleVue。

Vue实例的创建过程,如下:

var vm = new Vue({
 el: '#test',
 data: {
  name: 'Monkey'
 }
});

因此,我们也依瓢画葫芦,构建SimpleVue构造函数如下:

function SimpleVue(obj){
 this.$el = document.querySelector(obj.el);
 this.$options = obj;
 this._data = Object.create(null);
 //入口
 this.init();
 obj = null;
};
SimpleVue.prototype = {
 constructor: SimpleVue,
 init: function(){
  //TODO 
 }
};

接下来,我们在SimpleVue原型上编写一个watchData方法,通过利用ES5原生的defineProperty方法,监听data中的属性,如果属性值改变,那么我们就进行相关的页面处理。

如下:

SimpleVue.prototype = {
 //监听data属性
 watchData: function(){
  var data = this.$options.data,//得到data对象
   keys = Object.keys(data),//data对象上全部的自身属性,返回数组
   that = this;
  keys.forEach(function(elem){//监听每个属性
   Object.defineProperty(that, elem, {
    enumerable: true,
    configurable: true,
    get: function(){
     return that._data[elem];
    },
    set: function(newVal){
     that._data[elem] = newVal;
     that.update();//数据变化,更新页面
    }
   });
   that[elem] = data[elem];//初次进入改变that[elem],从而触发update方法
  });
 }
};

好了,如果我们检测到数据变化了呢?

那么,我们就更新视图嘛。

但是,怎么更新呢?

简单的实现方式就是,在初次构建SimpleVue实例时,就将页面中的模板保存下来,每次实例数据一改变,就通过正则替换掉原始的模板,即双括号中的变量,如下:

SimpleVue.prototype = {
 //初始化SimpleVue实例时,就将原始模板保留
 getTemplate: function(){
  this.template = this.$el.innerHTML; 
 },
 //数据改变更新视图
 update: function(){
  var that = this,
   template = that.template,
   reg = /(.*?)\{\{(\w*)\}\}/g,
   result = '';
  result = template.replace(reg, function(rs, $1, $2){
   var val = that[$2] || '';
   return $1 + val;
  });
  this.$el.innerHTML = result;
  console.log('updated');
 }
};

好了,整合上述js代码,完整的SimpleVue如下:

function SimpleVue(obj){
 this.$el = document.querySelector(obj.el);
 this.$options = obj;
 this._data = Object.create(null);
 //入口
 this.init();
 obj = null;
};
SimpleVue.prototype = {
 constructor: SimpleVue,
 init: function(){
  this.getTemplate();
  this.watchData();
 },
 //初始化SimpleVue实例时,就将原始模板保留
 getTemplate: function(){
  this.template = this.$el.innerHTML; 
 },
 //监听data属性
 watchData: function(){
  var data = this.$options.data,//得到data对象
   keys = Object.keys(data),//data对象上全部的自身属性,返回数组
   that = this;
  keys.forEach(function(elem){//监听每个属性
   Object.defineProperty(that, elem, {
    enumerable: true,
    configurable: true,
    get: function(){
     return that._data[elem];
    },
    set: function(newVal){
     that._data[elem] = newVal;
     that.update();//数据变化,更新页面
    }
   });
   that[elem] = data[elem];
  });
 },
 //数据改变更新视图
 update: function(){
  var that = this,
   template = that.template,
   reg = /(.*?)\{\{(\w*)\}\}/g,
   result = '';
  result = template.replace(reg, function(rs, $1, $2){
   var val = that[$2] || '';
   return $1 + val;
  });
  this.$el.innerHTML = result;
  console.log('updated');
 }
};

测试代码如下:

<!DOCTYPE html>
<head>
 <meta charset="utf-8">
</head>
 <body>
  <div id="test">
   <div>{{name}}</div>
  </div>
  <script src="./SimpleVue.js"></script>
  <script>
   var vm = new SimpleVue({
    el: '#test',
    data: {
     name: 'Monkey'
    }
   });
  </script>  
 </body>
</html>

效果如下:

Vue数据驱动模拟实现1

三、优化

上述实现效果,还不错哦。

但是,我们走读下上述代码,感觉还可以优化下。

(1)、在watchData方法中监听每个data属性时,如果我们设置相同值,页面也会更新的,因为set是监听赋值的,它又不知道是不是同一个值,因此,优化如下:

Vue数据驱动模拟实现1

(2)、在上述基础,我们加入了新旧值判断,但是如果我们频繁更新data属性呢?那么也就会频繁调用update方法。例如,当我们给vm.name同时赋值两个值时,页面就会更新两次,如下:

Vue数据驱动模拟实现1

怎么解决呢?

利用节流,即可:

SimpleVue.throttle = function(method, context, delay){
 clearTimeout(method.tId);
 method.tId = setTimeout(function(){
  method.call(context);
 }, delay);
};

好了,将优化点整合到原有代码中,得下:

function SimpleVue(obj){
 this.$el = document.querySelector(obj.el);
 this.$options = obj;
 this._data = Object.create(null);
 this.init();
 obj = null;
};
SimpleVue.throttle = function(method, context, delay){
 clearTimeout(method.tId);
 method.tId = setTimeout(function(){
  method.call(context);
 }, delay);
};
SimpleVue.prototype = {
 constructor: SimpleVue,
 init: function(){
  this.getTemplate();
  this.watchData();
 },
 getTemplate: function(){
  this.template = this.$el.innerHTML; 
 },
 watchData: function(){
  var data = this.$options.data,
   keys = Object.keys(data),
   that = this;
  keys.forEach(function(elem){
   Object.defineProperty(that, elem, {
    enumerable: true,
    configurable: true,
    get: function(){
     return that._data[elem];
    },
    set: function(newVal){
     var oldVal = that[elem];
     if(oldVal === newVal){
      return;
     }
     that._data[elem] = newVal;
     SimpleVue.throttle(that.update, that, 50);
    }
   });
   that[elem] = data[elem];
  });
 },
 update: function(){
  var that = this,
   template = that.template,
   reg = /(.*?)\{\{(\w*)\}\}/g,
   result = '';
  result = template.replace(reg, function(rs, $1, $2){
   var val = that[$2] || '';
   return $1 + val;
  });
  this.$el.innerHTML = result;
  console.log('updated');
 }
};

且,为了让我们使用更加方便,我们可以在上述代码基础上,加入一个created钩子(当然,你可以加入更多),完整代码见github。

好了,简单的数据驱动,我们算 实现了,也优化了,但,其实上述简易版Vue有很多问题,例如:

1)、监听的属性是个对象呢?且对象里又有其他属性,不就监听不成功了么?如下:

Vue数据驱动模拟实现1

2)、通过上述1)介绍,如果监听的属性是个对象,那么又该如何渲染DOM呢?

3)、渲染DOM我们采用的是innerHTML,那么随着DOM的扩大,性能显而易见,又该如何解决?

等等问题,我们将在后续随笔通过精读源码,一步一步完善。

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

Javascript 相关文章推荐
javascript数组操作总结和属性、方法介绍
Apr 05 Javascript
5款JavaScript代码压缩工具推荐
Jul 07 Javascript
事件委托与阻止冒泡阻止其父元素事件触发
Sep 02 Javascript
JavaScript返回0-1之间随机数的方法
Apr 06 Javascript
详解Bootstrap按钮
Jan 04 Javascript
原生javascript实现的一个简单动画效果
Mar 30 Javascript
js指定步长实现单方向匀速运动
Jul 17 Javascript
vue axios 给生产环境和发布环境配置不同的接口地址(推荐)
May 08 Javascript
vue项目部署上线遇到的问题及解决方法
Jun 10 Javascript
JavaScript Canvas实现验证码
Aug 02 Javascript
Javascript call及apply应用场景及实例
Aug 26 Javascript
解决vue项目axios每次请求session不一致的问题
Oct 24 Javascript
利用iscroll4实现轮播图效果实例代码
Jan 11 #Javascript
详解js前端代码异常监控
Jan 11 #Javascript
Vue数据驱动模拟实现3
Jan 11 #Javascript
jQuery实现判断控件是否显示的方法
Jan 11 #Javascript
jQuery Form表单取值的方法
Jan 11 #Javascript
vue实现ajax滚动下拉加载,同时具有loading效果(推荐)
Jan 11 #Javascript
浅谈JavaScript中promise的使用
Jan 11 #Javascript
You might like
1 Tube Radio
2021/03/02 无线电
php随机取mysql记录方法小结
2014/12/27 PHP
PHP中创建和验证哈希的简单方法实探
2015/07/06 PHP
php实现生成带二维码图片并强制下载功能
2018/02/24 PHP
JS模拟的QQ面板上的多级可展开的菜单
2009/10/10 Javascript
js 居中漂浮广告
2010/03/21 Javascript
jQuery源码中的chunker 正则过滤符分析
2012/07/31 Javascript
js弹出对话框方式小结
2015/11/17 Javascript
详解AngularJs ui-router 路由的简单介绍
2017/04/26 Javascript
Vue CLI2升级至Vue CLI3的方法步骤
2019/05/20 Javascript
基于layui table返回的值的多级嵌套的解决方法
2019/09/19 Javascript
vue-resourc发起异步请求的方法
2020/02/11 Javascript
原生javascript中this几种常见用法总结
2020/02/24 Javascript
介绍Python中内置的itertools模块
2015/04/29 Python
浅谈Python 字符串格式化输出(format/printf)
2016/07/21 Python
python编程嵌套函数实例代码
2018/02/11 Python
python实现批量按比例缩放图片效果
2018/03/30 Python
python scipy求解非线性方程的方法(fsolve/root)
2018/11/12 Python
Django 中间键和上下文处理器的使用
2019/03/17 Python
如何在mac下配置python虚拟环境
2020/07/06 Python
Python从文件中读取数据的方法步骤
2020/11/18 Python
英国在线照明超市:Castlegate Lights
2019/10/30 全球购物
SQL Server提供的3种恢复模型都是什么? 有什么区别?
2012/05/13 面试题
信息管理专业推荐信
2013/10/29 职场文书
自我评价200字分享
2013/12/17 职场文书
自我评价是什么
2014/01/04 职场文书
设计顾问服务计划书
2014/05/04 职场文书
委托书的格式
2014/08/01 职场文书
八荣八耻的活动方案
2014/08/16 职场文书
社区助残日活动总结
2014/08/29 职场文书
解除劳动合同证明书模板
2014/11/20 职场文书
党员转正申请报告
2015/05/15 职场文书
2015年骨干教师工作总结
2015/05/26 职场文书
MySQL锁机制
2021/04/05 MySQL
关于JavaScript 中 if包含逗号表达式
2021/11/27 Javascript
springmvc直接不经过controller访问WEB-INF中的页面问题
2022/02/24 Java/Android