一、原理
- 双向数据绑定:数据和视图同步,数据发生变化,视图跟着变动,视图变动,数据也随之产生扭转;
- 实现办法:通过Object.defineProperty()办法来实现;
Object.defineProperty()办法(JavaScript高级程序设计第三版P139):
Object.defineProperty()这个办法承受三个参数 属性所在的对象,属性的名字以及一个描述符对象.
let book = {
_year: 2004,
edition: 1
}
Object.defineProperty(book, 'year', {
get: function() {
console.log('调用了get办法');
return this._year;
},
set: function(newvalue) {
if (this._year !== newvalue) {
console.log('调用了set办法');
this._year = newvalue;
this.edition += 1;
}
}
});
book.year; // 调用了get办法
book.year = 2021; // 调用了set办法
二、双向数据绑定1.0
function Bind(options) {
this.el = options.el;
this.data = options.data;
getNode(this.el, this.data);
}
function getNode(el, data) {
let inputs = document.querySelector(el);
let children = inputs.children;
for (let i = 0; i < children.length; i++) {
compile(data, children[i]);
}
}
function compile(obj, data) {
let key = node.getAttribute('v-model');
define(data, key, data[key]);
}
function define(obj, key, val) {
Object.defineProperty(obj, key, {
get: function() {
return val;
},
set: function(newvalue) {
if (val !== newvalue) {
val = newvalue;
document.getElementsByClassName('insert')[0].innerHTML = newvalue;
}
}
})
}
let inputs = document.getElementsByTagName('input');
let vm = new Bind({
el: '#app',
data: {
name: '',
age: ''
}
});
function input1() {
vm.data.name = inputs[0].value;
}
function input2() {
vm.data.age = inputs[1].value;
}
这段代码尽管实现了实现了双向数据绑定,但也仅仅只是实现了这个性能,它还存在许多bug
比方:
- 代码整体看上去很凌乱,并且复用性不强;
- 应用的是函数的办法来定义Bind,所以编写代码的时候就须要时时思考this的指向问题;
- 在Bind()函数外面,如果options外面没有el这个属性,那么this.el就为null,前面在获取标签的时候也获取不了,前面也会跟着出错;compile()函数外面也没有判断key是否存在等等;
- 在input标签外面增加oninput事件来获取输出的数据;
……
三、双向数据绑定2.0
改良:
- 应用class来定义Bind;
- 应用 || 来避免参数options外面不存在el,data这些数据;
- 通过应用addEventListener来增加事件,并且思考了兼容问题;
- 为Bind增加了errorList数组来记录绑定时和绑定之后的谬误;
- 通过创立一个BindError对象,并采纳轮询的办法,来对Bind进行监听;
index.js
class Bind {
constructor(options) {
this.el = options.el || "";
this.data = options.data || {};
this.errorList = [];
this.getNode();
}
getNode() {
this.rootNode = document.querySelector(this.el);
const children = this.rootNode ? Array.prototype.slice.call(this.rootNode.children) : [];
children.forEach((child) => {
this.complie(child);
})
}
complie(node) {
const key = node.getAttribute('v-model');
if (key) {
this.addEvent(node, 'input', (e) => {
const { value = 'error' } = e.target;
this.data[key] = value;
});
this.define(this.data, key, this.data[key]);
} else {
var div = document.createElement('div');
div.appendChild(node)
this.errorList.push(`${div.innerHTML} doesn't have attribute v-model! `);
}
}
addEvent(node, type, handler) {
if (node.addEventListener) {
node.addEventListener(type, handler)
} else if (node.attachEvent) {
node.attachEvent('on' + type, handler)
} else {
node[type] = null;
}
}
define(obj, key, val) {
Object.defineProperty(obj, key, {
get: function () {
return val;
},
set: function (newvalue) {
if (newvalue !== val) {
val = newvalue;
document.getElementsByClassName('insert')[0].innerHTML = val;
}
}
});
}
}
var error = new BindError();
var vm = new Bind({
el: '#app',
data: {
name: '',
age: ''
}
})
error.addListenerList(vm);
error.js
class BindError {
constructor() {
this.listenerList = [];
}
addListenerList(obj) {
this.listenerList.push(obj);
}
interval = setInterval(()=>{
this.listenerList.forEach((obj)=>{
var { errorList } = obj;
var error = errorList.pop();
if(error){
throw new Error(error);
}
})
},1000);
}
有余:应用轮询,容易造成资源节约
双向数据绑定3.0
改良:不实用轮询,应用es6的Proxy来对Bind外面的errorList进行一个拦挡;
class Bind {
constructor(options) {
this.el = options.el || "";
this.data = options.data || {};
this.errorList = new Proxy([], {
set(target, prop, value) {
Reflect.set(target, prop, value);
throw new Error(value);
}
});
this.getNode();
}
getNode() {
this.rootNode = document.querySelector(this.el);
const children = this.rootNode ? Array.prototype.slice.call(this.rootNode.children) : [];
children.forEach((child) => {
this.complie(child);
})
}
complie(node) {
const key = node.getAttribute('v-model');
if (key) {
this.addEvent(node, 'input', (e) => {
const { value = 'error' } = e.target;
this.data[key] = value;
});
this.define(this.data, key, this.data[key]);
} else {
var div = document.createElement('div');
div.appendChild(node)
this.errorList.push(`${div.innerHTML} doesn't have attribute v-model! `);
}
}
addEvent(node, type, handler) {
if (node.addEventListener) {
node.addEventListener(type, handler)
} else if (node.attachEvent) {
node.attachEvent('on' + type, handler)
} else {
node[type] = null;
}
}
define(obj, key, val) {
Object.defineProperty(obj, key, {
get: function () {
return val;
},
set: function (newvalue) {
if (newvalue !== val) {
val = newvalue;
document.getElementsByClassName('insert')[0].innerHTML = val;
}
}
});
}
}
var vm = new Bind({
el: '#app',
data: {
name: '',
age: ''
}
})
发表回复