Vue运行内部运行机制 总览图:react
在 new Vue()以后。 Vue 会调用 _init 函数进行初始化,也就是这里的 init 过程,它会初始化生命周期、事件、 props、 methods、 data、 computed 与 watch 等。其中最重要的是经过 Object.defineProperty 设置 setter 与 getter 函数,用来实现「响应式」以及「依赖收集」,后面会详细讲到,这里只要有一个印象便可。算法
初始化以后调用 $mount 会挂载组件,若是是运行时编译,即不存在 render function可是存在 template 的状况,须要进行「编译」步骤。
由于编译有构建时编译与运行时编译的,其目的都是将template转化炒年糕render function,因此若是运行时检查到template存在可是没有render function的状况下会把template编译成render function。数组
compile编译能够分红 parse、optimize 与 generate 三个阶段,最终须要获得 render function浏览器
parse 会用正则等方式解析 template 模板中的指令、class、style等数据,造成AST。闭包
optimize 的主要做用是标记 static 静态节点,这是 Vue 在编译过程当中的一处优化,后面当 update 更新界面时,会有一个 patch 的过程, diff 算法会直接跳过静态节点,从而减小了比较的过程,优化了 patch 的性能。框架
generate 是将 AST 转化成 render function 字符串的过程,获得结果是 render 的字符串以及 staticRenderFns 字符串。
在经历过 parse、optimize 与 generate 这三个阶段之后,组件中就会存在渲染 VNode 所需的 render function 了。异步
接下来也就是 Vue.js 响应式核心部分。
这里的 getter 跟 setter 已经在以前介绍过了,在 init 的时候经过 Object.defineProperty 进行了绑定,它使得当被设置的对象被读取的时候会执行 getter 函数,而在当被赋值的时候会执行 setter 函数。
当 render function 被渲染的时候,由于会读取所需对象的值,因此会触发 getter 函数进行「依赖收集」,「依赖收集」的目的是将观察者 Watcher 对象存放到当前闭包中的订阅者 Dep 的 subs 中。造成以下所示的这样一个关系。函数
在修改对象的值的时候,会触发对应的 setter, setter 通知以前「依赖收集」获得的 Dep 中的每个 Watcher,告诉它们本身的值改变了,须要从新渲染视图。这时候这些 Watcher 就会开始调用 update 来更新视图,固然这中间还有一个 patch 的过程以及使用队列来异步更新的策略,这个咱们后面再讲。性能
咱们知道,render function 会被转化成 VNode 节点。Virtual DOM 其实就是一棵以 JavaScript 对象( VNode 节点)做为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终能够经过一系列操做使这棵树映射到真实环境上。因为 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,因此使它具备了跨平台的能力,好比说浏览器平台、Weex、Node 等。
好比说下面这样一个例子:优化
{ tag: 'div', /*说明这是一个div标签*/ children: [ /*存放该标签的子节点*/ { tag: 'a', /*说明这是一个a标签*/ text: 'click me' /*标签的内容*/ } ] }
渲染后能够获得
<div> <a>click me</a> </div>
这只是一个简单的例子,实际上的节点有更多的属性来标志节点,好比 isStatic (表明是否为静态节点)、 isComment (表明是否为注释节点)等。
前面咱们说到,在修改一个对象值的时候,会经过 setter -> Watcher -> update 的流程来修改对应的视图,那么最终是如何更新视图的呢?
当数据变化后,执行 render function 就能够获得一个新的 VNode 节点,咱们若是想要获得新的视图,最简单粗暴的方法就是直接解析这个新的 VNode 节点,而后用 innerHTML 直接所有渲染到真实 DOM 中。可是其实咱们只对其中的一小块内容进行了修改,这样作彷佛有些「浪费」。
那么咱们为何不能只修改那些「改变了的地方」呢?这个时候就要介绍「patch」了。咱们会将新的 VNode 与旧的 VNode 一块儿传入 patch 进行比较,通过 diff 算法得出它们的「差别」。最后咱们只须要将这些「差别」的对应 DOM 进行修改便可。
Vue.js 是一款 MVVM 框架,数据模型仅仅是普通的 JavaScript 对象,可是对这些对象进行操做时,却能影响对应视图,它的核心实现就是「响应式系统」。尽管咱们在使用 Vue.js 进行开发时不会直接修改「响应式系统」,可是理解它的实现有助于避开一些常见的「坑」,也有助于在碰见一些琢磨不透的问题时能够深刻其原理来解决它。
Object.defineProperty
首先咱们来介绍一下 Object.defineProperty,Vue.js就是基于它实现「响应式系统」的。
首先是使用方法:
/* obj: 目标对象 prop: 须要操做的目标对象的属性名 descriptor: 描述符=>{ enumerable: false, //对象的属性是否能够在 for...in 循环和 Object.keys() 中被枚举 configurable: false, //对象的属性是否能够被删除,以及除writable特性外的其余特性是否能够被修改。 writable: false, //为true时,value才能被赋值运算符改变。默认为 false。 value: "static", //该属性对应的值。能够是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。 get : function(){ //一个给属性提供 getter 的方法,若是没有 getter 则为 undefined。该方法返回值被用做属性值。默认为 undefined。 return this.value; }, set : function(newValue){ //提供 setter 的方法,若是没有 setter 则为 undefined。将该参数的新值分配给该属性。默认为 undefined。 this.value = newValue; }, } return value 传入对象 */ Object.defineProperty(obj, prop, descriptor) // 举个栗子 // 使用 __proto__ var obj = {}; var descriptor = Object.create(null); // 没有继承的属性 // 默认没有 enumerable,没有 configurable,没有 writable descriptor.value = 'static'; Object.defineProperty(obj, 'key', descriptor); // 显式 Object.defineProperty(obj, "key", { enumerable: false, configurable: false, writable: false, value: "static" }); // 在对象中添加一个属性与存取描述符的示例 var bValue; Object.defineProperty(o, "b", { get : function(){ return bValue; }, set : function(newValue){ bValue = newValue; }, enumerable : true, configurable : true });
要熟悉Object.defineProperty能够去MDN文档复习示例。
知道了 Object.defineProperty 之后,咱们来用它使对象变成可观察的。
这一部分的内容咱们在第二小节中已经初步介绍过,在 init 的阶段会进行初始化,对数据进行「响应式化」
为了便于理解,咱们不考虑数组等复杂的状况,只对对象进行处理。
首先咱们定义一个 cb 函数,这个函数用来模拟视图更新,调用它即表明更新视图,内部能够是一些更新视图的方法。
function cb (val) { /* 渲染视图 */ console.log("视图更新啦~"); }
而后咱们定义一个 defineReactive ,这个方法经过 Object.defineProperty 来实现对对象的「响应式」化,入参是一个 obj(须要绑定的对象)、key(obj的某一个属性),val(具体的值)。通过 defineReactive 处理之后,咱们的 obj 的 key 属性在「读」的时候会触发 reactiveGetter 方法,而在该属性被「写」的时候则会触发 reactiveSetter 方法。
function defineReactive (obj, key, val) { Object.defineProperty(obj, key, { enumerable: true, /* 属性可枚举 */ configurable: true, /* 属性可被修改或删除 */ get: function reactiveGetter () { return val; /* 实际上会依赖收集,下一小节会讲 */ }, set: function reactiveSetter (newVal) { if (newVal === val) return; cb(newVal); } }); }
固然这是不够的,咱们须要在上面再封装一层 observer 。这个函数传入一个 value(须要「响应式」化的对象),经过遍历全部属性的方式对该对象的每个属性都经过 defineReactive 处理。
function observer (value) { if (!value || (typeof value !== 'object')) {/*只考虑对象,非对象返回*/ return; } Object.keys(value).forEach((key) => { defineReactive(value, key, value[key]); }); }
最后,让咱们用 observer 来封装一个 Vue 吧!
在Vue的构造函数中,对options的data进行处理,这里的data想必你们很熟悉,就是平时咱们在写Vue项目时组件中的data属性(其实是一个函数,这里当作一个对象来简单处理)
class Vue{ /* Vue 构造类 */ constructor(options) { this._data = options.data; observer(this._data); } }
这样咱们只要 new 一个 Vue 对象,就会将 data 中的数据进行「响应式」化。若是咱们对 data 的属性进行下面的操做,就会触发 cb 方法更新视图。
let o = new Vue({ data: { test: "I am test." } }); o._data.test = "hello,world."; /* 视图更新啦~ */