Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【面经】字节跳动 笔试1:实现一个类可以完成事件 on,once,trigger,off #1

Open
liam61 opened this issue Mar 17, 2019 · 4 comments
Labels
面经 面经

Comments

@liam61
Copy link
Owner

liam61 commented Mar 17, 2019

手撸一个事件机制

关键词:发布-订阅模式

其实核心就是维护一个对象,对象的 key 存的是事件 type,对应的 value 为触发相应 type 的回调函数,即 listeners,然后 trigger 时遍历通知,即 forEach 进行回调执行。

class EventTarget {
  constructor() {
    this.listeners = {}; // 储存事件的对象
  }

  on(type, callback) {
    if (!this.listeners[type]) this.listeners[type] = []; // 如果是第一次监听该事件,则初始化数组
      this.listeners[type].push(callback);
  }

  once(type, callback) {
   if (!this.listeners[type]) this.listeners[type] = [];
      callback._once = true; // once 只触发一次,触发后 off 即可
      this.listeners[type].push(callback);
  }

  off(type, callback) {
    const listeners = this.listeners[type];
    if (Array.isArray(listeners)) {
      // filter 返回新的数组,会每次对 this.listeners[type] 分配新的空间
      // this.listeners[type] = listeners.filter(l => l !== callback);
      const index = listeners.indexOf(callback); // 根据 type 取消对应的回调
      this.listeners[type].splice(index, 1); // 用 splice 要好些,直接操作原数组

      if (this.listeners[type].length === 0) delete this.listeners[type]; // 如果回调为空,删除对该事件的监听
    }
  }

  trigger(event) {
    const { type } = event; // type 为必传属性
    if (!type) throw new Error('没有要触发的事件!');

    const listeners = this.listeners[type]; // 判断是否之前对该事件进行监听了
    if (!listeners) throw new Error(`没有对象监听 ${type} 事件!`);

    if (!event.target) event.target = this;

    listeners.forEach(l => {
      l(event);
      if (l._once) this.off(type, l); // 如果通过 once 监听,执行一次后取消
    });
  }
}

// 测试
function handleMessage(event) { console.log(`message received: ${ event.message }`); }

function handleMessage2(event) { console.log(`message2 received: ${ event.message }`); }

const target = new EventTarget();

target.on('message', handleMessage);
target.on('message', handleMessage2);
target.trigger({ type: 'message', message: 'hello custom event' }); // 打印 message,message2

target.off('message', handleMessage);
target.trigger({ type: 'message', message: 'off the event' }); // 只打印 message2

target.once('words', handleMessage);
target.trigger({ type: 'words', message: 'hello2 once event' }); // 打印 words
target.trigger({ type: 'words', message: 'hello2 once event' }); // 报错:没有对象监听 words 事件!
@liam61 liam61 added the 面经 面经 label Mar 17, 2019
@linfanxxxx
Copy link

大佬,如果on的callback是个匿名函数,就无法off了咋办

@liam61
Copy link
Owner Author

liam61 commented Mar 28, 2019

@linfanxxxx 这个模拟的是 dom2 的方法,一般都是用带名的,你可以回想 button 如何定义事件的 div.addEventListener('click', fn); div.removeEventListener('click', fn); 当然你也可以用 dom0 级事件,div.onclick = null; 这样回收机制机就会清理内存了

@AqingCyan
Copy link

大佬,我想问一下你是社招还是校招啊。还有就是这是观察者模式还是发布订阅啊,在我印象里面,发布者和订阅者在互相能知晓的情况下,应该是观察者啊。

@liam61
Copy link
Owner Author

liam61 commented Oct 8, 2019

@AqingCyan 校招。观察者和订阅发布模式是有区别的,主要是看有无中介者来传递消息,也就是像你说的是否能相互感知,但平时的话也可以不做区分,这里手撸的事件的话算是观察者模式

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
面经 面经
Projects
None yet
Development

No branches or pull requests

3 participants