关键词文章采集源码(Vue-beta.0vue源码注释:文章中源码的语法都使用Flow)

优采云 发布时间: 2021-09-15 15:23

  关键词文章采集源码(Vue-beta.0vue源码注释:文章中源码的语法都使用Flow)

  Vue是中国全球三分之一的前端网络终端。作为我的主要技术之一,我了解它,并对日常使用感到好奇。因此,此外,最近社区中出现了大量读取文章的Vue源代码。借此机会,我从大家的文章和讨论中汲取了一些营养,并在阅读源代码时总结了一些想法,制作了一些文章,作为我思想的总结,我的水平有限,欢迎留言讨论~

  目标Vue版本:2.5.17-beta.0

  Vue源代码注释:

  语句:文章使用flow作为源代码的语法,并根据需要对源代码进行了删节(以避免混淆)。如果要查看完整版本,请在上面输入GitHub地址。本文是一系列文章,底部显示了文章地址~

  1.响应系统

  通过官网的介绍,我们知道vue.js是一个MVVM框架。它不关心视图更改,而是通过数据驱动的视图更新,这使得我们的状态管理非常简单,这是如何实现的。从官方网站偷一张照片

  

  每个组件实例都有一个对应的观察者实例对象。它将在组件渲染期间将属性记录为依赖项。稍后,当调用依赖项的setter时,它将通知观察者重新计算,以便更新其关联的组件

  有三个重要的概念:observe、DEP和watcher,分别位于Src/core/observer/index.js、Src/core/observer/DEP.js和Src/core/observer/monitor.js中

  2.代码实现2.1初始状态

  响应条目位于Src/core/instance/init.js的initstate中:

  // src/core/instance/state.js

export function initState(vm: Component) {

const opts = vm.$options

if (opts.props) initProps(vm, opts.props) // 初始化props

if (opts.methods) initMethods(vm, opts.methods) // 初始化methods

if (opts.data) initData(vm) // 初始化data

if (opts.computed) initComputed(vm, opts.computed) // 初始化computed

if (opts.watch) initWatch(vm, opts.watch) // 初始化watch

}

}

  它定期定义几种方法来初始化道具、方法、数据、计算和wathcer。让我们看一下iITDATA方法

  // src/core/instance/state.js

function initData(vm: Component) {

let data = vm.$options.data

data = vm._data = typeof data === 'function'

? getData(data, vm)

: data || {}

observe(data, true /* asRootData */) // 给data做响应式处理

}

  首先,判断下一个数据是否是函数,如果是则取返回值,如果不是则取自身。然后采用观察法对数据进行处理。此方法尝试为_uu uuu创建观察者实例,如果成功创建,将返回一个新的观察者实例。如果存在现有的观察者实例,则返回现有的观察者实例

  2.2观察员/定义活动

  // src/core/observer/index.js

export function observe (value: any, asRootData: ?boolean): Observer | void {

let ob: Observer | void

ob = new Observer(value)

return ob

}

  此方法主要使用数据作为参数来实例化观察者对象实例。Observer是一个用于依赖项采集和通知更新的类。观察者构造函数使用definereactive方法形式化对象的键响应,递归地向对象的属性添加getter/setter,在获取数据时触发getter并采集依赖项,在修改值时,首先触发getter,然后触发setter并发送更新

  // src/core/observer/index.js

export class Observer {

value: any;

dep: Dep;

constructor (value: any) {

value: any;

this.dep = new Dep()

def(value, '__ob__', this) // def方法保证不可枚举

this.walk(value)

}

// 遍历对象的每一个属性并将它们转换为getter/setter

walk (obj: Object) {

const keys = Object.keys(obj)

for (let i = 0; i < keys.length; i++) { // 把所有可遍历的对象响应式化

defineReactive(obj, keys[i])

}

}

}

export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) {

const dep = new Dep() // 在每个响应式键值的闭包中定义一个dep对象

// 如果之前该对象已经预设了getter/setter则将其缓存,新定义的getter/setter中会将其执行

const getter = property && property.get

const setter = property && property.set

let childOb = !shallow && observe(val)

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get: function reactiveGetter () {

const value = getter ? getter.call(obj) : val // 如果原本对象拥有getter方法则执行

if (Dep.target) { // 如果当前有watcher在读取当前值

dep.depend() // 那么进行依赖收集,dep.addSub

}

return value

},

set: function reactiveSetter (newVal) {

const value = getter ? getter.call(obj) : val // 先getter

if (newVal === value || (newVal !== newVal && value !== value)) { // 如果跟原来值一样则不管

return

}

if (setter) { setter.call(obj, newVal) } // 如果原本对象拥有setter方法则执行

else { val = newVal }

dep.notify() // 如果发生变更,则通知更新,调用watcher.update()

}

})

}

  依赖项采集在getter期间执行。请注意,依赖项采集仅在dep.target中有值时执行。调用watcher实例的get方法时,pushtarget将此dep.target推入dep.target,原创watcher推入targetstack,获取watcher的当前值后,将其移出堆栈,并将原创watcher值分配给dep.target。Cleanupdeps将最终清除不再在新Dept中的观察程序,以防止视图中触发不必要的无用观察程序

  设置setter时,首先获取getter,如果与旧值相比没有变化,则返回。如果有更改,DEP将通知SUB中存储的所有依赖于此数据的观察者实例进行更新。这里,queuewatcher()将异步推送到dispatcher observer队列。下一步执行时,flushschedulerqueue()取出队列中的观察程序,执行watcher.run并执行相关的钩子函数

  2.3副署长

  上面多次提到了关键词dep。它是依赖项集合的容器,或称为依赖项采集器。它记录哪些观察者依赖于他们自己的更改,或者哪些观察者订阅他们自己的更改;以下是一位网友的一句话:

  @刘宏义0101:简单来说就是引用和计数。我会记下借我钱的人。当我的钱少的时候,我会告诉他们我没有钱

  记录借贷者的小账簿是这里dep实例中的子账簿

  // src/core/observer/dep.js

