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

Vue 响应式原理核心 #63

Open
amandakelake opened this issue Jan 8, 2019 · 2 comments
Open

Vue 响应式原理核心 #63

amandakelake opened this issue Jan 8, 2019 · 2 comments

Comments

@amandakelake
Copy link
Owner

amandakelake commented Jan 8, 2019

高级抽象

onStateChanged(() => {
	view = render(state)
})

将整个响应式系统抽象为一个onStateChanged方法,view自动根据state的变化而变化

那么如何实现onStateChanged呢?

先看下手动触发更新是怎么写的

let update;

const onStateChanged = _update => {
  update = _update;
}

const setState = newState => {
  state = newState;
  update();
}

每次更新时传入newState{a: 1}, 手动触发setState

onStateChanged(() => {
	view = render(state)
})

setState({ a: 1})

用过react的同学会发现,这就是整个react的工作核心。

那么,如果我不想手动setState,只想改变状态state.a = 2,然后view自动更新,如何做到autorun呢?

我们都知道NG是采用脏检测的方式(这里不展开叙述)

而vue是通过ES5的object.defineProperty()方法把数据对象变成响应式的可观察对象(observable),把所有属性转为getterssetters,这些 getter/setter对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化,这段话暂时不理解没关系,我们后面再回来理解。

autorun(() => {
  console.log(state.count)
})

这里是整个vue追踪变化的核心

下面我们尝试来自己实现一个最简单的响应式系统,主要分三块

  • 将数据对象转成getter/setter
  • 实现依赖追踪dependency-tracking
  • 实现mini-observer

将数据对象转成getter/setter

  • takes an Object as the argument
  • converts the Object's properties in-place into getter/setters using
    Object.defineProperty
  • The converted object should retain original behavior, but at the same time
    log all the get/set operations.
function convert (obj) {
	// 取出obj的每个key进行循环将整个obj所有属性都变成getter/setter
  Object.keys(obj).forEach(key => {
	  // 这里其实是个闭包,需要取得初始值,不然初始化可能会拿到underfined
    let internalValue = obj[key];
    Object.defineProperty(obj, key, {
      get() {
        console.log(`getting key "${key}": ${internalValue}`);
        return internalValue;
      },
      set(newValue) {
        console.log(`setting key "${key}" to: ${newValue}`);
        internalValue = newValue;
      }
    })
  })
}

实现效果如下

const obj = { foo: 123 }
convert(obj)

obj.foo // 'getting key "foo": 123'
obj.foo = 234 // 'setting key "foo" to 234'
obj.foo // 'getting key "foo": 234'

实现依赖追踪dependency-tracking

  • Create a Dep class with two methods: depend and notify.
  • Create an autorun function that takes an updater function.
  • Inside the updater function, you can explicitly depend on an instance of Dep by calling dep.depend()
  • Later, you can trigger the updater function to run again by calling dep.notify().

先看我们要实现的效果

const dep = new Dep()

autorun(() => {
  dep.depend()
  console.log('updated')
})
// should log: "updated"

dep.notify()
// should log: "updated"

Dep是一个class, 它有两个方法: dependnotify,顾名思义,一个是收集依赖,一个通知更新

无论何时调用dep.notify(), 传给autorun的方法应该再次 自动执行

// 订阅者Dep 主要作用是存放watcher观察者对象
// 这里为了简化概念,没有引入watcher,直接用变量替代
// 下面重点讲到的activeUpdate 类似于Dep.target = this;
class Dep {
  constructor() {
    // Set类数组,成员唯一不重复
    this.subscribes = new Set();
  }

  // 依赖收集,其实就是收集watcher
  depend() {
    if (activeUpdate) {
      this.subscribes.add(activeUpdate)
    }
  }

  // 通知所有watcher对象更新视图
  notify() {
    this.subscribes.forEach(sub => sub());
  }
}

接下来看下autorun的实现

let activeUpdate = null

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

这里需要好好理解一下
activeUpdate是一个autorun之外的变量
每当autorun执行,activeUpdate = wrappedUpdate代表的是autorun内正在执行的整个模块,如下图
315e9212-b588-45f6-8080-c024a8058490

在依赖收集

depend() {
	// 将整个`autorun`内正在执行的整个模块,也就是watcher存放到订阅列表中
	if (activeUpdate) {
	  this.subscribes.add(activeUpdate)
	}
}

mini-observer

结合上面两个模块

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

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

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

    function observe (obj) {
      // 遍历对象所有属性,并全部转为getter/setters
      Object.keys(obj).forEach(key => {
        let internalValue = obj[key]

        // 每个属性都有一个订阅实例
        const dep = new Dep()

        Object.defineProperty(obj, key, {
          // getter 负责依赖收集
          get () {
            dep.depend()
            return internalValue
          },

          // setter 负责通知更新
          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: 0
}

observe(state)

autorun(() => {
  console.log(state.count)
})
// "count is: 0"

state.count++
// "count is: 1"
@refanbanzhang
Copy link

@rotcx
Copy link

rotcx commented Dec 14, 2019

nice

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

No branches or pull requests

3 participants