数据劫持-发布订阅模式

本文深入解析了JavaScript中的数据劫持原理,通过示例代码详细解释了如何使用Object.defineProperty实现响应式数据。同时,介绍了发布订阅模式的工作流程,并给出了一个简单的类实现,用于订阅、发布和取消订阅事件。最后,通过实际应用展示了数据劫持在响应式系统中的作用以及发布订阅模式在解耦代码中的价值。
摘要由CSDN通过智能技术生成

数据劫持

function observe(data){
  if(typeof data !== 'object') return
  new Observer(data)
}

//Observer 是用于给属性对象加上监听属性的 
class Observer{
  constructor(value){
    this.value = value
    this.walk()
  }
  walk(){
    Object.keys(this.value).forEach((key) => defineReactive(this.value,key))
  }
}


//我们需要一个全局变量来保存这个值
function defineReactive(data,key,value = data[key]){
  //如果value是对象,递归调用observe来监听对象
  //如果value不是对象,observe函数会直接返回
  observe(value)
  Object.defineProperty(data,key,{
    get(){
      console.log(`你视图访问一个getter属性 -> ${value} `);
      return value
    },
    set(newValue){
      if(newValue === value) return 
      console.log(`你视图访问一个setter属性 -> ${newValue} `);
      value = newValue
      observe(newValue) //设置的值也要被监听
    }
  })
}

const obj = {
  a: 1,
  b: {
    c: 2
  }
}

observe(obj);

代码执行顺序

执行observe(obj) 入口函数
├── new Observer(obj),并执行this.walk()遍历obj的属性,执行defineReactive()
    ├── defineReactive(obj, a)
        ├── 执行observe(obj.a) 发现obj.a不是对象,直接返回
        ├── 执行defineReactive(obj, a) 的剩余代码
    ├── defineReactive(obj, b) 
	    ├── 执行observe(obj.b) 发现obj.b是对象
	        ├── 执行 new Observer(obj.b),遍历obj.b的属性,执行defineReactive()
                    ├── 执行defineReactive(obj.b, c)
                        ├── 执行observe(obj.b.c) 发现obj.b.c不是对象,直接返回
                        ├── 执行defineReactive(obj.b, c)的剩余代码
            ├── 执行defineReactive(obj, b)的剩余代码
代码执行结束

js发布订阅模式

发布-订阅模式 有3个模块,发布者,订阅者,调度中心

例子:这里处理中心相当于报刊办事大厅。发布者相当与某个杂志负责人,他来中心这注册一个的杂志,而订阅者相当于用户,我在中心订阅了这分杂志。每当发布者发布了一期杂志,办事大厅就会通知订阅者来拿新杂志

使用类封装,不用考虑this指向,便于理解

// 发布-订阅类 小案例
export default class Event{
  constructor(){
  }
  // 定义一个事件容器,用来装事件数组(因为订阅者可以是多个)
  handlers = {}
  // 参数是 事件名 和 事件方法
  addEventListener(type,handler){
    // 首先判断handlers内有没有type事件容器,没有则创建一个新数组容器
    if(!(type in this.handlers)){
      this.handlers[type] = []
    }
    // 将事件存入
    this.handlers[type].push(handler)
  }

  // 触发事件两个参数 (事件名, 参数)
  dispatchEvent(type,...params){
    console.log("params参数",params);
    // 若没有注册该事件则抛出错误
    if(!(type in this.handlers)){
      throw new Error('未注册该事件')
    }
    // 便利触发
    this.handlers[type].forEach(handler => {
      handler(...params)
    })
  }

  //事件移除参数(事件名,删除的事件,若无第二个参数则删除该事件的订阅和发布)
  removeEventlistener(type,handler){
    // 无效事件抛出
    if(!(type in this.handlers)){
      throw new Error("无效事件")
    }
    if(!handler){
      // 直接移除事件
      delete this.handlers[type]
    }else{
      const idx = this.handlers[type].findIndex(ele => ele  == handler)
      // 抛出异常事件
      if(idx === undefined){
        throw new Error('无该绑定事件');
      }

      // 移除事件
      this.handlers[type].splice(idx,1)
      this.handlers[type].length || delete this.handlers[type]
    }
  }
}
  • 知识弱点

    • //收
      let [a,...rest] = [1,2,3,4,5]
      console.log(a,rest); //1 [ 2, 3, 4, 5 ]
      
    • 所以在函数中也是一样的,形参使用(…rest) 语法,打印rest 是 收成一个数组,如果想要扩展还是要…rest

