[TOC]
*****
## 1 意义
>[info] 前端的视图层和数据层之间的互动
>[info] 可以从视图层发生改变后数据层发生改变
>[info] 也可以从数据层发生改变后视图层发生改变
>[info] 因此需要双向绑定实现视图和数据之间的互动
## 2 手动绑定
>[info] (1)从数据到视图层
在数据对象上定义get和set方法
调用时手动调用get或set数据,改变数据后发出UI层的渲染操作
>[info] (2)从视图到数据层的变化驱动
主要是input,select,textarea等表单元素的UI层发生变化
通过监听dom的change,keypress,keyup等事件激活数据层数据的更新
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>data-binding-method-set</title>
</head>
<body>
;UI视图层
<input q-value="value" type="text" id="input">
<div q-text="value" id="el"></div>
;双向绑定
<script>
;UI层
var elems = [document.getElementById('el'), document.getElementById('input')];
;数据层
var data = {
value: 'hello!'
};
;UI层修改
var command = {
text: function(str){
this.innerHTML = str;
},
value: function(str){
this.setAttribute('value', str);
}
};
;节点扫描数据绑定处理
var scan = function(){
for(var i = 0, len = elems.length; i < len; i++){
;当前元素节点
var elem = elems[i];
elem.command = [];
;元素节点属性处理
for(var j = 0, len1 = elem.attributes.length; j < len1; j++){
;当前处理属性
var attr = elem.attributes[j];
;属性值修改
if(attr.nodeName.indexOf('q-') >= 0){
command[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]);
elem.command.push(attr.nodeName.slice(2));
}
}
}
}
;从数据层到UI层
function mvSet(key, value){
data[key] = value;
scan();
}
;UI事件监听
elems[1].addEventListener('keyup', function(e){
mvSet('value', e.target.value);
}, false);
;开始扫描
scan();
;定时修改数据层
setTimeout(function(){
mvSet('value', 'fuck');
},1000)
</script>
</body>
</html>
~~~
## 3 脏检查
>[info] 检查脏数据进行UI层的操作更新,脏检查机制在数据发生变化时进行的。
>[info] 对UI层的dom事件,xht事件做了封装,在里面触发进入digest流程
>[info] digest流程里面,从rootscope开始遍历,检查所有的watcher。
>[info] 通过设置的数据来查找与数据相关的元素,比较数据变化,如果变化则进行指令操作
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>data-binding-drity-check</title>
</head>
<body>
;UI层
<input q-event="value" ng-bind="value" type="text" id="input">
<div q-event="text" ng-bind="value" id="el"></div>
;数据绑定处理
<script>
;UI层
var elems = [document.getElementById('el'), document.getElementById('input')];
;数据层
var data = {
value: 'hello!'
};
;UI层修改
var command = {
text: function(str) {
this.innerHTML = str;
},
value: function(str) {
this.setAttribute('value', str);
}
};
;UI层节点扫描处理
var scan = function(elems) {
for (var i = 0, len = elems.length; i < len; i++) {
;当前节点
var elem = elems[i];
;节点指令
elem.command = {};
;节点属性遍历
for (var j = 0, len1 = elem.attributes.length; j < len1; j++) {
var attr = elem.attributes[j];
;事件属性获取
if (attr.nodeName.indexOf('q-event') >= 0) {
;获取ng-bind属性
var dataKey = elem.getAttribute('ng-bind') || undefined;
;注册到节点指令
command[attr.nodeValue].call(elem, data[dataKey]);
;获取节点指令对应的值
elem.command[attr.nodeValue] = data[dataKey];
}
}
}
}
;脏检查机制
var digest = function(elems) {
;节点遍历
for (var i = 0, len = elems.length; i < len; i++) {
;当前节点
var elem = elems[i];
;节点属性遍历
for (var j = 0, len1 = elem.attributes.length; j < len1; j++) {
var attr = elem.attributes[j];
;事件属性获取
if (attr.nodeName.indexOf('q-event') >= 0) {
;获取ng-bind属性的值
var dataKey = elem.getAttribute('ng-bind') || undefined;
;节点指令注册
if(elem.command[attr.nodeValue] !== data[dataKey]){
command[attr.nodeValue].call(elem, data[dataKey]);
elem.command[attr.nodeValue] = data[dataKey];
}
}
}
}
}
;遍历节点初始化指令
scan(elems);
;数据劫持监听
function $digest(value){
var list = document.querySelectorAll('[ng-bind='+ value + ']');
digest(list);
}
;UI层事件注册
if(document.addEventListener){
elems[1].addEventListener('keyup', function(e) {
data.value = e.target.value;
$digest(e.target.getAttribute('ng-bind'));
}, false);
}else{
elems[1].attachEvent('onkeyup', function(e) {
data.value = e.target.value;
$digest(e.target.getAttribute('ng-bind'));
}, false);
}
;数据层定时更新
setTimeout(function() {
data.value = 'fuck';
$digest('value');
}, 2000)
</script>
</body>
</html>
~~~
## 4 前端数据劫持
>[info] 使用Object.defineProperty对数据对象做属性get和set的监听
>[info] 所有数据的读取和赋值操作时则调用节点的指令。
>[info] 对应Object.defineProperty需要做浏览器兼容处理
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>data-binding-hijacking</title>
</head>
<body>
;UI层
<input q-value="value" type="text" id="input">
<div q-text="value" id="el"></div>
;数据绑定
<script>
;UI层
var elems = [document.getElementById('el'), document.getElementById('input')];
;数据层
var data = {
value: 'hello!'
};
;UI层修改指令
var command = {
text: function(str) {
this.innerHTML = str;
},
value: function(str) {
this.setAttribute('value', str);
}
};
;节点遍历生成指令
var scan = function() {
;节点遍历
for (var i = 0, len = elems.length; i < len; i++) {
;当前节点
var elem = elems[i];
;节点的指令
elem.command = [];
;节点属性遍历
for (var j = 0, len1 = elem.attributes.length; j < len1; j++) {
;当前属性
var attr = elem.attributes[j];
;指令属性
if (attr.nodeName.indexOf('q-') >= 0) {
command[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]);
elem.command.push(attr.nodeName.slice(2));
}
}
}
}
;默认属性
var bValue;
;属性劫持
var defineGetAndSet = function(obj, propName) {
try {
Object.defineProperty(obj, propName, {
get: function() {
return bValue;
},
set: function(newValue) {
bValue = newValue;
scan();
},
enumerable: true,
configurable: true
});
} catch (error) {
console.log("browser not supported.");
}
}
;指令初始化
scan();
;注册数据监听指令
defineGetAndSet(data, 'value');
;UI层事件注册
if(document.addEventListener){
elems[1].addEventListener('keyup', function(e) {
data.value = e.target.value;
}, false);
}else{
elems[1].attachEvent('onkeyup', function(e) {
data.value = e.target.value;
}, false);
}
;数据层自动更新
setTimeout(function() {
data.value = 'fuck';
}, 2000)
</script>
</body>
</html>
~~~
## 5 小结
>[info] 数据绑定实现UI层与数据层的同步更新
>[info] UI层发生变化后数据层进行自动同步
>[info] 数据层发送变化UI层自动同步更新
>[info] 核心思路是UI层的事件监听与数据层的set/get劫持
## 6 参考连接
[数据绑定](https://ouvens.github.io/frontend-javascript/2015/11/29/js-data-two-ways-binding.html)
- 概述
- 框架结构
- 编译入口(\entries)
- web-compiler.js(web编译)
- web-runtime.js(web运行时)
- web-runtime-wih-compiler.js(web编译运行)
- web-server-renderer.js(web服务器渲染)
- 核心实现 (\core)
- index.js(核心入口)
- config.js(核心配置)
- core\util(核心工具)
- core\observer(双向绑定)
- core\vdom(虚拟DOM)
- core\global-api(核心api)
- core\instance(核心实例)
- 模板编译(\compiler)
- compiler\parser(模板解析)
- events.js(事件解析)
- helper.js(解析助手)
- directives\ref.js(ref指令)
- optimizer.js(解析优化)
- codegen.js(渲染生成)
- index.js(模板编译入口)
- web渲染(\platforms\web)
- compiler(web编译目录)
- runtime(web运行时目录)
- server(web服务器目录)
- util(web工具目录)
- 服务器渲染(\server)
- render-stream.js(流式渲染)
- render.js(服务器渲染函数)
- create-renderer.js(创建渲染接口)
- 框架流程
- Vue初始化
- Vue视图数据绑定
- Vue数据变化刷新
- Vue视图操作刷新
- 框架工具
- 基础工具(\shared)
- 模板编译助手
- 核心实例工具
- Web渲染工具
- 基础原理
- dom
- string
- array
- function
- object
- es6
- 模块(Module)
- 类(Class)
- 函数(箭头)
- 字符串(扩展)
- 代理接口(Proxy)
- 数据绑定基础
- 数据绑定实现
- mvvm简单实现
- mvvm简单使用
- vdom算法
- vdom实现
- vue源码分析资料