图解Vue 响应式流程及原理

目录
  • 阅读本文能够帮助你什么?
  • 一、组件化流程
    • 1. 整个new Vue阶段做了什么?
    • 2. 普通dom元素如何渲染到页面?
    • 3. 组件如何渲染到页面?
    • 4. Vue组件化简化流程
  • 二、响应式流程
    • 1. 依赖收集
    • 2. 派发更新
  • 三、彩蛋篇
    • 1. computed依赖收集
    • 2. computed派发更新
    • 3. user Watcher依赖收集

阅读本文能够帮助你什么?

  • 在学习vue源码的时候发现组件化过程很绕?
  • 在响应式过程中ObserverDepWatcher三大对象傻傻分不清?
  • 搞不清楚对象数组依赖收集、派发更新的流程?depwatcher互调造成混乱?
  • 学了一遍好像懂了又好像不全懂的感觉?而且缺乏大体流程概念?
  • 或者像我一样,有段时间没看vue源码好像有点遗忘?但是想快速回顾却无从下手?

本文主要分为1. 组件化2. 响应式原理3. 彩蛋(computed和watch)进行讲解。本文调试源码的vue版本是v2.6.14。整篇将采用源码讲解 + 流程图的方式详细还原整个Vue响应式原理的全过程。你可以了解到Dep.targetpushTargetpopTarget;响应式中的三大WatcherDepWathcer多对多的,互相收集的关系。

这篇是进阶的 Vue 响应式源码解析,文章比较长,内容比较深,大家可以先mark后看。看不懂的不要强行看,可以先看看其他作者的偏简单一点的源码解析文章,然后好好消化。等过段时间再回来看这篇,相信你由浅入深后再看本文,一定会有意想不到的收获~

一、组件化流程

在讲解整个响应式原理之前,先介绍一下Vue中另一个比较核心的概念——组件化,个人认为这也是学习响应式的前置核心。搞懂组件化,响应式学习如虎添翼!

1. 整个new Vue阶段做了什么?

  • 执行init操作。包括且不限制initLifecycleinitState
  • 执行mount。进行元素挂载
  • compiler步骤在runtime-only版本中没有。
    • compiler步骤对template属性进行编译,生成render函数。
    • 一般在项目中是在.vue文件开发,通过vue-loader处理生成render函数。

执行render。生成vnode

<div id="app">{{ message }}</div>
render (h) {
  return h('div', {
     attrs: {
        id: 'app'
      },
  }, this.message)
}
  • render例子,如下
  • 对应手写的render函数
  • patch。新旧vnode经过diff后,渲染到真实dom上

2. 普通dom元素如何渲染到页面?

  • 执行$mount

    • 实际执行mountComponent
    • 这里会实例化一个Watcher
    • Watcher中会执行get方法,触发updateComponent
  • 执行updateComponent。执行vm._update(vm._render(), hydrating)
  • 执行vm.render()
    • render其实调用createElment(h函数)
    • 根据tag的不同,生成组件、原生VNode并返回
  • 执行vm.update()createElm() 到 createChildren() 递归调用
  • 将VNode转化为真实的dom,并且最终渲染到页面

3. 组件如何渲染到页面?

这里以如下代码案例讲解更加清晰~没错,就是这么熟悉!就是一个初始化的Vue项目

// mian.js
import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: h => h(App),
}).$mount('#app')
// App.vue
<template>
    <div id="app">
      <p>{{ msg }}</p>
    </div>
</template>
<script>
    export default {
        name: 'App',
        data () {
            return {
                msg: 'hello world'
            }
        }
    }
</script>

主要讲解组件普通元素的不同之处,主要有2点:

如何生成VNode——创建组件VNodecreateComponent

如何patch——组件new Vue到patch流程createComponent

$vnode:占位符vnode。最终渲染vnode挂载的地方。所有的组件通过递归调用createComponent直至不再存在组件VNode,最终都会转化成普通的dom。

{
    tag: 'vue-component-1-App',
    componentInstance: {组件实例},
    componentOptions: {Ctor, ..., }
}

