avatar
fireworks99
keep hungry keep foolish

Mini Data Observer

响应性的实现

Angular:脏值检测

Vue:依赖跟踪

1. A => B

B = A * 10,B依赖A,B的响应性的实现方法,是在A的set方法中传入一个改变B的方法。(这个方法解释了B对A的依赖)

let a = 1;
let b = a * 10;
function setA(v) {
  a = v;
  updateB();
}
function updateB() {
  b = a * 10;
}

setA(2);
console.log(b);//20

2.监听A的变更

ES5的Object.defineProperty提供监听属性变更的功能(指set方法)

const state = {
  a: 0
};
let b;
function updateB() {
  b = state.a * 10;
}

let internalValue = state.a;
Object.defineProperty(state, "a", {
  get() {
    return internalValue;
  },
  set(v) {
    internalValue = v;
    updateB();
  }
})

state.a = 2;
console.log(b);//20

3.收集依赖

前面都是假定依赖A的变量只有B,且这是已知的。

而实际业务中,依赖A的变量随着代码的编写会逐渐增多,这就要求系统能够动态维护所有依赖A的变量对于A的依赖关系,以便在A发生变更时按照每个依赖关系修改每个依赖项。

需要实现一个依赖跟踪类Dep,类里有一个叫depend方法,该方法用于收集依赖项;另外还有一个notify方法,该方法用于触发依赖项的执行,也就是说只要在之前使用depend方法收集的依赖项,当调用notfiy方法时会被触发执行。

window.Dep = class Dep {
  constructor () {
    // 存放依赖关系
    this.subscribers = new Set()
  }
    // 新增依赖关系
  depend () {
    if (activeUpdate) {
      this.subscribers.add(activeUpdate)
    }
  }
    // 所有依赖项,按照依赖关系执行更新操作
  notify () {
    this.subscribers.forEach(sub => sub())
  }
}

let activeUpdate = null

//新增依赖关系(后面详细解释)
function autorun (update) {
  const wrappedUpdate = () => {
    activeUpdate = wrappedUpdate
    update()
    activeUpdate = null
  }
  wrappedUpdate()
}

4.合并2、3步

①.实现

class Dep {
  constructor () {
    this.subscribers = new Set()
  }

  depend () {
    if (activeUpdate) {
      this.subscribers.add(activeUpdate)
    }
  }

  notify () {
    this.subscribers.forEach(sub => sub())
  }
}

function observe (obj) {
  Object.keys(obj).forEach(key => {
    let internalValue = obj[key]

    const dep = new Dep()
    Object.defineProperty(obj, key, {
      get () {
        dep.depend()
        return internalValue
      },

      set (newVal) {
        const changed = internalValue !== newVal
        internalValue = newVal
        if (changed) {
          dep.notify()
        }
      }
    })
  })
  return obj
}

let activeUpdate = null

function autorun (update) {
  const wrappedUpdate = () => {
    activeUpdate = wrappedUpdate
    update()
    activeUpdate = null
  }
  wrappedUpdate()
}

②.使用

//情景模拟
{
  const state = {
    count: 1
  };

  observe(state);

  let foo = 1;
  let fire = 1;


  autorun(() => {
    foo = state.count * 10;
    console.log("state.count is " + state.count);
    console.log("foo is " + foo);
    console.log("fire is " + fire);
  });
  /**
   * ①控制台打印:
   * state.count is 1
   * foo is 10
   * fire is 1
   */

  state.count++;
  /**
   * ②控制台打印:
   * state.count is 2
   * foo is 20
   * fire is 1
   */


  autorun(() => {
    fire = state.count + 100;
    console.log("state.count is " + state.count);
    console.log("foo is " + foo);
    console.log("fire is " + fire);
  });
  /**
   * ③控制台打印:
   * state.count is 2
   * foo is 20
   * fire is 102
   */

  state.count++;
  /**
   * ③控制台打印:
   * state.count is 3
   * foo is 30
   * fire is 102
   * state.count is 3
   * foo is 30
   * fire is 103
   */
}

autorun这个方法的参数是一个function,这个function里面执行了updata方法,而update方法是按照依赖关系更新依赖项,这一操作势必会访问A,既然访问A,势必调用getter,既然调用getter,势必调用dep.depend来新增依赖关系。所以autorun这个方法是用来新增依赖关系的。

③.补充

上面说到”update方法是按照依赖关系更新依赖项,这一操作势必会访问A,既然访问A,势必调用getter”,接下来验证一下:

const state = {
  count: 0
}
let value = state.count;
Object.defineProperty(state, "count", {
  get() {
    console.log(value);
    return value;
  }
})

let foo = 0;
// 执行下面这一句
// 会执行get方法打印state.count的值
// 即打印 0
foo = state.count + 1;
Site by Baole Zhao | Powered by Hexo | theme PreciousJoy