JavaScript实现简单的双向数据绑定(EmberAngularVue)
Posted 刻刻帝丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript实现简单的双向数据绑定(EmberAngularVue)相关的知识,希望对你有一定的参考价值。
tip:有问题或者需要大厂内推的+我脉脉哦:丛培森 ٩( ‘ω’ )و
【本文源址:http://blog.csdn.net/q1056843325/article/details/72999948 转载请添加该地址】
什么是双向数据绑定呢?
简单的说
就是UI视图与数据绑定在了一块
也就是数据和视图是同步改变的
双向数据绑定最常见的应用场景就是表单
(应用场景还是很有限的)
现在我们要实现这样一个简单的数据绑定
输入栏中输入字符
和它绑定的节点内容同步改变
此外还有一个按钮用于生成随机数改变input和div内的数据
首先我们先把需要把html的简单结构实现
<input id="input" data-bind="demo">
<div id="output" data-bind="demo"></div>
<button id="random">随机数</button>
还需要在js中获取这些DOM节点
let $ = document.querySelector.bind(document);
let $i = $('#input');
let $o = $('#output');
let $random = $('#random');
#简易实现
如果仅仅是为了实现这样的效果实际上非常简单
我们很容易就可以想到input事件,然后动态改变
那么我们首先就来简单的实现一下
let def = 'default';
$i.value = def;
$o.textContent = def;
$i.oninput = function()
$o.textContent = $i.value;
$random.onclick = function()
let rand = Math.floor(Math.random()*10e5)
$i.value = rand;
$o.textContent = rand;
虽然实现了效果
但是实际上只有视图改变,影响数据改变的过程
而且也没有把节点联系在一起
#数据模型(Ember.js原理)
Ember.js使用了这种数据模型的方法
虽然很麻烦,但是很容易让我们理解
实际上就是把数据还有要节点封装在了一起
这样后续的更新一定会经过这个模型
模型就了解了变化
从而做出处理
首先我们来实现一个数据模型的类
当我们需要绑定一组节点时
就可以实例化这个数据模型
(为了方便下面我都使用ES6语法)
class DataModel
constructor(str = '')
this.data = str;
this.nodes = [];
bindTo(node)
this.nodes.push(node);
this.update();
update()
const INPUT_NODE = ['INPUT','TEXTAREA'];
let nodes = this;
for(let i = 0, node; node = nodes[i++];)
if(INPUT_NODE.includes(node.nodeName))
if(node.value !== this.data) //避免光标跳到尾部
node.value = this.data;
else
node.textContent = this.data;
set(str)
if(str !== this.value)
this.data = str;
this.update();
get()
return this.data;
this.data
就是我们模型的数据
而this.nodes
是我们绑定的节点列表
bindTo
方法接受我们的Dom节点并传入节点列表
既然有新节点进加入组织了(节点绑定),那么也肯定要让它接受新的数据(数据与UI改变)
update
方法用于更新视图,实际上是遍历所有绑定节点,判断类型然后做出改变
声明了数据模型类后我们就可以为input和div绑定到一个模型中了
let gModel =
demo: new DataModel('default')
;
//数据->视图
gModel[$i.getAttribute('data-bind')].bindTo($i);
gModel[$o.getAttribute('data-bind')].bindTo($o);
gModel
是我们声明的一个全局数据模型对象
因为页面中不一定只有这一组数据绑定
$i
与$o
的data-bind
属性值就相当于它们的“组织名”
这里我就起名demo了
使用模型的API来绑定这两个节点
//视图->数据
$i.addEventListener('input', function()
gModel[this.getAttribute('data-bind')].set(this.value);
);
$random.onclick = function()
gModel.demo.set(Math.floor(Math.random()*10e5));
最后绑定input事件还有按钮的click事件
当输入值后,就改变这个模型的data
set方法改变data的同时还会触发所有与之绑定在一起的节点做出更新
#脏检查(Angular.js原理)
Augular.js采用脏检查的方式来实现双向数据绑定
其原理是不会去监听数据的变化
而是我觉得你可能要发生数据变化的时候(用户交互,DOM操作等)
就去检查你的所有数据,看看到底有没有变化
不过这个数据检查是组件级别的
虽然如此,很多时候还是会产生很多没用的检查
我们需要模拟以下组件的核心函数
class Scope
constructor()
this.nodes = [];
this.watchers = [];
watch(watchExp, listener = function())
this.watchers.push(
watchExp,
listener
);
digest()
let dirty;
let watchers = this;
do
dirty = false;
for(var i = 0, watcher; watcher = watchers[i++];)
let newValue = watcher.watchExp();
let oldValue = watcher.last;
if(newValue !== oldValue)
dirty = true;
watcher.listener(newValue, oldValue);
watcher.last = newValue;
while(dirty);
update(newValue)
const INPUT_NODE = ['INPUT','TEXTAREA'];
let nodes = this;
for(let i = 0, node; node = nodes[i++];)
if(INPUT_NODE.includes(node.nodeName))
if(node.value !== newValue)
node.value = newValue;
else
node.textContent = newValue;
bindTo(node)
let nodes = this;
let key = node.getAttribute('data-bind');
if(!key)
return;
nodes.push(node);
this.update(this[key]);
this.watch(() =>
return this[key];
, (newValue, oldValue) =>
this.update(newValue);
);
区别于上一种方法
这里的this.nodes
代表某组件的全部节点
this.watchers
数组存储着watcher
每一个watcher封装着用于脏检查的函数
而watch方法就负责向watchers中添加watcher
它接受两个参数,一个取值函数watchExp和一个回调函数listener
digest方法会遍历整个watcher
last存储着上一个值,再通过取值函数获取值
通过比较可以知道值有没有变脏
如果脏了,就触发回调函数(渲染数据)并且更新last值
还要重新检查一遍watchers确保last和数据一致
(这里没有处理互相绑定死循环的问题,可以设置检查上限)
声明完组件类,我们就可以实例化一个组件
绑定节点,监听事件,还要手动进行脏检查
let scope = new Scope();
scope.demo = 'default';
//数据->视图
scope.bindTo($i);
scope.bindTo($o);
//视图->数据
$i.addEventListener('input', function()
scope[this.getAttribute('data-bind')] = this.value;
scope.digest();
);
$random.onclick = function()
scope.demo = Math.floor(Math.random()*10e5);
scope.digest();
#访问器监听(Vue.js原理)
vue.js实现数据变化影响视图变化的方式便是利用了ES5的setter
数据改变,触发setter渲染视图
视图影响数据没什么好说的,肯定需要监听input事件
这里我就写的简单点了
let data = ;
let def = 'default';
$i.value = def;
$o.textContent = def;
//数据->视图
Object.defineProperty(data, 'demo',
set: function(newValue)
$i.value = newValue;
$o.textContent = newValue;
);
//视图->数据
$i.addEventListener('input', function()
data[this.getAttribute('data-bind')] = this.value;
);
$random.onclick = function()
data.demo = Math.floor(Math.random()*10e5);
;
实际上vue实现的要比这复杂多得多
因为setter在很多情况下并不是万金油
也就是说并不是对象属性的任何变动它都能够监听的到
比如说以下场景:
- 向对象添加新属性
- 删除现有属性
- 数组改变
关于这些问题这里就不讨论了
如果有时间的同学可以去研究以下源码
此外还要说明一下
原本ES7草案中的Object.observe()由于严重的性能问题已经被移除了
以上是关于JavaScript实现简单的双向数据绑定(EmberAngularVue)的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript实现简单的双向数据绑定(EmberAngularVue)