_vnode:渲染vnode。

{
    tag: 'div',
    {
        "attrs": {
            "id": "app"
        }
    },
    // 对应占位符vnode: $vnode
    parent: {
        tag: 'vue-component-1-App',
        componentInstance: {组件实例},
        componentOptions: {Ctor, ..., }
    },
    children: [
        // 对应p标签
        {
            tag: 'p',
            // 对应p标签内的文本节点{{ msg }}
            children: [{ text: 'hello world' }]
        }, {
          // 如果还有组件VNode其实也是一样的
          tag: 'vue-component-2-xxx'
        }
    ]
}
  • (注意:这一步对应上图render流程的紫色块的展开!!!)
  • 区分普通元素VNode
    • 普通VNode:tag是html的保留标签,如tag: 'div'
    • 组件VNode:tag是以vue-component开头,如tag: 'vue-component-1-App'
  • (注意:这一步对应上图patch流程的紫色块的展开!!!)

4. Vue组件化简化流程

相信你看完细粒度的Vue组件化过程可能已经晕头转向了,这里会用一个简化版的流程图进行回顾,加深理解

二、响应式流程

案例代码

// 案例
export default {
    name: 'App',
    data () {
        return {
            msg: 'hello world',
            arr = [1, 2, 3]
        }
    }
}

1. 依赖收集

这里会从Observer、Dep、Watcher三个对象进行讲解,分 objectarray 两种依赖收集方式。

  • 一定要注意!数组 的依赖收集 跟 对象的属性 是不一样的。对象属性经过深度遍历后,最终就是以一个基本类型的数据为单位收集依赖,但是数组仍然是一个引用类型
  • 如果这里不懂,先想一个问题: 我们用 this.msg = 'xxx' 能触发 setter 派发更新,但是我们修改数组并不是用 this.arr = xxx ,而是用 this.arr.push(xxx) 等修改数组的方法。很显然,这时候并不是通过触发 arr 的 setter 去派发更新的。那是怎么做的呢?先带着这个问题继续往下看吧!

三个核心对象:Observer(蓝)、Dep(绿)、Watcher(紫)

依赖收集准备阶段——Observer、Dep的实例化

// 以下是initData调用的方法讲解,排列遵循调用顺序
function observe (value, asRootData) {
  if (!isObject(value)) return // 非对象则不处理
  // 实例化Observer对象
  var ob;
  ob = new Observer(value);
  return ob
}

function Observer (value) {
  this.value = value; // 保存当前的data
  this.dep = new Dep(); // 实例化dep,数组进行依赖收集的dep(对应案例中的arr)
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
    if (hasProto) {
      // 这里会改写数组原型。__proto__指向重写数组方法的对象
      protoAugment(value, arrayMethods);
    } else {
      copyAugment(value, arrayMethods, arrayKeys);
    }
    this.observeArray(value);
  } else {
    this.walk(value);
  }
}
// 遍历数组元素,执行对每一项调用observe,也就是说数组中有对象会转成响应式对象
Observer.prototype.observeArray = function observeArray (items) {
  for (var i = 0, l = items.length; i < l; i++) {
    observe(items[i]);
  }
}
// 遍历对象的全部属性,调用defineReactive
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  // 如案例代码,这里的 keys = ['msg', 'arr']
  for (var i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i]);
  }
}
function defineReactive (obj, key, val) {
  // 产生一个闭包dep
  var dep = new Dep();
  // 如果val是object类型,递归调用observe,案例代码中的arr会走这个逻辑
  var childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    get: function reactiveGetter () {
      // 求value的值
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) { // Dep.target就是当前的Watcher
        // 这里是闭包dep
        dep.depend();
        if (childOb) {
          // 案例代码中arr会走到这个逻辑
          childOb.dep.depend(); // 这里是Observer里的dep,数组arr在此依赖收集
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 下文派发更新里进行讲解
    }
  });
}
  • 注意 对象 、 数组 的不同处理方式。这里以 核心代码 + 图 进行讲解
  • 接下来核心分析 defineReactive 做了什么。注意 childOb ,这是数组进行依赖收集的地方(也就是为什么我们 this.arr.push(4) 能找到 Watcher 进行派发更新)

