由自定义事件到vue数据响应

前言

除了大家经常提到的自定义事件之外,浏览器本身也支持我们自定义事件,我们常说的自定义事件一般用于项目中的一些通知机制。最近正好看到了这部分,就一起看了下自定义事件不同的实现,以及vue数据响应的基本原理。

浏览器自定义事件

定义

除了我们常见的click,touch等事件之外,浏览器支持我们定义和分发自定义事件。

创建也十分简单:

//创建名为test的自定义事件
var event = new Event(‘test‘)
//如果是需要更多参数可以这样
var event = new CustomEvent(‘test‘, { ‘detail‘: elem.dataset.time });

大多数现代浏览器对new Event/CustomEvent 的支持还算可以(IE除外),可以看下具体情况:

可以放心大胆的使用,如果非要兼容IE那么有下面的方式

var event = document.createEvent(‘Event‘);
//相关参数
event.initEvent(‘test‘, true, true);

自定义事件的触发和原生事件类似,可以通过冒泡事件触发。

<form>
  <textarea></textarea>
</form>

触发如下,这里就偷个懒,直接拿mdn的源码来示例了,毕竟清晰易懂。

const form = document.querySelector(‘form‘);
const textarea = document.querySelector(‘textarea‘);

//创建新的事件,允许冒泡,支持传递在details中定义的所有数据
const eventAwesome = new CustomEvent(‘awesome‘, {
  bubbles: true,
  detail: { text: () => textarea.value }
});

  //form元素监听自定义的awesome事件,打印text事件的输出
  // 也就是text的输出内容
form.addEventListener(‘awesome‘, e => console.log(e.detail.text()));
  //
  // textarea当输入时,触发awesome
textarea.addEventListener(‘input‘, e => e.target.dispatchEvent(eventAwesome));

上面例子很清晰的展示了自定义事件定义、监听、触发的整个过程,和原生事件的流程相比看起来多了个触发的步骤,原因在原生事件的触发已经被封装无需手动处理而已。

应用

各大js类库

各种js库中用到的也比较多,例如zepto中的tap,原理就是监听touch事件,然后去触发自定的tap事件(当然这种成熟的框架做的是比较严谨的)。可以看下部分代码:

//这里做了个event的map,来将原始事件对应为自定义事件以便处理
// 可以只关注下ontouchstart,这里先判断是否移动端,移动端down就对应touchstart,up对应touchend,后面的可以先不关注
eventMap = (__eventMap && (‘down‘ in __eventMap)) ? __eventMap :
      (‘ontouchstart‘ in document ?
      { ‘down‘: ‘touchstart‘, ‘up‘: ‘touchend‘,
        ‘move‘: ‘touchmove‘, ‘cancel‘: ‘touchcancel‘ } :
      ‘onpointerdown‘ in document ?
      { ‘down‘: ‘pointerdown‘, ‘up‘: ‘pointerup‘,
        ‘move‘: ‘pointermove‘, ‘cancel‘: ‘pointercancel‘ } :
       ‘onmspointerdown‘ in document ?
      { ‘down‘: ‘MSPointerDown‘, ‘up‘: ‘MSPointerUp‘,
        ‘move‘: ‘MSPointerMove‘, ‘cancel‘: ‘MSPointerCancel‘ } : false)
 //监听事件
     $(document).on(eventMap.up, up)
      .on(eventMap.down, down)
      .on(eventMap.move, move)
 //up事件即touchend时,满足条件的会触发tap
 var up = function (e) {
      /* 忽略 */
       tapTimeout = setTimeout(function () {
           var event = $.Event(‘tap‘)
            event.cancelTouch = cancelAll
            if (touch.el) touch.el.trigger(event);
          },0)
        }
     //其他   

发布订阅

和原生事件一样,大部分都用于观察者模式中。除了上面的库之外,自己开发过程中用到的地方也不少。

举个例子,一个输入框表示单价,另一个div表示五本的总价,单价改变总价也会变动。借助自定义事件应该怎么实现呢。

html结构比较简单

