源码解析 —— Vue的响应式数据流

Vue、React介绍

目前前端社区比较推崇的框架有Vue 和 React,公司内部许多端都自发的将原有的老技术方案(widget + jQuery)迁移到 Vue / React上了。
我觉得Vue / React 有以下几点优势

  • 首先它们都有完整的组件化方案

  • virtual Dom (前端性能提升利器)

  • 成熟的社区生态

介绍一个Vue例子

《源码解析 —— Vue的响应式数据流》

上面的例子我们初始化了一个vue组件,当我们改变这个组件的状态时,页面的内容也会随之改变,这中间并不需要我们手动的去操作页面上的dom元素。
《源码解析 —— Vue的响应式数据流》

同时我们注意到Vue提供了一个语法糖 ——watch,这个就是我们今天要讲的 Vue响应式数据流的主角!代码很简单,就是组件的状态 name 改变的时候我们输出一句话 “name change”。
下面我们会向大家解释清楚为什么这个 watch 这么重要,以及它和 Vue的响应式数据流有什么关系。

Vue的响应式数据流的优势在哪?

Vue 和 React 都是前端的组件化框架,功能上大同小异,本质上就是借助virtual Dom帮助开发者管理混乱的Dom,并提供给开发者像操作状态机一样操作页面的能力。

但是Vue的virtual Dom 不是普通的 virtual Dom

Vue 2.0 的实现有与众不同的地方。和 Vue 的响应式系统结合在一起之后,它可以让你不必做任何事就获得完全优化的重渲染。由于每个组件都会在渲染时追踪其响应依赖,所以系统精确地知道应该何时重渲染、应该重渲染哪些组件。不需要 shouldComponentUpdate,也不需要 immutable 数据 – it just works . —— 尤雨溪

我们看一下第三方的性能分析:
《源码解析 —— Vue的响应式数据流》

除了性能,最大的优势是减轻了开发者的负担,开发者大多数情况下不需要依赖 shouldComponentUpdate,也不需要依赖 immutable 数据去判断组件是否需要重新渲染,Vue会帮你做好这件事。

举个例子来说明这两个的virtual dom的不同之处:

开发者就像一个老师,Vue和React这两个学生要做的事就是根据老师给出的长宽画出对应的长方形。每当老师改变给出的长和宽时,Vue能够自己发现长和宽变没变,需不需要重新画;React则需要老师告诉它长和宽变了,需要重新画了。

预备知识:Object.defineProperty 与 订阅发布设计模式

Object.defineProperty

JavaScript 提供一个非常强大的方法 Object.defineProperty,它可以定义当某一个值访问和赋值时会先执行自定义的钩子方法。

一个简单的Object.defineProperty例子

var obj = {};
var initValue = 'hello';
Object.defineProperty(obj,"newKey",{
    get:function (){
        //当获取值的时候触发的函数
        return initValue;    
    },
    set:function (value){
        //当设置值的时候触发的函数,设置的新值通过参数value拿到
        console.log(value)
        initValue = value;
    }
});
//获取值
console.log( obj.newKey );  //hello
//设置值
obj.newKey = 'change value'; //change value

这个方法给予JavaScript开发一种面向切面编程的能力,使用该方法我们能够隐式、自然的控制属性的访问和赋值。

订阅发布设计模式

《源码解析 —— Vue的响应式数据流》

订阅发布是一个非常常见的设计模式,原理也非常简单就是订阅者订阅信息,然后发布者发布信息通知订阅者更新。

Vue 源码

前面铺垫这么多就是希望大家能理解接下来要讲的响应式数据流。

Vue的初始化

《源码解析 —— Vue的响应式数据流》
如上图,Vue的初始化会执行一系列的方法,这里我们主要介绍Vue的initState 方法。
prop和data都是组件的属性,prop通常上是父组件传递下来的,data是组件自身定义的,Vue不推荐你去改组件传递下来的prop,因为那样会带来不必要的复杂度。

Observer

Prop和data 的最终归宿都是递归执行 defineReactive方法。
《源码解析 —— Vue的响应式数据流》

那defineReactive方法做了什么呢?
《源码解析 —— Vue的响应式数据流》

defineReactive会用 Object.defineProperty将组件的每个属性都包装一下,这样谁访问了这些属性,谁重新赋值了这些属性我们都能追踪到了。
Vue里面有一个 Observe类,所有的prop子属性和data本身都会带有一个Observer对象,Observer的构造函数
《源码解析 —— Vue的响应式数据流》
在控制台我们可以看到每个属性下都有__ob__,这说明这个属性已经被包装成 Observer对象了,所以的访问和赋值都能给追踪到,这里面也保存着所有订阅该Observer的订阅者Watcher。

Watcher

我们看一下Watcher的构造函数
《源码解析 —— Vue的响应式数据流》

Watcher支持 watch 一个表达式或者是一个方法。Watcher在构造的时候会先获取一次expOrFn的值,下面我们把expOrFn称为Watcher的Getter。

Dep

还有一个关键的类是Dep,这个类会帮助我们的属性记录下所有的Watcher,每个属性都有自己的Dep实例,同时Vue的Watcher访问的属性的时候 Dep会作为一个全局变量将自身的target属性指向访问的Wathcer。会执行下面的方法
《源码解析 —— Vue的响应式数据流》

同时我们再回来看 defineReactive这个重要的方法
《源码解析 —— Vue的响应式数据流》

当Watcher访问组件的属性时,通过Dep.target,Vue可以知道是Watcher访问的, 这样当Vue自己的Watcher访问属性的时候会被记录成订阅者,而我们访问的时候Vue不会执行多余的代码。这是一个很精妙的设计,将Object.defineProperty 与 订阅发布设计模式结合起来了。
看一下整个的流程图
《源码解析 —— Vue的响应式数据流》

Vue的render函数就是 Watcher 的 expOrFn

理解了以上Vue是如何将Object.defineProperty 与 订阅发布设计模式结合起来的,然后我们再举一反三:Vue的render函数如果就是 Watcher 的 expOrFn会怎么样?
回到Vue的源码里:
《源码解析 —— Vue的响应式数据流》

这里的 vm._render就是 render函数的一个封装,我们可以看到本质上:Vue的render函数就是 Watcher 的 expOrFn。那初始化的时候我们会先执行一边 render函数,在执行render函数的过程中访问了哪些 组件的属性,Vue都会用上面的提到的方法帮我们把依赖记录下来。所以当这个属性变化的时候,自然而然,就像文章开头的watch一样,我们会重新render一次,(开头的例子是输出“name change”)。

总结

讲到这里大家应该都能够明白Vue的响应式数据流是如何实现的。同时我们能够发现Vue提供给我们的许多语法糖都是同样的道理,比如Vue的computer就是将computer函数作为Watcher的expOrFn。
希望大家在理解Vue响应式数据流的基础上能够更加自信、灵活和稳健的使用这个优秀的框架。

    原文作者:kukuv2
    原文地址: https://segmentfault.com/a/1190000007604830
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