依赖收集触发阶段——Wather实例化、访问数据、触发依赖收集

// new Wathcer核心
function Watcher (vm, expOrFn, cb, options, isRenderWatcher) {
  if (typeof expOrFn === 'function') {
  // 渲染watcher中,这里传入的expOrFn是updateComponent = vm.update(vm.render())
  // this.getter等价于vm.update(vm.render())
    this.getter = expOrFn;
  } else {
    ...
  }
  // 这里进行判断,lazy为true时(计算属性)则什么都不执行,否则执行get
  this.value = this.lazy
    ? undefined
    : this.get(); // 本次为渲染Watcher,执行get,继续往下看~
}

// Watcher的get方法
Watcher.prototype.get = function get () {
  // 这里很关键,pushTarget就是把当前的Wather赋值给“Dep.target”
  pushTarget(this);
  var value;
  var vm = this.vm;
  try {
    // 1. 这里调用getter,也就是执行vm.update(vm.render())
    // 2. 执行vm.render函数就会访问到响应式数据,触发get进行依赖收集
    // 3. 此时的Dep.target为当前的渲染Watcher,数据就可以理所应当的把Watcher加入自己的subs中
    // 4. 所以此时,Watcher就能监测到数据变化,实现响应式
    value = this.getter.call(vm, vm);
  } catch (e) {
    ...
  } finally {
    popTarget();
    /*
    * cleanupDeps是个优化操作,会移除Watcher对本次render没被使用的数据的观测
    * 效果:处于v-if为false中的响应式数据改变不会触发Watcher的update
    * 感兴趣的可以自己去debugger调试,这里就不展开了
    */
    this.cleanupDeps();
  }
  return value
}
  • Dep.target相关讲解

    • targetStack:栈结构,用来保存Watcher
    • pushTarget:往targetStackpush当前的Watcher(排在前一个Watcher的后面),并把Dep.target赋值给当前Watcher
    • popTargettargetStack最后一个元素弹出(.pop),Dep.target赋值给最后一个Watcher(也就是还原了前一个Watcher)
    • 通过上述实现,vue保证了全局唯一的Watcher,准确赋值在Dep.target

细节太多绕晕了?来个整体流程,从宏观角度再过一遍(computed部分可看完彩蛋后再回来重温一下)

2. 派发更新

派发更新区分对象属性、数组方法进行讲解

如果想要深入了解组件的异步更新,戳这里,了解Vue组件异步更新之nextTick。本文只针对派发更新流程,不会对异步更新DOM进行展开讲解~

这里可以先想一下,以下操作会发生什么?

  • this.msg = 'new val'
  • this.arr.push(4)

是的,毫无疑问都会先触发他们之中的get,那再触发什么呢?我们接下来看

对象属性修改触发set,派发更新。this.msg = 'new val'

...
Object.defineProperty (obj, key, {
    get () {...},
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      // 判断新值相比旧值是否已经改变
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      // 如果新值是引用类型,则将其转化为响应式
      childOb = !shallow && observe(newVal);
      // 这里通知dep的所有watcher进行更新
      dep.notify();
    }
}
...

数组调用方法。this.arr.push(4)

// 数组方法改写是在 Observer 方法中
function Observer () {
    if (hasProto) {
        // 用案例讲解,也就是this.arr.__proto__ = arrayMethods
        protoAugment(value, arrayMethods);
    }
}   

// 以下是数组方法重写的实现
var arrayProto = Array.prototype; // 保存真实数组的原型
var arrayMethods = Object.create(arrayProto); // 以真数组为原型创建对象
// 可以看成:arrayMethods.__proto__ = Array.prototype
var methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
];

