zoukankan      html  css  js  c++  java
  • vue响应式原理

    vue的响应式是如何实现的?

    Watcher ----- Dep ---- walk + defineProperty

    1  vue 初始化 -- 进行数据的set、get绑定,并创建了一个Dep对象

    // src > core > observer > index.js
    // 执行 new Vue 时会依次执行以下方法 // 1. Vue.prototype._init(option) // 2. initState(vm) // 3. observe(vm._data) // 4. new Observer(data) // 5. 调用 walk 方法,遍历 data 中的每一个属性,监听数据的变化。
    //
    遍历所有属性并将它们转换为getter/setter。此方法仅在值类型为Object时调用。

    function walk(obj: Object) {
     const keys = Object.keys(obj);
    for (let i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i]);
      }
    }
    
    // 6. 执行 defineProperty 监听数据读取和设置。
    // 定义对象上的响应式属性
    function defineReactive(obj, key, val) { // 为每个属性创建 Dep(依赖搜集的容器) const dep = new Dep(); // 绑定 get、set Object.defineProperty(obj, key, { get() { const value = val; // 如果有 target 标识,则进行依赖搜集 if (Dep.target) { dep.depend(); } return value; }, set(newVal) { val = newVal; // 修改数据时,通知页面重新渲染 dep.notify(); }, });

     Dep对象是什么?

    1.2  Dep对象 -- 用于依赖收集,它实现了一个发布订阅模式,完成了数据Data和渲染视图 Watcher 的订阅

    // src > core > observer > dep.js
    class Dep {
    // Dep.target 是一个 Watcher 类型。 static target: ?Watcher; // subs 存放搜集到的 Watcher 对象集合 subs: Array<Watcher>; constructor() { this.subs = []; } addSub(sub: Watcher) { // 搜集所有使用到这个 data 的 Watcher 对象。 this.subs.push(sub); } depend() { if (Dep.target) { // 搜集依赖,最终会调用上面的 addSub 方法 Dep.target.addDep(this); } } notify() { const subs = this.subs.slice(); for (let i = 0, l = subs.length; i < l; i++) { // 调用对应的 Watcher,更新视图 subs[i].update(); } } }

      Watch的功能是什么?

     1.3 Watch 观察者 -- 实现了渲染方法 _render 和 Dep 的关联, 初始化 Watcher 的时候,打上 Dep.target 标识,然后调用 get 方法进行页面渲染。

    // src > core > observer > watcher.js
    class Watcher { constructor(vm: Component, expOrFn: string
    | Function) { // 将 vm._render 方法赋值给 getter。 // 这里的 expOrFn 其实就是 vm._render。 this.getter = expOrFn; this.value = this.get(); } get() { // 给 Dep.target 赋值为当前 Watcher 对象 Dep.target = this; // this.getter 其实就是 vm._render // vm._render 用来生成虚拟 dom、执行 dom-diff、更新真实 dom。 const value = this.getter.call(this.vm, this.vm); return value; } addDep(dep: Dep) { // 将当前的 Watcher 添加到 Dep 收集池中 dep.addSub(this); } update() { // 开启异步队列,批量更新 Watcher queueWatcher(this); } run() { // 和初始化一样,会调用 get 方法,更新视图 const value = this.get(); } }

     小结:Vue 通过 defineProperty 完成了 Data 中所有数据的代理,当数据触发 get 查询时,会将当前的 Watcher 对象加入到依赖收集池 Dep 中,当数据 Data 变化时,会触发 set 通知所有使用到这个 Data 的 Watcher 对象去 update 视图。

    Watcher 是什么时候创建的?

     2 模板渲染

    2.1 new Vue 执行流程

    // src > core > instance > lifecycle.js
    // 1. Vue.prototype._init(option)
    // 2. vm.$mount(vm.$options.el) // 3. render = compileToFunctions(template) ,编译 Vue 中的 template 模板,生成 render 方法。 // 4. Vue.prototype.$mount 调用上面的 render 方法挂载 dom。 // 5. mountComponent // 6. 创建 Watcher 实例 const updateComponent = () => { vm._update(vm._render()); }; // 结合上文,我们就能得出,updateComponent 就是传入 Watcher 内部的 getter 方法。 new Watcher(vm, updateComponent); // 7. new Watcher 会执行 Watcher.get 方法 // 8. Watcher.get 会执行 this.getter.call(vm, vm) ,也就是执行 updateComponent 方法 // 9. updateComponent 会执行 vm._update(vm._render()) // 10. 调用 vm._render 生成虚拟 dom Vue.prototype._render = function (): VNode { const vm: Component = this; const { render } = vm.$options; let vnode = render.call(vm._renderProxy, vm.$createElement); return vnode; }; // 11. 调用 vm._update(vnode) 渲染虚拟 dom Vue.prototype._update = function (vnode: VNode) { const vm: Component = this; if (!prevVnode) { // 初次渲染 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false); } else { // 更新 vm.$el = vm.__patch__(prevVnode, vnode); } }; // 12. vm.__patch__ 方法就是做的 dom diff 比较,然后更新 dom,这里就不展开了。

     Watcher 是在 Vue 初始化的阶段创建的,属于生命周期中 beforeMount 的位置创建的,创建 Watcher 时会执行 render 方法,最终将 Vue 代码渲染成真实的 DOM。

    Vue初始化到渲染dom的过程:

     当数据发生改变时,vue是怎样进行更新的?

    由上图可见,在 Data 变化时,会调用 Dep.notify 方法,随即调用 Watcher 内部的 update 方法,此方法会将所有使用到这个 Data 的 Watcher 加入一个队列,并开启一个异步队列进行更新,最终执行 _render 方法完成页面更新。

    整体流程如下:

     总结:Vue的响应式原理

    1. 从 new Vue 开始,首先通过 get、set 监听 Data 中的数据变化,同时创建 Dep 用来搜集使用该 Data 的 Watcher。
    2. 编译模板,创建 Watcher,并将 Dep.target 标识为当前 Watcher。
    3. 编译模板时,如果使用到了 Data 中的数据,就会触发 Data 的 get 方法,然后调用 Dep.addSub 将 Watcher 搜集起来。
    4. 数据更新时,会触发 Data 的 set 方法,然后调用 Dep.notify 通知所有使用到该 Data 的 Watcher 去更新 DOM。

  • 相关阅读:
    macbook 无声音解决方案
    webapck dev server代理请求 json截断问题
    百度卫星地图开启
    服务器 nginx配置 防止其他域名绑定自己的服务器
    记一次nginx php配置的心路历程
    遇到npm报错read ECONNRESET怎么办
    运行svn tortoiseSvn cleanup 命令失败的解决办法
    svn add 命令 递归目录下所有文件
    m4出现Please port gnulib freadahead.c to your platform! Look at the definition of fflush, fread, ungetc on your system, then report this to bug-gnulib."
    Ubuntu下安装GCC,mpc、mpfr、gmp
  • 原文地址:https://www.cnblogs.com/pleaseAnswer/p/13564354.html
Copyright © 2011-2022 走看看