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下操作css的float属性的特殊写法
Aug 22 Javascript
jquery json 实例代码
Dec 02 Javascript
High Performance JavaScript(高性能JavaScript)读书笔记分析
May 05 Javascript
使用Json比用string返回数据更友好,也更面向对象一些
Sep 13 Javascript
js 获取class的元素的方法 以及创建方法getElementsByClassName
Mar 11 Javascript
盘点javascript 正则表达式中 中括号的【坑】
Mar 16 Javascript
js 自带的 map() 方法全面了解
Aug 16 Javascript
微信小程序(应用号)开发新闻客户端实例
Oct 24 Javascript
vue脚手架中配置Sass的方法
Jan 04 Javascript
24行JavaScript代码实现Redux的方法实例
Nov 17 Javascript
nodemon实现Typescript项目热更新的示例代码
Nov 19 Javascript
three.js利用射线Raycaster进行碰撞检测
Mar 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类Class的概念
2012/06/14 PHP
win7下memCache的安装过程(具体操作步骤)
2013/06/28 PHP
PHPExcel中文帮助手册|PHPExcel使用方法(分享)
2017/06/09 PHP
PHP函数用法详解【初始化、嵌套、内置函数等】
2020/06/02 PHP
JavaScript this 深入理解
2009/07/30 Javascript
javascript实现tabs选项卡切换效果(自写原生js)
2013/03/19 Javascript
JQUERY 获取IFrame中对象及获取其父窗口中对象示例
2013/08/19 Javascript
javascript禁制后退键(Backspace)实例代码
2013/11/15 Javascript
jQuery实现提交按钮点击后变成正在处理字样并禁止点击的方法
2015/03/24 Javascript
基于MVC5和Bootstrap的jQuery TreeView树形控件(一)之数据支持json字符串、list集合
2016/08/11 Javascript
Vue.js学习笔记之 helloworld
2016/08/14 Javascript
微信小程序封装http访问网络库实例代码
2017/05/24 Javascript
Node.js 8 中的重要新特性
2017/06/28 Javascript
Vue的土著指令和自定义指令实例详解
2018/02/04 Javascript
详解Ant Design of React的安装和使用方法
2018/12/27 Javascript
微信小程序new Date()方法失效问题解决方法
2019/07/29 Javascript
基于javascript实现贪吃蛇小游戏
2019/11/25 Javascript
JS实现联想、自动补齐国家或地区名称的功能
2020/07/07 Javascript
Node.js 中判断一个文件是否存在
2020/08/24 Javascript
[00:32]2016完美“圣”典风云人物:Maybe宣传片
2016/12/05 DOTA
Python中的条件判断语句基础学习教程
2016/02/07 Python
对numpy中array和asarray的区别详解
2018/04/17 Python
tensorflow 恢复指定层与不同层指定不同学习率的方法
2018/07/26 Python
python3.6利用pyinstall打包py为exe的操作实例
2018/10/31 Python
HTML5中meta属性的使用方法
2016/02/29 HTML / CSS
html5需遵循的6个设计原则
2016/04/27 HTML / CSS
世界上最大的街头服饰网站:Karmaloop
2017/02/04 全球购物
中专自我鉴定
2014/02/05 职场文书
2015年工会工作总结范文
2015/07/23 职场文书
公司人力资源管理制度
2015/08/05 职场文书
php TP5框架生成二维码链接
2021/04/01 PHP
python随机打印成绩排名表
2021/06/23 Python
Java实现房屋出租系统详解
2021/10/05 Java/Android
关于JavaScript 中 if包含逗号表达式
2021/11/27 Javascript
海贼王十大潜力果实,路飞仅排第十,第一可毁世界(震震果实)
2022/03/18 日漫
mysql 索引的数据结构为什么要采用B+树
2022/04/26 MySQL