// 一个装饰器模型,重写7个数组方法
methodsToPatch.forEach(function (method) {
  // 保存原生的数组方法
  var original = arrayProto[method];
  // 劫持arrayMethods对象中的数组方法
  def(arrayMethods, method, function mutator () {
    var args = [], len = arguments.length;
    while ( len-- ) args[ len ] = arguments[ len ];

    var result = original.apply(this, args);
    var ob = this.__ob__; // 当我门调用this.arr.push(),这里就能到数组对象的ob实例
    var inserted;
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break
      case 'splice':
        inserted = args.slice(2);
        break
    }
    if (inserted) { ob.observeArray(inserted); }
    // 由于数组对象在new Observer中实例化了一个dep,并通过childOb逻辑收集了依赖,这里就能在ob实例中拿到dep属性
    ob.dep.notify();
    return result
  });
})
  • 这里可以联合数组的依赖收集再看一遍,你就恍然大悟了。为什么 对象的属性 、数组 的依赖收集方式不一样

整个new Vue阶段、到依赖收集派发更新的全部流程就到这里结束了。可以纵观流程图看出,Vue应用就是一个个Vue组件组成的,虽然整个组件化、响应式流程很多,但核心的路径一旦走通,你就会恍然大悟。

三、彩蛋篇

1. computed依赖收集

  • 案例代码
<template>
    <div id="app">
        {{ name }}
    </div>
</template>
<script>
export default {
    name: 'App',
    computed: {
      name () {
        return this.firstName + this.secondName
      }
    },
    data () {
        return {
            firstName: 'jing',
            secondName: 'boran'
        }
    }
}
</script>
  • 我们先看流程图。图有点大~大家可以放大看看,每个核心步骤都附有文字说明

根据案例概括一下,加深理解

// 访问computed时触发get的核心代码
function createComputedGetter (key) {
  return function computedGetter () {
    var watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) { // dirty第一次为true
        watcher.evaluate(); // 这里是对computed进行求值,对computed watcher执行依赖收集
      }
      if (Dep.target) {
        watcher.depend(); // 这里是对渲染Watcher进行依赖收集
      }
      return watcher.value
    }
  }
}

computed中的name其实就是一个computed Watcher,这个Watcher在init阶段生成

当App组件render的阶段,render函数会访问到模版中的{{ name }},则会触发computed的求值,也就是执行上面代码computedGetter()。执行watcher.evaluate()。也就是执行wathcer.get。上文依赖收集的第3点:依赖收集触发阶段有对get方法进行讲解,忘了的可以上去回顾一下执行watcher.depend()

Watcher.prototype.depend = function depend () {
  var i = this.deps.length;
  while (i--) {
    // 也就是调用Dep.depend => Watcher.addDep => dep.addSub
    this.deps[i].depend();
  }
}
// this.firstName和this.secondName的dep.subs
dep.subs: [name的computed watcher, App组件的渲染Watcher]

代码中判断watcher.dirty标志是什么?有什么用?

只有computed的值发生改变(也就是其依赖的数据改变),watcher.dirty才会被设为true

只有watcher.dirtytrue才会对computed进行 求值 或 重新求值

总结:也就是组件每次render,如果computed的值没改变,直接返回value值(是不需要重新计算的),这也是computed的一个特点

  • 首先pushTargetDep.target从App组件的渲染Watcher改为namecomputed Watcher
  • 其次执行cb:function() { return this.firstName + this.secondName }
  • 执行cb的过程中,必然会访问到firstNamesecondName,这时候就是我们熟悉的依赖收集阶段了。firstName、secondName都会把name这个computed watcher收集到自己的dep.subs[]
  • 最后popTarget把name的computed Watcher弹出栈,并恢复Dep.target为当前App组件的渲染Watcher
  • 遍历computed watcher的deps。其实就是firstName、secondName实例的Dep
  • dep.depend也就是调用watcher.addDep(把Dep收集进watcher.deps中),再由watcher.appDep调用dep.addSub(把Watcher收集进dep.subs中)
  • 这样一来,就完成了firstName、secondName对App组件的渲染watcher进行收集
  • 结果如下。响应式数据中会存在两个Watcher
  • 至于为什么响应式数据要收集2个watcher?下文computed派发更新会讲解

