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确认框的三种使用方法
Dec 17 Javascript
jquery通过visible来判断标签是否显示或隐藏
May 08 Javascript
jQuery使用fadein方法实现渐出效果实例
Mar 27 Javascript
第九篇Bootstrap导航菜单创建步骤详解
Jun 21 Javascript
JavaScript中获取时间的函数集
Aug 16 Javascript
Extjs让combobox写起来简洁又漂亮
Jan 05 Javascript
ajax前台后台跨域请求处理方式
Feb 08 Javascript
React Native 自定义下拉刷新上拉加载的列表的示例
Mar 01 Javascript
浅谈React之状态(State)
Sep 19 Javascript
微信小程序点击view动态添加样式过程解析
Jan 21 Javascript
jquery实现简单自动轮播图效果
Jul 29 jQuery
基于Vue+Webpack拆分路由文件实现管理
Nov 16 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
虹吸式咖啡探讨–研磨
2021/03/03 冲泡冲煮
模仿OSO的论坛(四)
2006/10/09 PHP
按上下级层次关系输出内容的PHP代码
2010/07/17 PHP
PHP中的生成XML文件的4种方法分享
2012/10/06 PHP
从PHP的源码中深入了解stdClass类
2014/04/18 PHP
php cookie 详解使用实例
2016/11/03 PHP
PHP远程连接oracle数据库操作实现方法图文详解
2019/04/11 PHP
Javascript typeof 用法
2008/12/28 Javascript
jquery子元素过滤选择器使用示例
2013/06/24 Javascript
使用indexOf等在JavaScript的数组中进行元素查找和替换
2013/09/18 Javascript
详解JavaScript中localStorage使用要点
2016/01/13 Javascript
javascript ASCII和Hex互转的实现方法
2016/12/27 Javascript
微信小程序之蓝牙的链接
2017/09/26 Javascript
解决vue 路由变化页面数据不刷新的问题
2018/03/13 Javascript
Javascript中弹窗confirm与prompt的区别
2018/10/26 Javascript
关于Vue源码vm.$watch()内部原理详解
2019/04/26 Javascript
uni-app如何页面传参数的几种方法总结
2020/04/28 Javascript
Element Dropdown下拉菜单的使用方法
2020/07/26 Javascript
如何手写简易的 Vue Router
2020/10/10 Javascript
Python中time模块与datetime模块在使用中的不同之处
2015/11/24 Python
python进程的状态、创建及使用方法详解
2019/12/06 Python
python 定义类时,实现内部方法的互相调用
2019/12/25 Python
Python random模块的使用示例
2020/10/10 Python
jupyter notebook 写代码自动补全的实现
2020/11/02 Python
让IE支持HTML5的方法
2012/12/11 HTML / CSS
html5 制作地图当前定位箭头的方法示例
2020/01/10 HTML / CSS
马云的职业生涯规划之路
2014/01/01 职场文书
人力资源专员岗位职责
2014/01/30 职场文书
消防先进事迹材料
2014/02/10 职场文书
婚礼主持结束词
2014/03/13 职场文书
岗位竞聘书范文
2014/03/31 职场文书
经典禁毒标语
2014/06/16 职场文书
导游词开场白
2015/01/31 职场文书
2015年中学团委工作总结
2015/07/22 职场文书
MySQL创建表操作命令分享
2022/03/25 MySQL
高通2023 年将发布高性能PC处理器
2022/04/29 数码科技