一、前言

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直接接触,效果如下:

好了,今儿的核心就是模拟上述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>

效果如下:

三、优化

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

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

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

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

怎么解决呢?

利用节流,即可:

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)、监听的属性是个对象呢?且对象里又有其他属性,不就监听不成功了么?如下:

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

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

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

模拟Vue之数据驱动1的更多相关文章

  1. 模拟Vue之数据驱动2

    一.前言 在随笔“模拟Vue之数据驱动1”结尾处,我们说到如果监听的属性是个对象呢?那么这个对象中的其他属性岂不就是监听不了了吗? 如下: 倘若user中的name.age属性变化,如何知道它们变化了 ...

  2. 模拟Vue之数据驱动3

    一.前言 在"模拟Vue之数据驱动2"中,我们实现了个Observer构造函数,通过它可以达到监听已有数据data中的所有属性. 但,倘若我们想在某个对象中,新增某个属性呢? 如下 ...

  3. 模拟Vue之数据驱动4

    一.前言 在"模拟Vue之数据驱动3"中,我们实现了为每个对象扩展一个$set方法,用于新增属性使用,这样就可以监听新增的属性了. 当然,数组也是对象,也可以通过$set方法实现新 ...

  4. 模拟Vue之数据驱动5

    一.前言 在"模拟Vue之数据驱动4"中,我们实现了push.pop等数组变异方法. 但是,在随笔末尾我们提到,当pop.sort这些方法触发后,该怎么办呢?因为其实,它们并没有往 ...

  5. 模拟Vue之数据驱动

    一.前言 在随笔"模拟Vue之数据驱动1"结尾处,我们说到如果监听的属性是个对象呢?那么这个对象中的其他属性岂不就是监听不了了吗? 如下: 倘若user中的name.age属性变化 ...

  6. 模拟vue的tag属性,在react里实现自定义Link

    我封装了一个简单的实现react里自定义Link的方法,方便大家使用. 因为普通组件没有metch.location.history等属性.只有在<Router>里面的<compon ...

  7. vue-toy: 200行代码模拟Vue实现

    vue-toy 200行左右代码模拟vue实现,视图渲染部分使用React来代替Snabbdom,欢迎Star. 项目地址:https://github.com/bplok20010/vue-toy ...

  8. vue实现数据驱动视图原理

    一.什么是数据驱动 数据驱动是vuejs最大的特点.在vuejs中,所谓的数据驱动就是当数据发生变化的时候,用户界面发生相应的变化,开发者不需要手动的去修改dom. 比如说我们点击一个button,需 ...

  9. 模拟vue实现简单的webpack打包

    一.安装nodejs,查看是否安装成功 二.package.json项目初始化 npm init 电脑有node环境,在根目录下运行命令npm init初始化项目,根据提示输入项目相关信息,然后运行. ...

随机推荐

  1. 2018-2019-2 网络对抗技术 20165212 Exp4 恶意代码分析

    2018-2019-2 网络对抗技术 20165212 Exp4 恶意代码分析 原理与实践说明 1.实践目标 监控你自己系统的运行状态,看有没有可疑的程序在运行. 分析一个恶意软件,就分析Exp2或E ...

  2. flask第30篇——宏macro和import标签

    宏是Jinja2特有的,像Django则没有这个. 先新建一个项目macroDemo: 然后在templates文件夹中新建index.html文件,并在代码中返回渲染后的文件: 然后回到index. ...

  3. (转)Linux 定时关机、休眠命令

    立刻关机:sudo haltsudo init 0 sudo shutdown -h nowsudo shutdown -h 0....定时/延时关机:sudo shutdown -h 19:3019 ...

  4. 南阳oj-ASCII码排序-用了一个晚上

    #include <iostream> #include <sstream> #include <stdio.h> #include <string> ...

  5. ZH奶酪:隐马尔可夫模型学习小记——forward算法+viterbi算法+forward-backward算法(Baum-welch算法)

    网上关于HMM的学习资料.博客有很多,基本都是左边摘抄一点,右边摘抄一点,这里一个图,那里一个图,公式中有的变量说不清道不明,学起来很费劲. 经过浏览几篇博文(其实有的地方写的也比较乱),在7张4开的 ...

  6. 一个高效的敏感词过滤方法(PHP)

    $badword = array( '张三','张三丰','张三丰田' ); $badword1 = array_combine($badword,array_fill(0,count($badwor ...

  7. DEDECMS ShowMsg()样式修改 提示信息的修改以及美化

    织梦DedeCMS系统,处处都在用到提示信息,但是这个提示框,前台后台一层不变,太死板了,可能有很多人都有过去修改它的想法,只是苦于不知道去哪里 改.今天我就来说说这个吧,DedeCMS的所有提示信息 ...

  8. 使用纯生js操作cookie

    前段时间做项目的时候要使用js操作cookie,jquery也有相应的插件,不过还是觉得纯生的js比较好,毕竟不依赖jq. //获得coolie 的值 function cookie(name) { ...

  9. Android Studio 默认keystore 以及自定义keystore

    我们使用Android Studio 运行或测试我们的app  它使用一个默认的debug.keystore进行签名. 这个默认签名(keystore)是不需要密码的,它的默认位置在 $HOME/.a ...

  10. Android画图之抗锯齿 paint 和 Canvas 两种方式

    在画图的时候,图片如果旋转或缩放之后,总是会出现那些华丽的锯齿.其实Android自带了解决方式.    方法一:给Paint加上抗锯齿标志.然后将Paint对象作为参数传给canvas的绘制方法. ...