讲到这里,我以自己的理解讲解下文章开头引言的问题:为什么Watcher、Dep多对多且相互收集? 这可能也是大家阅读Vue源码中一直存在的一个疑惑(包括我自己刚开始读也是这样)

对的,当然是为了computed中的响应式数据收集渲染Watcher啦!!!

还有!!! 还记得前文中依赖收集的第3点——依赖收集触发阶段的代码讲解中我写了很多注释的cleanupDeps吗?

// 此时flag为true,也就是说msg2没有渲染在页面中
<div v-if="flag">{{ msg1 }}</div>
<div v-else>{{ msg2 }}</div>
<button @click=() => { this.msg2 = 'change' }>changeMsg2</button>
function cleanupDeps () {
  var i = this.deps.length;
  while (i--) {
    // 这里对watcher所观测的响应式数据的dep进行遍历
    // 对的,这样一来,是不是watcher中的deps就发挥作用了呢?
    var dep = this.deps[i];
    if (!this.newDepIds.has(dep.id)) {
      // 这里对当前渲染中没有访问到的响应式数据进行依赖移除
      dep.removeSub(this);
    }
  }
  ...
}
  • cleanupDeps的作用就是清除掉当前没有使用到的响应式数据。怎么清除?我们往下看
  • 首先看个案例回答个问题,代码如下。当flag为true时,msg2并没有渲染在页面中,那么此时我们点击按钮修改msg2的值会不会、或者应不应该触发这个组件的重新渲染呢?
  • 答案肯定是不会、不应该。所以:cleanupDeps就是为此而存在的
  • cleanupDeps是怎么工作的呢?接着看下面代码
  • 到此,你是否已经懂得了watcher中为什么要收集自己观测的响应式数据对应的dep呢?

2. computed派发更新

派发相对来说比较简单了~跟响应式的派发更新基本一致,继续以案例来讲解吧!

当我们修改firstName会发生什么?this.firstName = 'change'

首先触发firstName的set,最终会调用dep.notify()。firstName的dep.subs中有2个watcher,分别执行对应watcher的notify

Watcher.prototype.update = function update () {
  if (this.lazy) {
    this.dirty = true; // computed会走到这里,然后就结束了
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this); // 渲染watcher会走到这里
  }
}

computed watcher:将dirty属性置为true。

渲染watcher会执行派发更新流程(如本文响应式流程——2.派发更新一致)

nextTick阶段执行flushSchedulerQueue,则会执行watcher.run()

watcher.run会执行watcher.get方法,也就是重新执行render、update的流程

执行render又会访问到name的computed,从而又会执行computedGetter

此时的watcher.dirty在本步骤3已经置为true,又会执行watcher.evaluate()进行computed的求值,执行watcher.depend()......后续的流程就是派发更新的流程了~

3. user Watcher依赖收集

user Watcher的依赖收集相比computed会简单一点,这里不会赘述太多,只说核心区别,还有watch的常用配置immediatedeepsync

user Watcher在init阶段会执行一次watcher.get(),在这里会访问我们watch的响应式数据,从而进行依赖收集。回顾下computed,computed在这个阶段什么也没做。

// 没错,又是这段熟悉的代码
this.value = this.lazy
  ? undefined
  : this.get(); // user Watcher和渲染 Watcher都在new Watcher阶段执行get()

如果userWatcher设置的immediate: true,则会在new Watcher后主动触发一次cb的执行

Vue.prototype.$watch = function (expOrFn, cb, options) {
  ...
  var watcher = new Watcher(vm, expOrFn, cb, options);
  if (options.immediate) {
    // immediate则会执行我们传入的callback
    try {
      cb.call(vm, watcher.value);
    } catch (error) {

    }
  }
  return function unwatchFn () {
    watcher.teardown();
  }
};

deep逻辑很简单,大概讲下:深度遍历这个对象,访问到该对象的所有属性,以此来触发所有属性的getter。这样,所有属性都会把当前的user Watcher收集到自己的dep中。因此,深层的属性值修改(触发set派发更新能通知到user Watcher),watch自然就能监测到数据改变~感兴趣的同学可以自己去看看源码中traverse的实现。

