title: vue源码解析之响应式原理
date: 2020-11-06 18:21:41
tags:
- vue
- 源码
categories: 学习
copyright: false
top_img: /img/bg1.png
vue响应式原理的核心代码主要是在:src\core\instance\state.js
export function initState (vm: Component) {vm._watchers = []const opts = vm.$optionsif (opts.props) initProps(vm, opts.props)if (opts.methods) initMethods(vm, opts.methods)if (opts.data) {initData(vm)} else {observe(vm._data = {}, true /* asRootData */)}if (opts.computed) initComputed(vm, opts.computed)if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)}}
直截了当的看下initData
initData:
function initData (vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}if (!isPlainObject(data)) {data = {}process.env.NODE_ENV !== 'production' && warn('data functions should return an object:\n' +'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}// proxy data on instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`,vm)}}if (props && hasOwn(props, key)) {process.env.NODE_ENV !== 'production' && warn(`The data property "${key}" is already declared as a prop. ` +`Use prop default value instead.`,vm)} else if (!isReserved(key)) {proxy(vm, `_data`, key)}}// observe dataobserve(data, true /* asRootData */)}
上边代码,我们的主线任务就是observe(data, true /* asRootData */),
直线任务就是数据代理 proxy(vm, _data, key)
proxy:
const sharedPropertyDefinition = {enumerable: true,configurable: true,get: noop,set: noop}export function proxy (target: Object, sourceKey: string, key: string) {sharedPropertyDefinition.get = function proxyGetter () {return this[sourceKey][key]}sharedPropertyDefinition.set = function proxySetter (val) {this[sourceKey][key] = val}Object.defineProperty(target, key, sharedPropertyDefinition)}
数据代理其实非常简单,也就这几行代码,vue中data是储存在_data中的,通过Object.defineProperty,设置get和set,将target(vm)取值代理到了,vm._data,赋值也是代理到了,vm._data
observe:
export function observe (value: any, asRootData: ?boolean): Observer | void {if (!isObject(value) || value instanceof VNode) {return}let ob: Observer | voidif (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {ob = value.__ob__} else if (shouldObserve &&!isServerRendering() &&(Array.isArray(value) || isPlainObject(value)) &&Object.isExtensible(value) &&!value._isVue) {ob = new Observer(value)}if (asRootData && ob) {ob.vmCount++}return ob}
上边代码中,主要关注的是ob = new Observer(value),vue将value(data)传进了Observer构造函数进行实例化,那我们就看下Observer构造函数的相关代码
Observer类
export class Observer {value: any;dep: Dep;vmCount: number; // number of vms that have this object as root $dataconstructor (value: any) {this.value = valuethis.dep = new Dep()this.vmCount = 0def(value, '__ob__', this)if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods)} else {copyAugment(value, arrayMethods, arrayKeys)}this.observeArray(value)} else {this.walk(value)}}/*** Walk through all properties and convert them into* getter/setters. This method should only be called when* value type is Object.*/walk (obj: Object) {const keys = Object.keys(obj)for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i])}}/*** Observe a list of Array items.*/observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}}}
上边代码在constructor中判断了是否为数组,那就是说明,vue对于对象和数组进行了不同的处理,这时候应该能联想到,vue文档中数组的7种变异方法。我们待会再来看这个,我们首先看不是数组的情况。
也就是walk方法。
walk
walk (obj: Object) {const keys = Object.keys(obj)for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i])}}
显然,defineReactive是我们需要关注的.
export function defineReactive (obj: Object,key: string,val: any,customSetter?: ?Function,shallow?: boolean) {const dep = new Dep()const property = Object.getOwnPropertyDescriptor(obj, key)if (property && property.configurable === false) {return}// cater for pre-defined getter/settersconst getter = property && property.getconst setter = property && property.setif ((!getter || setter) && arguments.length === 2) {val = obj[key]}let childOb = !shallow && observe(val)//递归,拦截对象中的所有对象Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {const value = getter ? getter.call(obj) : valif (Dep.target) {dep.depend()if (childOb) {childOb.dep.depend()if (Array.isArray(value)) {dependArray(value)}}}return value},set: function reactiveSetter (newVal) {const value = getter ? getter.call(obj) : val/* eslint-disable no-self-compare */if (newVal === value || (newVal !== newVal && value !== value)) {return}/* eslint-enable no-self-compare */if (process.env.NODE_ENV !== 'production' && customSetter) {customSetter()}// #7981: for accessor properties without setterif (getter && !setter) returnif (setter) {setter.call(obj, newVal)} else {val = newVal}childOb = !shallow && observe(newVal)dep.notify()}})}
我们直接来看 Object.defineProperty,
在get中,是给dep实例添加依赖,
if (Dep.target) {dep.depend()if (childOb) {childOb.dep.depend()if (Array.isArray(value)) {dependArray(value)}}
Dep类
export default class Dep {static target: ?Watcher;id: number;subs: Array<Watcher>;constructor () {this.id = uid++this.subs = []}addSub (sub: Watcher) {this.subs.push(sub)}removeSub (sub: Watcher) {remove(this.subs, sub)}depend () {if (Dep.target) {Dep.target.addDep(this)}}notify () {// stabilize the subscriber list firstconst subs = this.subs.slice()if (process.env.NODE_ENV !== 'production' && !config.async) {// subs aren't sorted in scheduler if not running async// we need to sort them now to make sure they fire in correct// ordersubs.sort((a, b) => a.id - b.id)}for (let i = 0, l = subs.length; i < l; i++) {subs[i].update()}}}
Dep类,调用实例方法depend,接着调用了类静态属性target(target表示当前取值的组件watcher)的addDep方法.
Watcher类
export default class Watcher {vm: Component;expression: string;cb: Function;id: number;deep: boolean;user: boolean;lazy: boolean;sync: boolean;dirty: boolean;active: boolean;deps: Array<Dep>;newDeps: Array<Dep>;depIds: SimpleSet;newDepIds: SimpleSet;before: ?Function;getter: Function;value: any;constructor (vm: Component,expOrFn: string | Function,cb: Function,options?: ?Object,isRenderWatcher?: boolean) {this.vm = vmif (isRenderWatcher) {vm._watcher = this}vm._watchers.push(this)// optionsif (options) {this.deep = !!options.deepthis.user = !!options.userthis.lazy = !!options.lazythis.sync = !!options.syncthis.before = options.before} else {this.deep = this.user = this.lazy = this.sync = false}this.cb = cbthis.id = ++uid // uid for batchingthis.active = truethis.dirty = this.lazy // for lazy watchersthis.deps = []this.newDeps = []this.depIds = new Set()this.newDepIds = new Set()this.expression = process.env.NODE_ENV !== 'production'? expOrFn.toString(): ''// parse expression for getterif (typeof expOrFn === 'function') {this.getter = expOrFn} else {this.getter = parsePath(expOrFn)if (!this.getter) {this.getter = noopprocess.env.NODE_ENV !== 'production' && warn(`Failed watching path: "${expOrFn}" ` +'Watcher only accepts simple dot-delimited paths. ' +'For full control, use a function instead.',vm)}}this.value = this.lazy? undefined: this.get()}/*** Evaluate the getter, and re-collect dependencies.*/get () {pushTarget(this)let valueconst vm = this.vmtry {value = this.getter.call(vm, vm)} catch (e) {if (this.user) {handleError(e, vm, `getter for watcher "${this.expression}"`)} else {throw e}} finally {// "touch" every property so they are all tracked as// dependencies for deep watchingif (this.deep) {traverse(value)}popTarget()this.cleanupDeps()}return value}/*** Add a dependency to this directive.*/addDep (dep: Dep) {const id = dep.idif (!this.newDepIds.has(id)) {this.newDepIds.add(id)this.newDeps.push(dep)if (!this.depIds.has(id)) {dep.addSub(this)}}}/*** Clean up for dependency collection.*/cleanupDeps () {let i = this.deps.lengthwhile (i--) {const dep = this.deps[i]if (!this.newDepIds.has(dep.id)) {dep.removeSub(this)}}let tmp = this.depIdsthis.depIds = this.newDepIdsthis.newDepIds = tmpthis.newDepIds.clear()tmp = this.depsthis.deps = this.newDepsthis.newDeps = tmpthis.newDeps.length = 0}/*** Subscriber interface.* Will be called when a dependency changes.*/update () {/* istanbul ignore else */if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()} else {queueWatcher(this)}}/*** Scheduler job interface.* Will be called by the scheduler.*/run () {if (this.active) {const value = this.get()if (value !== this.value ||// Deep watchers and watchers on Object/Arrays should fire even// when the value is the same, because the value may// have mutated.isObject(value) ||this.deep) {// set new valueconst oldValue = this.valuethis.value = valueif (this.user) {try {this.cb.call(this.vm, value, oldValue)} catch (e) {handleError(e, this.vm, `callback for watcher "${this.expression}"`)}} else {this.cb.call(this.vm, value, oldValue)}}}}/*** Evaluate the value of the watcher.* This only gets called for lazy watchers.*/evaluate () {this.value = this.get()this.dirty = false}/*** Depend on all deps collected by this watcher.*/depend () {let i = this.deps.lengthwhile (i--) {this.deps[i].depend()}}/*** Remove self from all dependencies' subscriber list.*/teardown () {if (this.active) {// remove self from vm's watcher list// this is a somewhat expensive operation so we skip it// if the vm is being destroyed.if (!this.vm._isBeingDestroyed) {remove(this.vm._watchers, this)}let i = this.deps.lengthwhile (i--) {this.deps[i].removeSub(this)}this.active = false}}}
addDep (dep: Dep) {const id = dep.idif (!this.newDepIds.has(id)) {this.newDepIds.add(id)this.newDeps.push(dep)if (!this.depIds.has(id)) {dep.addSub(this)}}}
addDep将dep的相关信息,添加到了watcher中,同时执行了dep的addSub将watcher添加到dep中的subs数组中,形成相互依赖。
set: function reactiveSetter (newVal) {const value = getter ? getter.call(obj) : val/* eslint-disable no-self-compare */if (newVal === value || (newVal !== newVal && value !== value)) {return}/* eslint-enable no-self-compare */if (process.env.NODE_ENV !== 'production' && customSetter) {customSetter()}// #7981: for accessor properties without setterif (getter && !setter) returnif (setter) {setter.call(obj, newVal)} else {val = newVal}childOb = !shallow && observe(newVal)dep.notify()}
我们看到在set函数中,执行了dep.notify()这个函数就是来发布,通知订阅者进行更新的。
notify () {// stabilize the subscriber list firstconst subs = this.subs.slice()if (process.env.NODE_ENV !== 'production' && !config.async) {// subs aren't sorted in scheduler if not running async// we need to sort them now to make sure they fire in correct// ordersubs.sort((a, b) => a.id - b.id)}for (let i = 0, l = subs.length; i < l; i++) {subs[i].update()}}
我们知道,subs数组中的都是watcher,而每一个watcher中都有一个update函数,是用来更新页面的。
update之前nextTick介绍过就不重复介绍了。到此为止,vue响应式原理的核心内容已经结束。
