jQuery如果上了数据绑定。

692 阅读3分钟

如果说,vue、react是自动档,那么jQuery就是手动挡。自动挡虽然好开,但是总是缺少那么点乐趣,手动挡虽然慢慢被替代,不过自然有其独有的味道。如果,设想一下有这么一个中间产品,半自动档,会不会让老车焕发第二春?

先上一段代码片段

传统下的jQuery代码

<ul class="melon-normal" data-classes="瓜"> </ul>
<section>
  选择的瓜是:<span id="choose"></span>
</section>
    
<script>
// 一般意义上的jQuery开发形式
function createList(list) {
  let melonList = list.map(text => $('<li />').text(text));
  $('ul.melon-normal').empty().append(melonList);
}


$('ul.melon-normal').on('click', 'li', ({ target, delegateTarget }) => {
  let { classes } = $(delegateTarget).data()
  let name = $(target).text()
  $('#choose').text([classes, name])
});

let list = ['西瓜', '黄瓜', '苦瓜', '白瓜', '生瓜'];
createList(list);

</script>

数据绑定的核心


// 制作一个数据绑定形式
let observer = (() => {
  // 换成数据绑定的形式
  const genEventName = (id, key) => {
    return [id, key].join('_');
  }
  const notify = (id, key, newVal, oldVal) => {
    let notifyEvent = new CustomEvent(genEventName(id, key), {
      detail: [newVal, oldVal]
    });
    document.dispatchEvent(notifyEvent);
  }
  const bindEvent = (id, key, callback) => {
    document.addEventListener(genEventName(id, key), callback)
  }
  const create = (newObject, data) => {
    for (let key in data) {
      let value = data[key];
      Object.defineProperty(newObject, key, {
        get() {
          return value
        },
        set(newVal) {
          if (newVal !== value) {
            let oldVal = value;
            value = newVal;
            notify(this.__uuid, key, newVal, oldVal);
          }
        }
      })
    }
  }

  return function (data) {
    let newObject = Object.create(null);

    // 假设生成一个不重复的id
    let __uuid = parseInt(Math.random() * 1e5);
    newObject.__uuid = __uuid;

    return {
      on(keyname, callback) {
        if (typeof callback === 'function')
          bindEvent(newObject.__uuid, keyname, (e) => callback.apply(newObject, e.detail));
        return this;
      },
      create(callback) {
        create(newObject, data);
        if (typeof callback == 'function') {
          callback.call(newObject, newObject);
        }
        return newObject;
      }
    }
  }
})();

数据绑定的响应式

数据绑定,亦或者叫数据劫持。

现代浏览器下可以使用Proxy代理对象,通过中间人访问元数据,而这里使用重建新对象的形式。无论是proxy还是重建对象,都是对元数据的代理行为,不会修改元数据。

要操作一个数据的访问和设置,首先要知道数据什么时候被更新和获取,然后通过控制中间环节,可以很好的处理此间的行为,把真是的数据隐藏起来,这算是设计模式中的代理模式了。

本文中的数据劫持在create(newObject, data);完成,通过Object.defineProperty完成对元数据的key拷贝,这样,就有一份一比一key对应的数据对象了,后续的操作都通过本对象进行操作。

数据的响应式

当需求是,设置某个数据后,操作某个数据的变更结果。那通过什么形式实现呢?

通知与响应

dom操作上一个很重要的方式就是事件,比如click、change,那如果使用一个自定义的过程,模仿click、change会如何?

const genEventName = (id, key) => {
    return [id, key].join('_');
}

const notify = (id, key, newVal, oldVal) => {
    let notifyEvent = new CustomEvent(genEventName(id, key), {
      detail: [newVal, oldVal]
    });
    document.dispatchEvent(notifyEvent);
}

const bindEvent = (id, key, callback) => {
    document.addEventListener(genEventName(id, key), callback)
}

使用document.addEventListener监听事件,该事件可以是click、touchmove、也可以是a、b、c。使用此特性,可以构建一个不重复的事件,然后在某个时刻触发该事件,则有了以下的事件构建形式:

let newObject = Object.create(null); 
// 假设生成一个不重复的id 
let __uuid = parseInt(Math.random() * 1e5); 
newObject.__uuid = __uuid;

return {
    on(keyname, callback) { 
        if (typeof callback === 'function')
            bindEvent(newObject.__uuid, keyname, (e) => callback.apply(newObject, e.detail)); 
        return this;
    },
}

且在数据 data.key = newvalue 时,触发此事件即完成的事件闭环

Object.defineProperty(newObject, key, {
    set(newVal) {
      if (newVal !== value) {
        let oldVal = value;
        value = newVal;
        // 数据变化通知事件
        notify(this.__uuid, key, newVal, oldVal);
      }
    }
}

则流程为

graph TD
数据绑定 --> 主动key监听--> value变更-->事件通知

看一下完成实例

<ul class="home" data-classes="家"> </ul>
<section>
  选择的瓜是:<span id="choose"></span>
</section>

let data = observer({ list: [], choose: ""});

// 当list被修改时,更新html的内容
data.on('list', (newList, oldList) => {
  let allList = newList.map((text) => $('<li/>').text(text));
  $('ul.home').empty().append(allList);
});

// 当choose被更新时,修改choose的文本
data.on('choose',  (choose)=> $('#choose').text(choose))

// 创建整个监听结果
data.create(function onMounted(newObject) {
  $('ul.home').on('click', 'li', ({ target, delegateTarget }) => {
    let { classes } = $(delegateTarget).data()
    let name = $(target).text()
    
    // 当点击li时,修改choose的数据
    newObject.choose = [classes, name];
  });
  
  // 修改list的数据
  newObject.list = ["男人", '女人', '小孩', '狗', '家']
});

如果换成vue


<div id="app">
    <ul class="home" data-classes="家" ref="home">
        <li v-for="text in list" @click="liClick(text)">{{text}}</li>
    </ul>
    <section>
      选择的瓜是:<span>{{choose}}</span>
    </section>
</div>


new Vue {
    app:"#app"
    data:{
        list:[],
        classes:"家"
        choose:""
    },
    methods:{
        liClick(text){
            this.choose = [this.$refs.home.dataset.classes,text]
        }
    },
    mounted(){
        this.list = ["男人", '女人', '小孩', '狗', '家'];
    }
}

代码很清爽,大量的重复工作——监听、绑定、dom更新,都在不知不觉中完成,如果换车这种形式:

<ul class="home" data-classes>
    <li for-list="item" text-item on-click="chooseClick(item)"></li>
</ul>
<section>
  选择的瓜是:<span text-choose></span>
</section>

let data = observer('#app',{ list: [], choose: ""})
    .chooseClick(function(text){
        this.choose = text;
    })


// 创建整个监听结果
data.create(function onMounted(newObject) {  
  // 修改list的数据
  newObject.list = ["男人", '女人', '小孩', '狗', '家']
});

结后语

jQuery对作者而言,和jsp一样,就是青春。而现在mvvm的基础框架上,对前端的发展是一个很大的突破,让代码的结构更清晰。jQuery在历史的长河中,也贡献了很多设计思想,比如$()document.querySelector()。但是时代总要向前,也会不久的将来也会像怀念jQuery一样怀念vue、react...