一、原理
- 双向数据绑定:数据和视图同步,数据发生变化,视图跟着变动,视图变动,数据也随之产生扭转;
- 实现办法:通过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: '' }})