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 相关文章推荐
slice函数的用法 之不错的应用
Dec 29 Javascript
JavaScript 内置对象属性及方法集合
Jul 04 Javascript
Ext JS 4实现带week(星期)的日期选择控件(实战二)
Aug 21 Javascript
node.js中的buffer.write方法使用说明
Dec 10 Javascript
JS实现的简洁纵向滑动菜单(滑动门)效果
Oct 19 Javascript
js改变html的原有内容实现方法
Oct 05 Javascript
JavaScript中捕获/阻止捕获、冒泡/阻止冒泡方法
Dec 07 Javascript
JS中用childNodes获取子元素换行会产生一个子元素
Dec 08 Javascript
vue中如何让子组件修改父组件数据
Jun 14 Javascript
vue slots 组件的组合/分发实例
Sep 06 Javascript
jQuery 点击获取验证码按钮及倒计时功能
Sep 20 jQuery
基于Vue实现平滑过渡的拖拽排序功能
Jun 12 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
php字符串截取中文截取2,单字节截取模式
2007/12/10 PHP
php foreach 使用&amp;(与运算符)引用赋值要注意的问题
2010/02/16 PHP
Symfony2开发之控制器用法实例分析
2016/02/05 PHP
Laravel执行migrate命令提示:No such file or directory的解决方法
2016/03/16 PHP
Kindeditor编辑器添加图片上传水印功能(php代码)
2017/08/03 PHP
php+croppic.js实现剪切上传图片功能
2018/08/14 PHP
php7 list()、session及其他模块的修改实例分析
2020/05/25 PHP
[原创]js与自动伸缩图片 自动缩小图片的多浏览器兼容的方法总结
2007/03/12 Javascript
项目实践之javascript技巧
2007/12/06 Javascript
href下载文件根据id取url并下载
2014/05/28 Javascript
javascript运动框架用法实例分析(实现放大与缩小效果)
2016/01/08 Javascript
js HTML5手机刮刮乐代码
2020/09/29 Javascript
详解Vue学习笔记入门篇之组件的内容分发(slot)
2017/07/17 Javascript
浅谈redux, koa, express 中间件实现对比解析
2019/05/23 Javascript
js模拟F11页面全屏显示
2019/09/17 Javascript
vue中js判断长时间不操作界面自动退出登录(推荐)
2020/01/22 Javascript
[45:40]Ti4 冒泡赛第二天NEWBEE vs NaVi 1
2014/07/15 DOTA
Python读写Excel文件的实例
2013/11/01 Python
python中使用百度音乐搜索的api下载指定歌曲的lrc歌词
2014/07/18 Python
Python中的os.path路径模块中的操作方法总结
2016/07/07 Python
Python实现的简单dns查询功能示例
2017/05/24 Python
详解Python中正则匹配TAB及空格的小技巧
2019/07/26 Python
PyQt5的相对布局管理的实现
2020/08/07 Python
HTML5 Canvas标签使用收录
2009/07/07 HTML / CSS
Opodo英国旅游网站:预订廉价航班、酒店和汽车租赁
2018/07/14 全球购物
师范应届毕业生自荐信
2013/11/18 职场文书
晨会主持词
2014/03/17 职场文书
创建绿色学校先进个人材料
2014/08/20 职场文书
领导班子四风查摆对照检查材料思想汇报
2014/10/05 职场文书
起诉离婚协议书样本
2014/11/25 职场文书
2015年教育实习工作总结
2015/04/24 职场文书
小学英语教学随笔
2015/08/14 职场文书
python实现批量提取指定文件夹下同类型文件
2021/04/05 Python
MySql新手入门的基本操作汇总
2021/05/13 MySQL
python 字典和列表嵌套用法详解
2021/06/29 Python
node.js使用express-fileupload中间件实现文件上传
2021/07/16 Javascript