Axiu Blog
ew Vue({ el: '#app', data: { foo: '123' } }) `new Vue`实际会进入`Vue._init`的初始化函数,这也是一切的开始。 ### 初始化数据 这一步完成的工作是:为`data`创建`Observer`对象,使其产生响应式的效果。操作(`get/set`)`data`
因为最近在用`Vue`,所以想把代码看一下,我觉得,越多的了解运行机制,越能更好的解决问题和……避坑。 本文必须提前要了解的两个概念是:**`defineProperty`特性**,**观察者模式**。 我们以一个简单的结构来开始 new Vue({ el: '#app', data: { foo: '123' } }) `new Vue`实际会进入`V
因为最近在用`Vue`,所以想把代码看一下,我觉得,越多的了解运行机制,越能更好的解决问题和……避坑。 本文必须提前要了解的两个概念是:**`defineProperty`特性**,**观察者模式**。 我们以一个简单的结构来开始 new Vue({ el: '#app', data: { foo: '123' } }) `new Vue`实际会进入`V
Vue-初始化和渲染过程
Max

因为最近在用Vue,所以想把代码看一下,我觉得,越多的了解运行机制,越能更好的解决问题和……避坑。

本文必须提前要了解的两个概念是:defineProperty特性观察者模式

我们以一个简单的结构来开始

new Vue({ el: '#app', data: { foo: '123' } })

new Vue实际会进入Vue._init的初始化函数,这也是一切的开始。

初始化数据

这一步完成的工作是:为data创建Observer对象,使其产生响应式的效果。操作(get/setdata时,实际会操作挂载为_datadata,然后触发defineProperty设置的响应操作。

-initState -initData

config.datavm.data

用一个proxy(vm, keys[i])完成对data内容的挂载。实现用代理(proxy)的方式访问

Object.defineProperty(vm, keys[i], { get: function proxyGetter () { return vm._data[key] } set: function proxy (val) { vm._data[key] = val } })

data创建对应的Observer

这一步操作之后,创建出的结构如下

Observer structure

Observer与data的对应结构

其中vmCount以该对象作为root $datavm计数器;dep实例包含一个自增Id,和一个Watcher列表以存储该依赖的改变会触发哪些watcher更新。

代码流程

observe(data) // data => {foo: '123'},最外层data对象 -new Observer(data) -Observer.walk -defineReactive(obj, key, value) // {foo: '123', __ob__: obj}, foo, '123' -Object.defineProperty(obj, key, {...}) -let childOb = observe(val)

逐层遍历对象的属性,生成与之对应的Observer,直到目标不为对象为止。childObdepdefineReactive时创建)一样,也作为闭包对象被reactiveGetter/reactiveSetter使用。

这一步结束后,会使data的所有属性都产生响应式的效果,即,操作(get/setdata里任意位置的数据,都会触发reactiveGetter/reactiveSetter

初次渲染

整个渲染过程都包在一个watcher里,并保存在实例对象的_watcher上。和watcher前后相连的,就是beforeMountmounted两个生命周期函数。(哪些操作或配置会创建Watcher?)

-initRender -Vue._mount // beforeMount vm._watcher = new Watcher(vm, function () { vm._update(vm._render(), hydrating); }, noop); // mounted -this.value = this.get(); // this =>Watcher

注意:watcher是包含value属性的,因为创建的时候就会执行传入的getter函数并获取返回值。所以声明在watch关键字里的函数要return一个返回值。

Watcher structure

需要关注的是这个watcher如何执行的。

{ Dep.pushTarget(this); // Dep.target赋值为当前watcher value = this.getter.call(this.vm, this.vm); // Watcher的处理函数 Dep.popTarget(); this.cleanupDeps(); }

依赖收集

这部分内容比较碎,涉及到两个类:DepWatcher。需要配合观察者模式来想一下整个流程。

Dep就是dependency(依赖),前面说过,一个data属性对应一个Observer,一个Observer里包含一个dep实例。dep.subs中存储了所有观察者(Watcher),属性变更,就会触发这些watcher

Watcher就是观察者,这里的watchervm._watcher是执行渲染的基本单元。对应depwatcher中也存储了dep实例和depId,即收集此渲染所需的依赖(dep,一个dep对应一个data属性)。

所以,整个过程就是围绕着这两个类来操作的:dep拿小本本记录所有在观察它的Watcher,当有变动是,让这些Watcher采取行动。Watcher也记录所有dep依赖,必要时(不再需要观察某个dep),通知dep把自己从它的小本本上划掉。

1、准备阶段
vm._watcher.get()。保存watcherDep的静态变量target,作为是否处于收集依赖阶段的标志位使用;

2、渲染阶段
Vue._render,在需要的时候(即调用到某个data属性时)获取data的值(逐层操作,直到取到目标值)

2.1 创建依赖列表
reactiveGetter外层闭包里的dep存入watchernewDepIdsnewDeps

2.2 创建反向记录
watcher存入闭包的dep.subs,这一步很重要,属性变更时,这里是入口;

3、重置阶段
Dep.target重新置为nullnewDepIds替换depIdsnewDeps替换deps,下次使用,如果检查上次有依赖A,如果此次没有,表示已经不需要(如v-iffalse),会从dep.subs中移除。

执行渲染

渲染会用到虚拟节点VNode,限于篇幅(其实还没看),这里先不说。大概看一下渲染流程,render会生成一个奇怪的代码段(template?),来实际执行从虚拟节点到DOM节点的渲染过程。

-vm._update(vm._render(), hydrating); // vm._render()返回一个VNode -vm._render() -with(this){return _h('ul',{attrs:{"id":"demo"}},[_h('div',{on:{"click":clickHandler}},[_s(foo)])])} =createElement // 创建不同类型的节点 -vm.__patch__(vm.$el, vnode, hydrating)

更新流程:复制旧的虚拟节点 => 更新内容 => 插入替换后的节点 => 删除之前的节点

后话:框架使用感受

坊间传言VueJS更易上手,ReactJS开发难度略高,AngularJS技能树陡峭。确实,和React相比,Vue似乎天然不要求组件化,这个感觉使前端开发更趋近于传统页面脚本的逻辑。产生这种感觉,除了先入为主,还有其本身特性比较……零碎,无论是watchcomputed或者更常用的directives,都在传达一个“霍,黑科技,这个窍门很NB”的感觉。

这种感觉乍一看很吸引人,因为新上手确实酷炫。但是带来的一个问题是,新手很容易忽略框架本身的特性。虽然Vue各个环节是完备的,但是最通常的使用,还是一个UI框架+一个大Vue实例,必要的时候使用几个第三方组件。甚至当感觉代码到一定规模觉得可以拆一下的时候,还是懒得动,或者觉得没必要。相比较来说,React只有stateprops,所以入门开发人员很容易聚焦到必要的代码结构和声明周期上来。

前面的唠叨和本文主旨没太大关系,因为完整对比是要放在对二者充分了解之后,这里只是从入门简单说两句。

Comments