对于 数据双向绑定 的核心其实就是 Object.defineProperty
Object.definedProperty方法可以在一个对象上直接定义一个新的属性、或修改一个对象已经存在的属性,最终返回这个对象。
当这个属性经过 definedProperty 设置 get、set后,对 属性的赋值和取值,都会调用 set 和 get,可以将其理解为 definedProperty 可以对 属性的 操作进行监控
<div id="app">
<p v-bind:title="message" id="b">{{ message }}</p>
<input v-model="message" id="a">
<input v-model="message">
<p v-bind:title="message">{{ message }}</p>
<p v-bind:title="message2">{{message2}}</p>
<input v-model="message2">
<p v-bind:title="message3">{{ message3 }}</p>
</div>
<script>
function watch(othis, el, key, callbacks) {
let data = othis.$data;
let message = data[key]
// 绑定代理
porxy(data, key, el, callbacks)
// 绑定完成后 将数据对象的值 设置到 el 对象上
data[key] = message;
}
function oninput(othis, el, key) {
// 绑定 input 事件
let data = othis.$data;
el.oninput = function (e) {
// 将 input 输入值设置到数据属性对象上
// 这里进行设置时将会处理属性 set 方法
data[key] = e.target.value;
}
}
function porxy(data, key, el, callbacks) {
// 核心:使用 defineProperty 对属性代理后,进行 属性的 取值 赋值, 实际上调用的是get 、 set方法
// 代理后 取数据对象的值 其实就是 el.value 的值, 设置取数据对象的值就是 设置 el.value 的值
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
// 代理后 数据对象的值 其实就是 el.value 的值
value = el.value;
if (callbacks) {
// 循环处理回调对象
for (let i = 0; i < callbacks.length; i++) {
let callback = callbacks[i];
callback.fn(callback.el, callback.key, value);;
}
}
return value
},
set: function (v) {
// 设置数据对象的值实际就是设置 el.value 的值
el && (el.value = v);
// 设置值后进行一次取值,用于触发 其他 el 对象的回调处理方法
data[key];
}
});
}
function dataPorxy(othis, data) {
for (const key in data) {
if (data.hasOwnProperty(key)) {
Object.defineProperty(othis, key, {
enumerable: true,
configurable: true,
get: function () {
return data[key];
},
set: function (v) {
data[key] = v;
}
});
}
}
}
function bind(othis, props) {
// 将数据对象进行绑定代理
for (const attrValue in props) {
if (props.hasOwnProperty(attrValue)) {
const element = props[attrValue];
watch(othis, element.el, attrValue, element.fn);
}
}
dataPorxy(othis, othis.$data)
}
function VBind(param) {
this.$data = param.data;
this.el = document.querySelector(param.el);
othis = this;
var reg = RegExp('^{{.*}}$');
let nodeList = this.el.querySelectorAll("*");
// 回调处理队列
let props = {};
nodeList.forEach(function (el, i) {
if (reg.test(el.innerHTML.trim())) {
let attrValue = el.innerHTML;
attrValue = attrValue.replace(/({|})/g, '').trim();
// 将同名数据对象回调处理放在同一个队列里 fn[]
// 并将其绑定到到 el 对象
props[attrValue] || (props[attrValue] = {
fn: [],
el: null
});
// 并将其绑定到到 el 对象
props[attrValue].el = el;
// 将回调对象 放入 fn 队列
props[attrValue].fn.push({
fn: function (el, key, v) {
el[key] = v;
},
key: "innerText",
el: el
});
}
let attrs = el.attributes;
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
const nodeName = attr.nodeName;
const attrValue = attr.nodeValue;
let names = nodeName.split(':');
switch (names[0]) {
case 'v-bind':
// 将同名数据对象回调处理放在同一个队列里 fn[]
// 并将其绑定到到 el 对象
props[attrValue] || (props[attrValue] = {
fn: [],
el: null
});
// 并将其绑定到到 el 对象
props[attrValue].el = el;
// 将回调对象 放入 fn 队列
props[attrValue].fn.push({
fn: function (el, key, v) {
el.setAttribute(key, v);
},
key: names[1],
el: el
});
break;
case 'v-model':
// 对 input 对象 绑定 input 事件
oninput(othis, el, attrValue);
props[attrValue] || (props[attrValue] = {
fn: [],
el: el
});
props[attrValue].el = el;
props[attrValue].fn.push({
fn: function (el, key, v) {
el[key] = v;
},
key: "value",
el: el
});
default:
break;
}
}
});
bind(this, props);
}
var b = new VBind({
el: '#app',
data: {
message: 'hello word',
message2: '双向绑定',
message3: '绑定到非 INPUT'
}
})
/**
* 执行下面的页面会自动改变
* b.message = 'abc'
* b.message2 = 'abc'
* b.message3 = 'abc'
*/
</script>