sync。当前tick执行,以此能先于渲染Wathcer执行。不设置同步的watcher都会放到nextTick中执行。

Watcher.prototype.update = function update () {
  if (this.lazy) {
    this.dirty = true; // 计算属性
  } else if (this.sync) {
    this.run(); // 同步的user Wathcer
  } else {
    queueWatcher(this); // 普通user Watcher和渲染Watcher
  }
}

总体来说,Vue的源码其实是比较好上手的,整体代码流程非常的清晰。但是想要深入某一块逻辑,最好结合流程图debugger方式亲自上手实践。毕竟真正搞懂一门框架的源码并非易事,我也是通过不断debugger调试,一遍遍走核心流程,才能较好的学习理解vue的实现原理~

写在最后,这篇文章也算是自己的一个知识沉淀吧,毕竟很早之前就学习过Vue的源码了,但是也一直没做笔记。现在回顾一下,发现很多都有点忘了,但是缺乏一个快速记忆、回顾的笔记。如果要直接硬磕源码重新记忆,还是比较费时费力的~作为知识分享,希望可以帮助到想学习源码,想要进阶的你,大家彼此共勉,一同进步!

以上就是图解Vue 响应式原理的详细内容,更多关于Vue 响应式的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue.js响应式数据的简单实现方法(一看就会)

    目录 引言 基本概念 副作用函数 响应式数据 响应式数据的基本实现 实现思路 初步实现尝试 完善响应系统 泛化副作用函数名 修复漏洞 总结 引言 在Vue.js之中,Vue会自动跟踪JavaScript状态变化并在状态发生改变时响应式地更新DOM,这就是Vue.js的两大核心功能之一——响应性,是每一个Vue.js框架使用者必须熟练掌握的的功能.而得益于Vue.js自身支持的声明式渲染,Vue.js的学习成本大大降低,就算是一个前端领域的小白,只要能看懂并简单使用基本的HTML.JavaScri

  • Vue响应式系统的原理详解

    目录 vue响应式系统的基本原理 1.回顾一下Object.defineProperty的用法 2.实战1:使用 Object.defineProperty 对 person的age属性 进行监听 3.数据代理 4.vue中实现响应式思路 总结 1.Vue中的数据代理: 2.Vue中数据代理的好处: 3.基本原理: 4.vue中实现响应式思路 vue响应式系统的基本原理 我们使用vue时,对数据进行操作,就能影响对应的视图.那么这种机制是怎么实现的呢? 思考一下,是不是就好像我们对数据的操作 被

  • vue3中defineProps传值使用ref响应式失效详解

    子组件接收父组件的传参. 父组件: <template> <Son :data="data"/> </template> <script setup> import { ref } from "vue"; let data = ref('hello') setTimeout(() => { data.value = 'how are you doing' }, 2000) </script> 子组件:

  • Vue2 响应式系统之数组

    目录 1.场景 2.场景 2 3.方案 3.收集依赖代码实现 4.通知依赖代码实现 5.测试 6.总结 本文接Vue2响应式系统.Vue2 响应式系统之分支切换 ,响应式系统之嵌套.响应式系统之深度响应还没有看过的小伙伴需要看一下. 1.场景 import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { list: ["hello"],

  • vue中provide inject的响应式监听解决方案

    目录 provide inject的响应式监听解决 vue监听赋值及provide与inject provide inject的响应式监听解决 提示:provide 和 inject 绑定并不是可响应的.这是刻意为之的.然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的. 所以传值传对象即可 provide(){     return {       provObj: {         uuidList:{}       }     }   }, this._provided.p

  • 图解Vue 响应式流程及原理

    目录 阅读本文能够帮助你什么? 一.组件化流程 1. 整个new Vue阶段做了什么? 2. 普通dom元素如何渲染到页面? 3. 组件如何渲染到页面? 4. Vue组件化简化流程 二.响应式流程 1. 依赖收集 2. 派发更新 三.彩蛋篇 1. computed依赖收集 2. computed派发更新 3. user Watcher依赖收集 阅读本文能够帮助你什么? 在学习vue源码的时候发现组件化过程很绕? 在响应式过程中Observer.Dep.Watcher三大对象傻傻分不清? 搞不清楚

  • Vue 响应式系统依赖收集过程原理解析

    目录 背景 目标 源码解读 入口函数:observe class Observer Observe 如何处理数组 Observe 如何处理对象 class Dep Dep.target class Watcher Watcher 的应用 何时触发依赖收集? 数据变化时,如何进行更新? 总结 参考资料 背景 在 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) ,它的定义在 src/core/instance/state.js 中.在初始化 data 和 pro

  • 详细分析vue响应式原理

    前言 响应式原理作为 Vue 的核心,使用数据劫持实现数据驱动视图.在面试中是经常考查的知识点,也是面试加分项. 本文将会循序渐进的解析响应式原理的工作流程,主要以下面结构进行: 分析主要成员,了解它们有助于理解流程 将流程拆分,理解其中的作用 结合以上的点,理解整体流程 文章稍长,但大部分是代码实现,还请耐心观看.为了方便理解原理,文中的代码会进行简化,如果可以请对照源码学习. 主要成员 响应式原理中,Observe.Watcher.Dep这三个类是构成完整原理的主要成员. Observe,响

  • Vue响应式原理的示例详解

    Vue 最独特的特性之一,是非侵入式的响应系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.聊到 Vue 响应式实现原理,众多开发者都知道实现的关键在于利用 Object.defineProperty , 但具体又是如何实现的呢,今天我们来一探究竟. 为了通俗易懂,我们还是从一个小的示例开始: <body> <div id="app"> {{ message }} </div> <script> v

  • vue中v-model和响应式的实现原理解析

    目录 v-model 响应式实现 v-model 首先要了解v-model就是vue帮我们封装的语法糖,真正实现靠的还是: v-bind:绑定响应式数据 触发 input 事件 并传递数据 例如下面示例: <template> // 这两种写法等价 <input v-bind:name="name" v-on:input="name=$event.target.value"/> <input v-model="name&quo

  • 一篇文章带你彻底搞懂VUE响应式原理

    目录 响应式原理图 编译 创建compile类 操作fragment 获取元素节点上的信息 获取文本节点信息 操作fragment 响应式 数据劫持 收集依赖 响应式代码完善 Dep类 全局watcher用完清空 依赖的update方法 需要注意的一个地方 双剑合璧 总结 首先上图,下面这张图,即为MVVM响应式原理的整个过程图,我们本篇都是围绕着这张图进行分析,所以这张图是重中之重. 响应式原理图 一脸懵逼?没关系,接下来我们将通过创建一个简单的MVVM响应系统来一步步了解这个上图中的全过程.

  • Vue响应式原理Observer、Dep、Watcher理解

    开篇 最近在学习Vue的源码,看了网上一些大神的博客,看起来感觉还是蛮吃力的.自己记录一下学习的理解,希望能够达到简单易懂,不看源码也能理解的效果

  • vue响应式原理与双向数据的深入解析

    了解object.defineProperty 实现响应式 清楚 observe/watcher/dep 具体指的是什么 了解 发布订阅模式 以及其解决的具体问题 在Javascript里实现数据响应式一般有俩种方案,分别对应着vue2.x 和 vue3.x使用的方式,他们分别是: 对象属性拦截 (vue2.x) Object.defineProperty 对象整体代理 (vue3.x) Proxy 提示:以下是本篇文章正文内容,下面案例可供参考 vue-响应式是什么? Vue 最独特的特性之一

  • VUE响应式原理的实现详解

    目录 总结 前言 相信vue学习者都会发现,vue使用起来上手非常方便,例如双向绑定机制,让我们实现视图.数据层的快速同步,但双向绑定机制实现的核心数据响应的原理是怎么样的呢,接下来让我们开始介绍: function observer(value) { //给所有传入进来的data 设置一个__ob__对象 一旦value有__ob__ 说明该value已经做了响应式处理 Object.defineProperty(value, '__ob__', { value: this, //当前实例 也

随机推荐