let uid = 0 // Dep实例的id,为了方便去重

export default class Dep {

static target: ?Watcher // 当前是谁在进行依赖的收集

id: number

subs: Array // 观察者集合

constructor() {

this.id = uid++ // Dep实例的id,为了方便去重

this.subs = [] // 存储收集器中需要通知的Watcher

}

addSub(sub: Watcher) { ... } /* 添加一个观察者对象 */

removeSub(sub: Watcher) { ... } /* 移除一个观察者对象 */

depend() { ... } /* 依赖收集,当存在Dep.target的时候把自己添加观察者的依赖中 */

notify() { ... } /* 通知所有订阅者 */

}

const targetStack = [] // watcher栈

export function pushTarget(_target: ?Watcher) { ... } /* 将watcher观察者实例设置给Dep.target,用以依赖收集。同时将该实例存入target栈中 */

export function popTarget() { ... } /* 将观察者实例从target栈中取出并设置给Dep.target */

  这里,dep实例中SUB采集的依赖关系是watcher,它是watcher的一个实例,将来将用于通知更新

  2.4观察者

  // src/core/observer/watcher.js

/* 一个解析表达式,进行依赖收集的观察者,同时在表达式数据变更时触发回调函数。它被用于$watch api以及指令 */

export default class Watcher {

constructor(

vm: Component,

expOrFn: string | Function,

cb: Function,

options?: ?Object,

isRenderWatcher?: boolean // 是否是渲染watcher的标志位

) {

this.getter = expOrFn // 在get方法中执行

if (this.computed) { // 是否是 计算属性

this.value = undefined

this.dep = new Dep() // 计算属性创建过程中并未求值

} else { // 不是计算属性会立刻求值

this.value = this.get()

}

}

/* 获得getter的值并且重新进行依赖收集 */

get() {

pushTarget(this) // 设置Dep.target = this

let value

value = this.getter.call(vm, vm)

popTarget() // 将观察者实例从target栈中取出并设置给Dep.target

this.cleanupDeps()

return value

}

addDep(dep: Dep) { ... } /* 添加一个依赖关系到Deps集合中 */

cleanupDeps() { ... } /* 清理newDeps里没有的无用watcher依赖 */

update() { ... } /* 调度者接口,当依赖发生改变的时候进行回调 */

run() { ... } /* 调度者工作接口,将被调度者回调 */

getAndInvoke(cb: Function) { ... }

evaluate() { ... } /* 收集该watcher的所有deps依赖 */

depend() { ... } /* 收集该watcher的所有deps依赖,只有计算属性使用 */

teardown() { ... } /* 将自身从所有依赖收集订阅列表删除 */

}

  get方法中执行的getter是updatecomponent=()=&gt;{VM._update(VM._render(),hydrating)},此方法从VM_render()开始生成渲染的vnode树。在此过程中,它访问当前Vue实例的VM上的数据,触发相应响应对象的getter,然后VM_uuUpdate()进行修补

  注意,这里的get方法最终执行getandinvoke。此方法首先遍历存储在watcher中的DEP,删除不再在newdep中的订阅,然后depids=newdepids;DEPs=newdeps,清除newdepid和newdeps。每次添加新订阅时,不再需要的旧订阅都将被删除,以便在某些情况下,例如,当依赖于模板的数据发生变化时,V-IF不再需要该模板,观察者将不会收到更新通知

  2.5总结

  整个采集过程都与此有关。您可以将其与上述过程进行比较

  

  观察者可在以下情况下使用:

  只要它依赖于其他观察者,比如数据、数据属性、计算属性和道具,那么在闭包中就会生成一个dep实例dep,dep.dependent将在调用getter时采集依赖它的人,并将依赖的观察者存储在它自己的子中,即this.sub.push(sub),以便在更改时通知将其存储在dep.subs数组中。如果依赖它的观察者发生变化,请及时更新~

  只要对象依赖于其他响应对象,就会生成一个观察者观察者来计算观察者依赖于哪些响应对象。在评估观察程序之前,将当前观察程序设置为全局dep.target,并在从属响应对象发生变化时及时更新它

  本文是一个系列文章,稍后将进行更新以共同取得进展~

  Vue源代码读取-文件结构和操作机制Vue源代码读取-依赖项采集原则Vue源代码读取-批量异步更新和nexttick原则

  互联网上的大多数帖子深度不同,甚至相互矛盾。接下来的文章是学习过程的总结。如果您发现错误,欢迎您留言并指出~

  参考:

  Vue2.1.7源代码学习vue.js技术秘密分析vue.js内部运行机制vue.js文档【大干货】携手带您通过vue部分源代码学习vue.js源代码学习I-数据选项状态学习

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线