vue的双向绑定原理(数据驱动)
程序员文章站
2022-04-19 15:36:32
...
vue 采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter, getter,在数据变化时发布信息给订阅者,触发相应的监听回调。
1. js实现简单的双向绑定
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<input type="text" class="name">
<h1></h1>
<script>
var obj = {};
Object.defineProperty(obj, 'a',{
set: function (value) {
document.getElementsByTagName('h1')[0].innerHTML = value;
console.log('更新了' + value);
},
get: function() {
return obj;
}
})
document.addEventListener('keyup', function(e) {
obj.a = e.target.value;
});
</script>
</body>
</html>
对于set来说,只要触发到obj.a = xxx的赋值操作,则会触发到set函数。
2. 较完整的js双向绑定代码(引用其他作者)
<!DOCTYPE html>
<head>
<title>myVue</title>
</head>
<style>
#app {
text-align: center;
}
</style>
<body>
<div id="app">
<form>
<input type="text" v-model="number">
<button type="button" v-click="increment">增加</button>
</form>
<h3 v-bind="number"></h3>
</div>
</body>
<script>
function myVue(options) {
this._init(options);
}
myVue.prototype._init = function (options) {
this.$options = options;
this.$el = document.querySelector(options.el);
this.$data = options.data;
this.$methods = options.methods;
this._binding = {};
this._obverse(this.$data);
this._complie(this.$el);
}
myVue.prototype._obverse = function (obj) {
console.log(obj);
var value;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
this._binding[key] = {
_directives: []
};
value = obj[key];
if (typeof value === 'object') {
this._obverse(value);
}
var binding = this._binding[key];
Object.defineProperty(this.$data, key, {
enumerable: true,
configurable: true,
get: function () {
console.log(`获取${value}`);
return value;
},
set: function (newVal) {
console.log(`更新${newVal}`);
if (value !== newVal) {
value = newVal;
binding._directives.forEach(function (item) {
item.update();
})
}
}
})
}
}
}
myVue.prototype._complie = function (root) {
var _this = this;
var nodes = root.children;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.children.length) {
this._complie(node);
}
if (node.hasAttribute('v-click')) {
node.onclick = (function () {
var attrVal = nodes[i].getAttribute('v-click');
return _this.$methods[attrVal].bind(_this.$data);
})();
}
if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
node.addEventListener('input', (function(key) {
var attrVal = node.getAttribute('v-model');
_this._binding[attrVal]._directives.push(new Watcher(
'input',
node,
_this,
attrVal,
'value'
))
return function() {
_this.$data[attrVal] = nodes[key].value;
}
})(i));
}
if (node.hasAttribute('v-bind')) {
var attrVal = node.getAttribute('v-bind');
_this._binding[attrVal]._directives.push(new Watcher(
'text',
node,
_this,
attrVal,
'innerHTML'
))
}
}
}
function Watcher(name, el, vm, exp, attr) {
this.name = name; //指令名称,例如文本节点,该值设为"text"
this.el = el; //指令对应的DOM元素
this.vm = vm; //指令所属myVue实例
this.exp = exp; //指令对应的值,本例如"number"
this.attr = attr; //绑定的属性值,本例为"innerHTML"
this.update();
}
Watcher.prototype.update = function () {
this.el[this.attr] = this.vm.$data[this.exp];
}
window.onload = function() {
var app = new myVue({
el:'#app',
data: {
number: 0
},
methods: {
increment: function() {
this.number ++;
},
}
})
}
</script>
对上面代码的理解:
代码段,一开始,通过window.onload进行页面加载,并创建了app对象,触发了function myVue “构造函数”的执行,myVue函数进行_init初始化,在_init中,赋值后,先执行_obverse函数,在_obverse函数中,采用递归的方法,将app的dada中的所有属性绑定了相应set,和get操作,并在set操作中写明代码若触发到set则更新相应的数据。接着执行_complie函数,在_complie函数中递归查找所有绑定了v-click、v-model、v-bind的节点,若是有绑定v-bind、和v-model的节点,则将该结点创建为watcher的对象,并存入_binding数组中。此时则完成了初始化操作!现在,假设用户修改了文本框input的值,此时则会被input 的addEventListener监听到,则触发到相应的监听回调函数,执行 _this.$data[attrVal] = nodes[key].value;操作,进而触发到set函数,紧接着则触发到update函数,将watcher对象进行相应的更新,则完成了整个过程。