ForeveHG / Frontend-Daily-Interview

学习,尝试回答一些前端面试题
1 stars 0 forks source link

26. 观察者模式(编程) #27

Open ForeveHG opened 4 years ago

ForeveHG commented 4 years ago

请实现下面的自定义事件 Event 对象的接口,功能见注释(测试1) 该 Event 对象的接口需要能被其他对象拓展复用(测试2)

// 测试1
Event.on('test', function (result) {
    console.log(result);
});
Event.on('test', function () {
    console.log('test');
});
Event.emit('test', 'hello world'); // 输出 'hello world' 和 'test'
// 测试2
var person1 = {};
var person2 = {};
Object.assign(person1, Event);
Object.assign(person2, Event);
person1.on('call1', function () {
    console.log('person1');
});
person2.on('call2', function () {
    console.log('person2');
});
person1.emit('call1'); // 输出 'person1'
person1.emit('call2'); // 没有输出
person2.emit('call1'); // 没有输出
person2.emit('call2'); // 输出 'person2'
var Event = {
    // 通过on接口监听事件eventName
    // 如果事件eventName被触发,则执行callback回调函数
    on: function (eventName, callback) {
        //你的代码
    },
    // 触发事件 eventName
    emit: function (eventName) {
        //你的代码
    }
};
ForeveHG commented 4 years ago

草率而且很有问题版:

var Event = {
  listeners: {},
  // 通过on接口监听事件eventName
  // 如果事件eventName被触发,则执行callback回调函数
  on: function (eventName, callback) {
    if (!this.listeners[eventName]) this.listeners[eventName] = [];
    this.listeners[eventName].push(callback)
  },
  // 触发事件 eventName
  emit: function (eventName) {
    if (this.listeners[eventName]) {
      this.listeners[eventName].forEach(callback => {
        callback(...[].slice.call(arguments, 1))
      })
    }
  }
};

测试代码中复用Event是使用Object.assign方法,这个方法只能浅复制,新的对象中不光会有on,emit还会有用来存放回调函数的listeners属性,并且listeners属性被公用了,会导致 person1.emit('call2'); person2.emit('call1'); 这两个本来不该有输出的调用输出内容

ForeveHG commented 4 years ago

分析一下,listeners是不应该被拷贝的,每个复用Event的对象自身都应该持有一个listeners,而Object.assign是只拷贝对象自身的可枚举属性,根据这几点,可以写出下面这一版:

var Event = {
  // 通过on接口监听事件eventName
  // 如果事件eventName被触发,则执行callback回调函数
  on: function (eventName, callback) {
    if (!this._listeners) Object.defineProperty(this, '_listeners', {
      writable: true,
      value: {}
    })

    if (!this._listeners[eventName]) this._listeners[eventName] = [];
    this._listeners[eventName].push(callback)
  },
  // 触发事件 eventName
  emit: function (eventName) {
    if (this._listeners[eventName]) {
      this._listeners[eventName].forEach(callback => {
        callback(...[].slice.call(arguments, 1))
      })
    }
  }
};