Vue双向数据绑定原理深度解析
⾸先,什么是双向数据绑定?Vue是三⼤MVVM框架之⼀,数据绑定简单来说,就是当数据发⽣变化时,相应的视图会进⾏更新,当视图更新时,数据也会跟着变化。
在分析其原理和代码的时候,⼤家⾸先了解如下⼏个js函数的作⽤:
1. [].slice.call(lis): 将伪数组转换为真数组
2. deType: 得到节点类型
3. Object.defineProperty(obj, propertyName, {}): 给对象添加/修改属性(指定描述符)
configurable: true/false 是否可以重新define
enumerable: true/false 是否可以枚举(for..in / keys())
value: 指定初始值
writable: true/false value是否可以修改存取(访问)描述符
get: 函数, ⽤来得到当前属性值
set: 函数, ⽤来监视当前属性值的变化
4. Object.keys(obj): 得到对象⾃⾝可枚举的属性名的数组
5. DocumentFragment: ⽂档碎⽚(⾼效批量更新多个节点)
6. obj.hasOwnProperty(prop): 判断prop是否是obj⾃⾝的属性
如果想了解这些函数具体使⽤:
⾸先,我来看⼀下如何实现最基础的数据绑定:
<body>
<div>请输⼊:<input type="text" id="inputId"/></div>
<div>输⼊的值为:<span id="showId"></span></div>
</body>
<script>
var inputValue = ElementById('inputId');
var showValue = ElementById('showId');
var obj = {};
Object.defineProperty(obj, 'msg', {
enumerable: true,
configurable: true,
set (newVal) {
showValue.innerHTML = newVal;
}
})
inputValue.addEventListener('input', function(e) {
obj.msg = e.target.value;
})
</script>
View Code
对于vue来说,Vue.js则是通过数据劫持以及结合发布者-订阅者来实现的数据绑定,数据劫持是利⽤ES5的Object.defineProperty(obj, key, val)来劫持各个属性的的setter以及getter,在数据变动时发布消息给订阅者,从⽽触发相应的回调来更新视图。
我们来看⼀下数据双向绑定的流程图:
1、实现⼀个数据Obverser,对data中的数据进⾏监听,若有变化,通知相应的订阅者。
2、实现⼀个指令解析器Compile,对于每个元素上的指令进⾏解析,根据指令替换数据,更新视图。
3、实现⼀个Watcher,⽤来连接Obverser和Compile, 并为每个属性绑定相应的订阅者,当数据发⽣变
化时,执⾏相应的回调函数,从⽽更新视图。
4、构造函数(new MVue({}))
我们来看⼀下对应的js代码:
⼀、Obverser.js
function Observer(data) {
// 保存data对象
this.data = data;
// ⾛起
this.walk(data);
}
Observer.prototype = {
walk: function(data) {
var me = this;
// 遍历data中所有属性
Object.keys(data).forEach(function(key) {
// 针对指定属性进⾏处理
});
},
convert: function(key, val) {
// 对指定属性实现响应式数据绑定
this.defineReactive(this.data, key, val);
},
defineReactive: function(data, key, val) {
// 创建与当前属性对应的dep对象
var dep = new Dep();
// 间接递归调⽤实现对data中所有层次属性的劫持
var childObj = observe(val);
// 给data重新定义属性(添加set/get)
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
// 建⽴dep与watcher的关系
if (Dep.target) {
dep.depend();
}
// 返回属性值
return val;
},
set: function(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// 新的值是object的话,进⾏监听
childObj = observe(newVal);
// 通过dep
}
});
}
};
function observe(value, vm) {
/
/ value必须是对象, 因为监视的是对象内部的属性
if (!value || typeof value !== 'object') {
return;
}
// 创建⼀个对应的观察都对象
return new Observer(value);
};
var uid = 0;
function Dep() {
// 标识属性
this.id = uid++;
/
/ 相关的所有watcher的数组
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
depend: function() {
Dep.target.addDep(this);
},
removeSub: function(sub) {
var index = this.subs.indexOf(sub);
if (index != -1) {
this.subs.splice(index, 1);
}
},
notify: function() {
// 通知所有相关的watcher(⼀个订阅者)
this.subs.forEach(function(sub) {
sub.update();
});
}
};
Dep.target = null;
View Code
1). Observer
* ⽤来对data所有属性数据进⾏劫持的构造函数
* 给data中所有属性重新定义属性描述(get/set)
* 为data中的每个属性创建对应的dep对象
2). Dep(Depend)
* data中的每个属性(所有层次)都对应⼀个dep对象
* 创建的时机:
* 在初始化define data中各个属性时创建对应的dep对象
* 在data中的某个属性值被设置为新的对象时
* 对象的结构
{
id, // 每个dep都有⼀个唯⼀的id
subs //包含n个对应watcher的数组(subscribes的简写)
}
* subs属性说明
* 当⼀个watcher被创建时, 内部会将当前watcher对象添加到对应的dep对象的subs中
* 当此data属性的值发⽣改变时, 所有subs中的watcher都会收到更新的通知, 从⽽最终更新对应的界⾯⼆、模板解析(Compile.js)
function Compile(el, vm) {
// 保存vm
this.$vm = vm;
// 保存el元素
this.$el = this.isElementNode(el) ? el : document.querySelector(el); // 如果el元素存在
if (this.$el) {
// 1. 取出el中所有⼦节点, 封装在⼀个framgment对象中
this.$fragment = de2Fragment(this.$el);
// 2. 编译fragment中所有层次⼦节点
this.init();
// 3. 将fragment添加到el中
this.$el.appendChild(this.$fragment);
}
}
Compile.prototype = {
node2Fragment: function (el) {
var fragment = ateDocumentFragment(),
child;
// 将原⽣节点拷贝到fragment
vuejs流程图插件while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
},
init: function () {
// 编译fragment
thispileElement(this.$fragment);
},
compileElement: function (el) {
// 得到所有⼦节点
var childNodes = el.childNodes,
// 保存compile对象
me = this;
// 遍历所有⼦节点
[].slice.call(childNodes).forEach(function (node) {
// 得到节点的⽂本内容
var text = Content;
// 正则对象(匹配⼤括号表达式)
var reg = /\{\{(.*)\}\}/; // {{name}}
// 如果是元素节点
if (me.isElementNode(node)) {
// 编译元素节点的指令属性
mepile(node);
// 如果是⼀个⼤括号表达式格式的⽂本节点
} else if (me.isTextNode(node) && st(text)) {
/
/ 编译⼤括号表达式格式的⽂本节点
mepileText(node, RegExp.$1); // RegExp.$1: 表达式 name }
// 如果⼦节点还有⼦节点
if (node.childNodes && node.childNodes.length) {
// 递归调⽤实现所有层次节点的编译
mepileElement(node);
}
});
},
compile: function (node) {
// 得到所有标签属性节点
var nodeAttrs = node.attributes,
me = this;
// 遍历所有属性
[].slice.call(nodeAttrs).forEach(function (attr) {
// 得到属性名: v-on:click
var attrName = attr.name;
// 判断是否是指令属性
if (me.isDirective(attrName)) {
// 得到表达式(属性值): test
var exp = attr.value;
// 得到指令名: on:click
var dir = attrName.substring(2);
// 事件指令
if (me.isEventDirective(dir)) {
// 解析事件指令
compileUtil.eventHandler(node, me.$vm, exp, dir);
// 普通指令
} else {
// 解析普通指令
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp); }
// 移除指令属性
}
});
},
compileText: function (node, exp) {
// 调⽤编译⼯具对象解析
<(node, this.$vm, exp);
},
isDirective: function (attr) {
return attr.indexOf('v-') == 0;
},
isEventDirective: function (dir) {
return dir.indexOf('on') === 0;
},
isElementNode: function (node) {
deType == 1;
},
isTextNode: function (node) {
deType == 3;
}
};
// 指令处理集合
var compileUtil = {
/
/ 解析: v-text/{{}}
text: function (node, vm, exp) {
this.bind(node, vm, exp, 'text');
},
// 解析: v-html
html: function (node, vm, exp) {
this.bind(node, vm, exp, 'html');
},
// 解析: v-model
model: function (node, vm, exp) {
this.bind(node, vm, exp, 'model');
var me = this,
val = this._getVMVal(vm, exp);
node.addEventListener('input', function (e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
me._setVMVal(vm, exp, newValue);
val = newValue;
});
},
/
/ 解析: v-class
class: function (node, vm, exp) {
this.bind(node, vm, exp, 'class');
},
// 真正⽤于解析指令的⽅法
bind: function (node, vm, exp, dir) {
/*实现初始化显⽰*/
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论