<div >一本书的价格:<input type=‘text‘ id=‘el‘ value=10 /></div>
<div >5本书的价格:<span id=‘el2‘>50</span>元</div>

当改变input值得时候,效果如下demo地址

大概思路捋一下:

1、自定义事件,priceChange,用来监听改变price的改变

2、 加个监听事件,priceChange触发时改变total的值。

3、input value改变的时候,触发priceChange事件

代码实现如下:

  const count = document.querySelector(‘#el‘),
      total1 = document.querySelector(‘#el2‘);
  const eventAwesome = new CustomEvent(‘priceChange‘, {
      bubbles: true,
      detail: { getprice: () => count.value }
    });
  document.addEventListener(‘priceChange‘, function (e) {
      var price = e.detail.getprice() || 0
      total1.innerHTML=5 * price
    })
  el.addEventListener(‘change‘, function (e) {
    var val = e.target.value
    e.target.dispatchEvent(eventAwesome)
  });

代码确实比较简单,当然实现的方式是多样的。但是看起来是不是有点vue数据响应的味道。

确实目前大多数框架中都会用到发布订阅的方式来处理数据的变化。例如vue,react等,以vue为例子,我们可以来看看其数据响应的基本原理。

自定义事件

这里的自定义事件就是前面提到的第二层定义了,非基于浏览器的事件。这种事件也正是大型前端项目中常用到。对照原生事件,应该具有on、trigger、off三个方法。分别看一下

  1. 对照原生事件很容易理解,绑定一个事件,应该有对应方法名和回调,当然还有一个事件队列
class Event1{
    constructor(){
      // 事件队列
      this._events = {}
    }
    // type对应事件名称,call回调
    on(type,call){
      let funs = this._events[type]
      // 首次直接赋值,同种类型事件可能多个回调所以数组
      // 否则push进入队列即可
      if(funs){
        funs.push(call)
      }else{
        this._events.type=[]
        this._events.type.push(call)
      }
    }
}
  1. 触发事件trigger
// 触发事件
    trigger(type){
      let funs = this._events.type,
        [first,...other] = Array.from(arguments)
      //对应事件类型存在,循环执行回调队列
      if(funs){
        let i = 0,
            j = funs.length;
        for (i=0; i < j; i++) {
          let cb = funs[i];
          cb.apply(this, other);
        }
      }
    }
  1. 解除绑定:
// 取消绑定,还是循环查找
    off(type,func){
      let funs = this._events.type
      if(funs){
        let i = 0,
          j = funs.length;
        for (i = 0; i < j; i++) {
          let cb = funs[i];
           if (cb === func) {
            funs.splice(i, 1);
            return;
          }
        }
      }
      return this
    }
  }

这样一个简单的事件系统就完成了,结合这个事件系统,我们可以实现下上面那个例子。

html不变,绑定和触发事件的方式改变一下就好

 // 初始化 event1为了区别原生Event
  const event1 = new Event1()    

  // 此处监听 priceChange 即可
  event1.on(‘priceChange‘, function (e) {
      // 值获取方式修改
      var price = count.value || 0
      total1.innerHTML = 5 * price
    })
  el.addEventListener(‘change‘, function (e) {
    var val = e.target.value
    // 触发事件
    event1.trigger(‘priceChange‘)
  });

这样同样可以实现上面的效果,实现了事件系统之后,我们接着实现一下vue里面的数据响应。

vue的数据响应

说到vue的数据响应,网上相关文章简直太多了,这里就不深入去讨论了。简单搬运一下基本概念。详细的话大家可以自行搜索。

基本原理

直接看图比较直观:

就是通过观察者模式来实现,不过其通过数据劫持方式实现的更加巧妙。

数据劫持是通过Object.defineProperty()来监听各个属性的变化,从而进行一些额外操作。

举个简单例子:

let a = {
   b:‘1‘
}
Object.defineProperty(a,‘b‘,{
        get(){
            console.log(‘get>>>‘,1)
            return 1
        },
        set(newVal){
            console.log(‘set>>>11‘,‘设置是不被允许的‘)
            return 1
        }
    })
a.b //‘get>>>1‘
a.b = 11    //set>>>11 设置是不被允许的

所谓数据劫持就是在get/set操作时加上额外操作,这里是加了些log,如果在这里去监听某些属性的变化,进而更改其他属性也是可行的。

要达到目的,应该对每个属性在get是监听,set的时候出发事件,且每个属性上只注册一次。

另外应该每个属性对应一个监听者,这样处理起来比较方便,如果和上面那样全放在一个监听实例里面,有多个属性及复杂操作时,就太难维护了。

//基本数据
let data = {
    price: 5,
    count: 2
  },
callb = null  

可以对自定义事件进行部分改造,

不需要显式指定type,全局维护一个标记即可

事件数组一维即可,因为是每个属性对应一个示例

class Events {
    constructor() {
      this._events = []
    }
    on() {
      //此处不需要指定tyep了
      if (callb && !this._events.includes(callb)) {
        this._events.push(callb)
      }
    }
    triger() {
      this._events.forEach((callb) => {
        callb && callb()
      })
    }
  }

对应上图中vue的Data部分,就是实行数据劫持的地方

Object.keys(data).forEach((key) => {
    let initVlue = data[key]
    const e1 = new Events()
    Object.defineProperty(data, key, {
      get() {
         //内部判断是否需要注册
        e1.on()
        // 执行过置否
        callb = null
        // get不变更值
        return initVlue
      },
      set(newVal) {
        initVlue = newVal
        // set操作触发事件,同步数据变动
        e1.triger()
      }
    })
  })

此时数据劫持即事件监听准备完成,大家可能会发现callback始终为null,这始终不能起作用。为了解决该问题,下面的watcher就要出场了。

function watcher(func) {
    // 参数赋予callback,执行时触发get方法,进行监听事件注册
    callb = func
    // 初次执行时,获取对应值自然经过get方法注册事件
    callb()
    // 置否避免重复注册
    callb = null
  }
  // 此处指定事件触发回调,注册监听事件
  watcher(() => {
    data.total = data.price * data.count
  })

这样就保证了会将监听事件挂载上去。到这里,乞丐版数据响应应该就能跑了。

再加上dom事件的处理,双向绑定也不难实现。

可以将下面的完整代码放到console台跑跑看。

let data = {
    price: 5,
    count: 2
  },
    callb = null

  class Events {
    constructor() {
      this._events = []
    }
    on() {
      if (callb && !this._events.includes(callb)) {
        this._events.push(callb)
      }
    }
    triger() {
      this._events.forEach((callb) => {
        callb && callb()
      })
    }
  }

  Object.keys(data).forEach((key) => {
    let initVlue = data[key]
    const e1 = new Events()
    Object.defineProperty(data, key, {
      get() {
         //内部判断是否需要注册
        e1.on()
        // 执行过置否
        callb = null
        // get不变更值
        return initVlue
      },
      set(newVal) {
        initVlue = newVal
        // set操作触发事件,同步数据变动
        e1.triger()
      }
    })
  })
  function watcher(func) {
    // 参数赋予callback,执行时触发get方法,进行监听事件注册
    callb = func
    // 初次执行时,获取对应值自然经过get方法注册事件
    callb()
    // 置否避免重复注册
    callb = null
  }
  // 此处指定事件触发回调,注册监听事件
  watcher(() => {
    data.total = data.price * data.count
  })

结束语

参考文章

vue数据响应的实现

Creating and triggering events

看到知识盲点,就需要立即行动,不然下次还是盲点。正好是事件相关,就一并总结了下发布订阅相关进而到了数据响应的实现。个人的一点心得记录,分享出来希望共同学习和进步。更多请移步我的博客

demo地址

源码地址

原文地址:https://www.cnblogs.com/pqjwyn/p/9536356.html

时间: 2024-11-13 01:18:55

由自定义事件到vue数据响应的相关文章

Vue 数据响应式原理

Vue 数据响应式原理 Vue.js 的核心包括一套"响应式系统"."响应式",是指当数据改变后,Vue 会通知到使用该数据的代码.例如,视图渲染中使用了数据,数据改变后,视图也会自动更新. 举个简单的例子,对于模板: {{ name }} 创建一个 Vue 组件: var vm = new Vue({ el: '#root', data: { name: 'luobo' } }) 代码执行后,页面上对应位置会显示:luobo. 如果想改变显示的名字,只需要执行:

vue数据响应式原理

vue2.0数据响应式原理 对象 Obect.defineproperty 定义对象的属性mjm defineproperty 其实不是核心的为一个对象做数据双向绑定,而是去给对象做属性标签,设置一系列操作权限,只不过属性里的get和set实现了响应式 var ob = { a: 1, b: 2 } //1-对象 2-属性 3-对于属性的一系列配置 Object.defineProperty(ob, 'a' , { //a对象则是ob的绝对私有属性,,默认都是true writable: fal

由自定义事件到双向绑定

前言 除了大家经常提到的自定义事件之外,浏览器本身也支持我们自定义事件,我们常说的自定义事件一般也都是用于项目中的一些通知机制,例如双向绑定.一起看一下如何实现自定义事件,以及基于观察者模式的双向绑定的基本原理. 浏览器自定义事件 定义 除了我们常见的click,touch等事件之外,浏览器支持我们定义和分发自定义事件. 创建也十分简单: //创建名为test的自定义事件 var event = new Event('test') //如果是需要更多参数可以这样 var event = new

vue组件-子组件向父组件传递数据-自定义事件

自定义事件 我们知道,父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,应该怎样做?那就是自定义事件!

Vue:Axios异步通信、计算属性、内容分发、自定义事件

1. Axios 什么是Axios Axios 是一个开源的可以用在浏览器端和 NodeJS 的异步通信框架,她的主要作用就是实现 AJAX 异步通信,其功能特点如下: 从浏览器中创建 XMLHttpRequests 从 node.js 创建 http 请求 支持 Promise API [JS中链式编程] 拦截请求和响应 转换请求数据和响应数据 取消请求 自动转换 JSON 数据 客户端支持防御 XSRF(跨站请求伪造) GitHub:https://github.com/axios/axio

Vue组件绑定自定义事件

Vue组件使用v-on绑定自定义事件: 可以分为3步理解: 1.在组件模板中按照正常事件机制绑定事件: template: '<button v-on:click="increment">{{ counter }}</button>', 如上,v-on:click就是用来给子组件绑定点击事件的,这就是原生的自带的事件,容易理解. 2.子组件的事件发生时,在事件函数中向父组件"报告"这一事件(使用$emit): methods: { incre

vue自定义事件将组件内的数据传输到实例中去使用

vue自定义事件将组件内的数据传输到实例中去使用 <body> <div id="app"> <h1 style="color:deeppink">{{ outer_title }}</h1> //③给实例绑定一个方法 <hello :reader="new_msg"  v-on:chang_event="chang_tit"></hello> </

Vue自定义事件

父组件使用props传递数据给子组件,子组件怎么跟父组件通信呢?这时,Vue的自定义事件就派上用场了.本文将详细介绍Vue自定义事件: 使用 v-on(eventName) 监听事件 使用 $emit(eventName) 触发事件 我们在myapp/src/components下新建一个组件Emit.vue,同时我们得在主组件APP.vue中引入该组件,以及注册该组件 然后我们在Emit.vue文件中去些一个自定义事件的例子 当我们点击按钮就能获取到组件内部的事件和参数 原文地址:https:

[02]WPF异步响应,自定义事件、委托——多线程处理

题记 在编写有GUI的程序时,会遇到这样一种情形:用户点击了一个按钮,程序处理这个事件,然而这个处理过程耗时间较长.我们不想让软件卡在这里,而是让用户可以继续使用其他的软件功能.这种问题可以用多线程的事件响应来解决.这里,我就WPF的多线程事件响应做一个简单的归纳. 一.简单的异步的事件响应 在WPF中,针对简单的多线程处理过程,我们可以使用.NET自带的BackgroundWork完成.BackgroundWork的处理过程就是异步的,不会让用户界面停止响应. using System.Com