[Dictionary] Vue responsive front-end principle is easy to understand

How to understand Responsive

It can be understood: When a state change associated with this state of affairs will also immediately change, that is, after the data related to DOM status change also changed from the front view. Data model is just plain JavaScript objects. And when you modify them, the view will be updated.

Throw a question

We take a look at our common Vue in writing:

<div id="app" @click="changeNum">
  {{ num }}
</div>

var app = new Vue({
  el: '#app',
  data: {
    num: 1
  },
  methods: {
    changeNum() {
      this.num = 2
    }
  }
})
复制代码

The wording is very common, but when you considered why the implementation of this.num = 2rear view Why would update it? Through this article I strive to make this point clear.

If you do not use Vue, how should we achieve?

My first idea is to achieve something like this:

let data = {
  num: 1
};
Object.defineProperty(data, 'num',{
  value: value,
  set: function( newVal ){
    document.getElementById('app').value = newVal;
  }
});
input.addEventListener('input', function(){
  data.num = 2;
});
复制代码

This rough elements to achieve click, automatically update the view.

Here we need to operate the accessor properties of the object by Object.defineProperty. To monitor data changes when the operations related to DOM.

And here we use a common model - publish / subscribe model.

I drew a rough flow chart to illustrate the observer mode and publish / subscribe model. as follows:

 

 

 

Careful students will find that my rough process and different places use Vue is the need to re-render myself DOM operations.

If we use Vue, then this step is the internal code to handle the Vue. This is also the reason why we do not need to manually operate the DOM when using Vue's.

About Object.definePropertyI already mentioned in the previous article, I will not repeat them here.

Vue is how to achieve a responsive

We know that the object can Object.definePropertyoperate their access properties that objects have getterand settermethods. This is the cornerstone to achieve responsive.

Look at a very intuitive flow chart:

initData way

In Vue initialization time, its _init()method calls the execution initState(vm)method. initStateThe method is mainly on props, methods, data, computedand wathcerother attributes do initialization.

这里我们就对 data 初始化的过程做一个比较详细的分析。

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    ......
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    ...... // 省略部分兼容代码,但不影响理解
    if (props && hasOwn(props, key)) {
      ......
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}
复制代码

initData初始化 data 的主要过程也是做两件事:

  1. 通过 proxy 把每一个值 vm._data.[key] 都代理到 vm.[key] 上;
  2. 调用 observe 方法观测整个 data 的变化,把 data 也变成响应式(可观察),可以通过 vm._data.[key] 访问到定义 data 返回函数中对应的属性。

数据劫持 — Observe

通过这个方法将 data 下面的所有属性变成响应式(可观察)。

// 给对象的属性添加 getter 和 setter,用于依赖收集和发布更新
export class Observer {
  value: any;
  dep: Dep;  
  vmCount: number; 
  constructor (value: any) {
    this.value = value
    // 实例化 Dep 对象
    this.dep = new Dep()
    this.vmCount = 0
    // 把自身实例添加到数据对象 value 的 __ob__ 属性上
    def(value, '__ob__', this)
    // value 是否为数组的不同调用
    if (Array.isArray(value)) {
      const augment = hasProto ? protoAugment : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  // 取出所有属性遍历
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
复制代码

def 函数内封装了 Object.defineProperty ,所以你 console.log(data) ,会发现多了一个 __ob__ 的属性。

defineReactive 方法遍历所有属性

// 定义一个响应式对象的具体实现
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
  ..... // 省略部分兼容代码,但不影响理解
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // 进行依赖收集
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      ..... // 省略部分兼容代码,但不影响理解
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 对新的值进行监听
      childOb = !shallow && observe(newVal)
      // 通知所有订阅者,内部调用 watcher 的 update 方法 
      dep.notify()
    }
  })
}
复制代码

defineReactive 方法最开始初始化 Dep 对象的实例,然后通过对子对象递归调用observe 方法,使所有子属性也能变成响应式的对象。并且在 Object.definePropertygettersetter 方法中调用 dep 的相关方法。

即:

  1. getter 方法完成的工作就是依赖收集 —— dep.depend()
  2. setter 方法完成的工作就是发布更新 —— dep.notify()

我们发现这里都和 Dep 对象有着不可忽略的关系。接下来我们就看看 Dep 对象。这个 Dep

调度中心作用的 Dep

前文中我们提到发布/订阅模式,在发布者和订阅者之前有一个调度中心。这里的 Dep 扮演的角色就是调度中心,主要的作用就是:

  1. 收集订阅者 Watcher 并添加到观察者列表 subs
  2. 接收发布者的事件
  3. 通知订阅者目标更新,让订阅者执行自己的 update 方法

详细代码如下:

// Dep 构造函数
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }
  // 向 dep 的观察者列表 subs 添加 Watcher
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }
  // 从 dep 的观察者列表 subs 移除 Watcher
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }
  // 进行依赖收集
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  // 通知所有订阅者,内部调用 watcher 的 update 方法
  notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
// Dep.target 是全局唯一的观察者,因为在任何时候只有一个观察者被处理。
Dep.target = null
// 待处理的观察者队列
const targetStack = []

export function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

export function popTarget () {
  Dep.target = targetStack.pop()
}
复制代码

Dep 可以理解成是对 Watcher 的一种管理,Dep 和 Watcher 是紧密相关的。所以我们必须看一看 Watcher 的实现。

订阅者 —— Watcher

Watcher 中定义了许多原型方法,这里我只粗略的讲 updateget 这三个方法。

  // 为了方便理解,部分兼容代码已被我省去
  get () {
    // 设置需要处理的观察者
    pushTarget(this)
    const vm = this.vm
    let value = this.getter.call(vm, vm)
    // deep 是否为 true 的处理逻辑
    if (this.deep) {
      traverse(value)
    }
    // 将 Dep.target 指向栈顶的观察者,并将他从待处理的观察者队列中移除
    popTarget()
    // 执行依赖清空动作
    this.cleanupDeps()
    return value
  }

  update () {
    if (this.computed) {
      ...
    } else if (this.sync) { 
      // 标记为同步
      this.run()
    } else {      
      // 一般都是走这里,即异步批量更新:nextTick
      queueWatcher(this)
    }
  }
复制代码

Vue 的响应式过程大概就是这样了。感兴趣的可以看看源码。

最后我们在通过这个流程图来复习一遍:


如果有想学习web前端的程序员,可加下v❤:TZ07900,免费送web前端的视频教程噢!

Guess you like

Origin blog.csdn.net/weixin_44970764/article/details/91044593