不使用类实现

  • 创建一个对象
  • 在该对象上创建一个缓存列表(调度中心)
  • on 方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心),这里的缓存列表是一个数组,因为会有多个订阅者
  • emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
  • off 方法可以根据 event 值取消订阅(取消订阅)
  • once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)
// 公众号对象
let eventEmitter = {};

// 缓存列表,存放 event 及 fn
eventEmitter.list = {};

//订阅
eventEmitter.on = function (event,fn){
  let _this = this;
  //如果没有对应的event值,既是说没有被订阅过, 就给event创建缓存列表
  // 如有对象中相应的event值,把fn添加到对应event 的缓存列表里
  (_this.list[event] || (_this.list[event] = [])).push(fn);
  return _this;
}

//发布
eventEmitter.emit = function (){
  // _this 保存的是eventEmitter 里边的emit对象 所以下边调用 里边的方法需要 把_this 传入进去
  let _this = this;
  //第一个参数 是对应的 event 值,直接用数组的shift方法去除  (arguments里边保存了 调用函数的 参数)
  let event = [].shift.call(arguments),fns = [..._this.list[event]];

  //如果缓存列表里没有 fn 就返回 fasle
  if(!fns || fns.length === 0){
    return false;
  }

  // 遍历 event 值对应的缓存列表,依次执行fn
  fns.forEach(fn => {
    fn.apply(_this,arguments);
  });

  return _this;
}
  • 知识盲点
    • arguments里边保存了 调用函数的 参数,所以[].shift.call(arguments),取到的是arguments 里第一个call(arguments)意思是把this指向arguments
    • 为什么fn.apply(_this,arguments)要传入_this?
      • 因为外层是一个箭头函数,this的指向不明确,所以外层要保存 一层_this,
      • _this里边呢,保存的是eventEmitter这个对象,然后进行调用
    • 为什么需要把_this返回出去?
      • 不知道

添加off 和 once 方法

//取消订阅
eventEmitter.off = function(event,fn){
  let _this = this;
  let fns = _this.list[event]; //存放对应 标识 的 订阅列表
  // 如果缓存列表中没有相应的 fn ,返回false
  if(!fns) return false;
  if(!fn){
    //如果没有传fn的话,就会将event值对应缓存列表中的fn都清空
    fns && (fns.length = 0)
  }else{
    // 若有fn,遍历对标,看看传入的fn与哪个函数相同,如果相同 直接在缓存列表中删除即可
    let cb = fns.findIndex(item => item === fn);
    fns.splice(cb,1);
  }

  return _this
}
  • 知识弱点
    • 两个相同的函数比较 布尔值 为 true ,因为引用地址是一样的
// 监听一次
eventEmitter.once = function(event,fn){
  // 先绑定,调用后删除
  let _this = this;
  function on(){
    _this.off(event,on);
    fn.apply(_this,arguments)
  }
  on.fn = fn;
  _this.on(event,on); //这里调用的 是外边的 on方法
  return _this;
}

  • 知识盲区

    • 这个once方法 不是说 订阅 就触发,而是 绑定到调度中心,使用一次后就执行off 方法进行一个取消订阅

    • 为什么 要 on.fn = fn 这样写? 不这样写 好像也能正常进行调用且 ,调度中心里边也会删除

      • 不知道

调用


function user1 (content) {
  console.log('用户1订阅了:', content);
};

function user2 (content) {
  console.log('用户2订阅了:', content);
};

function user3(content){
  console.log('用户3订阅了:', content);
}

// 订阅
eventEmitter.on('article', user1);
eventEmitter.on('article', user2);

//监听一次
eventEmitter.once("article2",user3)

// 取消订阅
eventEmitter.off("article",user1)

// 发布
eventEmitter.emit('article', 'Javascript 发布-订阅模式');
eventEmitter.emit('article2', 'Javascript 发布-观察模式');
eventEmitter.emit('article2', 'Javascript 发布-观察模式');

个人总结

  • 数据劫持的作用呢?就是给所有的对象属性添加上getter 和 setter属性,这样所有的对象就是响应式的了
    • 怎么添加呢?就是通过一个Observe类,里边自定义一个方法walk(),通过Object.keys()遍历 顶级对象key,调用defineReactive()方法添加,如果遍历key对应值 为对象,再递归调用
  • 了解发布订阅的作用?便于理解vue中的 e m i t , emit, emit,on, o n c e , once, once,off,总的来说发布订阅就是 把需要在全局使用的函数,订阅到 一个全局对象(调用中心)里边去,当我们需要在那个 阶段使用 这个函数的时候,只需要发布就能执行。该模式有利也有弊,好处就是对象之间的解耦,缺点就是创建订阅者 要消耗一定的时间和内存(暂时没有体验到)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值