一、原理

  1. 双向数据绑定:数据和视图同步,数据发生变化,视图跟着变动,视图变动,数据也随之产生扭转;
  2. 实现办法:通过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
比方:

  1. 代码整体看上去很凌乱,并且复用性不强;
  2. 应用的是函数的办法来定义Bind,所以编写代码的时候就须要时时思考this的指向问题;
  3. 在Bind()函数外面,如果options外面没有el这个属性,那么this.el就为null,前面在获取标签的时候也获取不了,前面也会跟着出错;compile()函数外面也没有判断key是否存在等等;
  4. 在input标签外面增加oninput事件来获取输出的数据;

......

三、双向数据绑定2.0

改良:

  1. 应用class来定义Bind;
  2. 应用 || 来避免参数options外面不存在el,data这些数据;
  3. 通过应用addEventListener来增加事件,并且思考了兼容问题;
  4. 为Bind增加了errorList数组来记录绑定时和绑定之后的谬误;
  5. 通过创立一个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: ''    }})