[vue源码01] data响应式 和 初始化渲染
文章图片
文章图片
文章图片
导航
[[深入01] 执行上下文](https://juejin.im/post/684490...)
[[深入02] 原型链](https://juejin.im/post/684490...)
[[深入03] 继承](https://juejin.im/post/684490...)
[[深入04] 事件循环](https://juejin.im/post/684490...)
[[深入05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490...)
[[深入06] 隐式转换 和 运算符](https://juejin.im/post/684490...)
[[深入07] 浏览器缓存机制(http缓存机制)](https://juejin.im/post/684490...)
[[深入08] 前端安全](https://juejin.im/post/684490...)
[[深入09] 深浅拷贝](https://juejin.im/post/684490...)
[[深入10] Debounce Throttle](https://juejin.im/post/684490...)
[[深入11] 前端路由](https://juejin.im/post/684490...)
[[深入12] 前端模块化](https://juejin.im/post/684490...)
[[深入13] 观察者模式 发布订阅模式 双向数据绑定](https://juejin.im/post/684490...)
[[深入14] canvas](https://juejin.im/post/684490...)
[[深入15] webSocket](https://juejin.im/post/684490...)
[[深入16] webpack](https://juejin.im/post/684490...)
[[深入17] http 和 https](https://juejin.im/post/684490...)
[[深入18] CSS-interview](https://juejin.im/post/684490...)
[[深入19] 手写Promise](https://juejin.im/post/684490...)
[[深入20] 手写函数](https://juejin.im/post/684490...)
[[react] Hooks](https://juejin.im/post/684490...)
[[部署01] Nginx](https://juejin.im/post/684490...)
[[部署02] Docker 部署vue项目](https://juejin.im/post/684490...)
[[部署03] gitlab-CI](https://juejin.im/post/684490...)
[[源码-webpack01-前置知识] AST抽象语法树](https://juejin.im/post/684490...)
[[源码-webpack02-前置知识] Tapable](https://juejin.im/post/684490...)
[[源码-webpack03] 手写webpack - compiler简单编译流程](https://juejin.im/post/684490...)
[[源码] Redux React-Redux01](https://juejin.im/post/684490...)
[[源码] axios ](https://juejin.im/post/684490...)
[[源码] vuex ](https://juejin.im/post/684490...)
[[源码-vue01] data响应式 和 初始化渲染 ](https://juejin.im/post/684490...)
[[源码-vue02] computed 响应式 - 初始化,访问,更新过程 ](https://juejin.im/post/684490...)
[[源码-vue03] watch 侦听属性 - 初始化和更新 ](https://juejin.im/post/684490...)
[[源码-vue04] Vue.set 和 vm.$set ](https://juejin.im/post/684490...)
[[源码-vue05] Vue.extend ](https://juejin.im/post/684490...)
[[源码-vue06] Vue.nextTick 和 vm.$nextTick ](https://juejin.im/post/684790...)
前置知识
Watcher类
- 【[vue源码01] data响应式 和 初始化渲染】Watcher分为三种
- computed watcher -
负责更新computed
- user-watcher -
watch 一个函数
- render-watcher -
重新渲染
- computed watcher -
- 三种watcher有固定的执行顺序
computed watcher -> user watcher -> render watcher
- 这样安排三种 watcher 的顺序的原因?
- computed watcher 在 render watcher 前面执行,就能保证在视图渲染的时候拿到的是最新的computed
文章图片
- data响应式具体过程
- 在
new Vue(options)
构造函数中调用this._init(options)
方法,而this._init(options)
方法是在initMixin(Vue)
中定义的 Vue.prototype._init
=> 这里主要关注initState(vm)
- 合并 options 对象
- 调用 initProxy
- 调用 initState(vm)
- 其他数据的初始化
vm.$mount(vm.$options.el)
初始化渲染
initState(vm)
=> 这里主要关注initData
- initProps
- initMethods
- initData
- initComputed
- initWatch
initData
- 传入的options对象的data,是函数就调用返回对象,是对象就直接使用
- 如果props,method,data中有相同的key值就抛出警告
proxy(vm,
_data, key)
- 主要的作用就是给data中的属性做一层代理
- 通过访问
this.name = vm.name = vm._data.name = vm.$options.data.name = vm.data.name
observe(data, true)
observe(data, true)
- 判断data是否具有
__ob__
属性,该属性表示data是否观察过了,即具有响应式了 - 没有
__ob__
属性,就就行观测,执行new Observer(value)
- 判断data是否具有
new Observer(value)
- 给data添加
__ob__
属性,值是当前的observer
实例 - data是数组
- 具有原型执行 protoAugment(value, arrayMethods)
- 重写数组原型上的 7 种方法
- push pop unshift shift splice sort reverse 这7种都改变数组
- push unshift splice 添加的属性包装成数组
-
- 继续执行
ob.observeArray(inserted)
循环遍历添加的每一项属性,执行第5步中的observe(items[i])
- 继续执行
-
- 并调用
ob.dep.notify()
执行watcher种的upate去更新视图,从而是这些重写的数组方法具有响应式
- 并调用
-
- 不具有有原型执行 copyAugment(value, arrayMethods, arrayKeys)
this.observeArray(value)
- 循环遍历数组种的每一个成员,执行第5步中的
observe(items[i])
- 循环遍历数组种的每一个成员,执行第5步中的
- 具有原型执行 protoAugment(value, arrayMethods)
- data是对象
this.walk(value)
- 给data添加
this.walk(value)
defineReactive(obj, keys[i])
defineReactive(obj, keys[i])
- Object.defineProperty
- get
- dep.depend()
- set
- dep.notify()
- get
- Object.defineProperty
- 在
- 源码
- initState - src/core/instance/state.js
initState - src/core/instance/state.js ---export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options // opts 获取vm中的 options 参数 if (opts.props) initProps(vm, opts.props) // props存在,就初始化 props if (opts.methods) initMethods(vm, opts.methods)// methods存在,就初始化 methodsif (opts.data) { // data存在,初始化 data initData(vm) } else { // data不存在 observe(vm._data = https://www.it610.com/article/{}, true /* asRootData */) }if (opts.computed) initComputed(vm, opts.computed) // computed存在,初始化 computed if (opts.watch && opts.watch !== nativeWatch) { // watch存在,并且不是原生的对象上的watch属性,就初始化 watch // 分为三种 watcher // render watcher // compute watcher // user watcher - watch initWatch(vm, opts.watch) } }
- initData - src/core/instance/state.js
initData - src/core/instance/state.js ---function initData (vm: Component) { let data = https://www.it610.com/article/vm.$options.data // 获取传入的options对象上的data属性 // 注意:这里的vm.$options在不是组件的情况下,是合并后的optionsdata = vm._data = typeof data ==='function' ? getData(data, vm) : data || {} // data是一个函数就调用取返回值,是对象就直接赋值 // vm._data = https://www.it610.com/article/vm.$options.dataif (!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 instance const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (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 ) // method中存在了该key,所以data中不能再有相同的key } } 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 // props中存在了该key,所以data中不能再有相同的key ) } else if (!isReserved(key)) { // props和methods中都不存在该key,就执行代理proxy函数 proxy(vm, `_data`, key) } } // observe data // data的响应式 observe(data, true /* asRootData */) }
- proxy - src/core/instance/state.js
proxy - src/core/instance/state.js ---export function proxy (target: Object, sourceKey: string, key: string) { // proxy(vm, `_data`, key) sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] // 1. 重写 get // 2. 返回 this._data[key] // 3. this指向:sharedPropertyDefinition.get方法是通过 vm.key 来调用的,this指向vm } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val // 1. 重写 set // 2. this._data[key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) // vm[key] = vm._data[key] // 因为: vm._data = https://www.it610.com/article/vm.$options.data // 所以:vm[key] = vm._data[key] =vm.$options.data[key] }
- observe - src/core/observer/index.js
observe - src/core/observer/index.js ---export functionobserve (value: any, asRootData: ?boolean): Observer | void{ // 1. observe(vm._data = https://www.it610.com/article/{}, true /* asRootData */) // 2. observe(data, true /* asRootData */) if (!isObject(value) || value instanceof VNode) { // 不是一个对象 或者 是VNode return } let ob: Observer | void if (hasOwn(value,'__ob__') && value.__ob__ instanceof Observer) { // 如果value具有__ob__属性 并且 __ob__ 是Observer的实例,就直接赋值 // 即已经观测过了, 直接赋值 ob = value.__ob__ } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value) // 生成一个ob实例 } if (asRootData && ob) { // 如果是根data即new Vue()初始化的时候传入的data // 并且 ob 存在 // vmCount++ // 即统计被生成的次数 ob.vmCount++ } return ob }
- Observer - src/core/observer/index.js
Observer- src/core/observer/index.js ---export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $dataconstructor (value: any) { this.value = https://www.it610.com/article/value // 赋值传入的对象 this.dep = new Dep() // dep实例 this.vmCount = 0 def(value,'__ob__', this) // def // 给 ( value ) 添加 ( __ob__ ) 属性,值是 ( observer ) 实例 // function def (obj: Object, key: string, val: any, enumerable?: boolean) { //Object.defineProperty(obj, key, { //value: val, //enumerable: !!enumerable, //writable: true, //configurable: true //}) // } if (Array.isArray(value)) { if (hasProto) { // const hasProto = '__proto__' in {} // 是数组,并且本身具有原型属性__proto__ protoAugment(value, arrayMethods) // protoAugment 重写原型 // value.__proto__ = arrayMethods // arrayMethods // const arrayMethods = Object.create(arrayProto) // const arrayProto = Array.prototype //def(arrayMethods, method, function mutator (...args){}) // 重写 7 种数组原型上的方法 } else { // 是数组,并且本身不具有原型,复制原型上的每一个属性 copyAugment(value, arrayMethods, arrayKeys) // function copyAugment (target: Object, src: Object, keys: Array) { //for (let i = 0, l = keys.length; i < l; i++) { //const key = keys[i] //def(target, key, src[key]) //} // }// const arrayKeys = Object.getOwnPropertyNames(arrayMethods)} this.observeArray(value) // 观测数组 // 1. 循环数组的每一项, 对每一项执行 observe(items[i]) } 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) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
- walk - src/core/observer/index.js
walk - src/core/observer/index.js ---walk (obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } }
- defineReactive - src/core/observer/index.js
defineReactive - src/core/observer/index.js ---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 } // Object.getOwnPropertyDescriptor(obj, key) 表示获取obj.key属性属性描述对象// cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] }let childOb = !shallow && observe(val) // 继续观测对象的每一项的value值,如果还是对象就继续观察 添加响应Object.definePropertyObject.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = https://www.it610.com/article/getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() // 循环收集依赖 if (Array.isArray(value)) { // 如果每一项的key对象的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 setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() // ---------------------------- 通知watcher执行更新 } }) }
- Dep类 - src/core/observer/dep.js
Dep类 - src/core/observer/dep.js ---export default class Dep { static target: ?Watcher; // target一个 watcher 类型的静态属性id: number; // 每次new都使id+1 // 即每次执行都自增1subs: Array
; // subs数组 用来存放 wacher constructor () { this.id = uid++ // id++ uid++ 每次执行都加 +1 this.subs = [] // 初始化为空数组,存放依赖即watcher }addSub (sub: Watcher) { this.subs.push(sub) // 添加 watcher }removeSub (sub: Watcher) { remove(this.subs, sub) // 删除 watcher }depend () { if (Dep.target) { //--------------------------------------------- 重点注意 Dep.target // Dep.target 是当前正在执行的正在计算的 watcher,存在闭包中,相当于全局变量 // 在下面有初始化Dep.target.addDep(this) // ------------------------------------ Dep 和 Watcher 相互关系 // Dep.target.addDep(this) // 向 watcher 中添加 dep 实例 // this参数就是dep实例 // Dep.target 就是一个正在计算的watcher// Watcher中的 addDep // addDep (dep: Dep) { //const id = dep.id // dep实例的id属性 //if (!this.newDepIds.has(id)) { // ------------ newDepIds中不存在该id //this.newDepIds.add(id) // 向 ( Watcher类 ) 的 newDepIds中添加 id //this.newDeps.push(dep) // 向 ( Watcher类 ) 的 newDeps 中添加 dep //if (!this.depIds.has(id)) { // ------------- depIds 中不存在该id //dep.addSub(this)// 向 ( Dep类 ) 的 subs 中添加 该watcher //} //} // }// 这里操作有点绕 // Dep.target.addDep(this) // 1. 调用 Dep.target.addDep(this) = new Watcher().addDep(this) // 2. 把 dep 实例添加到 watcher的 newDeps 数组中 // 3. 把 dep.id 添加到 watcher的 newDepIds 数组中 // 4. 执行 dep.addSubs 把 watcher 添加到 Dep的 subs 数组中 } }notify () { const subs = this.subs.slice() // 浅拷贝subs数组,缓存一份 if (process.env.NODE_ENV !== 'production' && !config.async) { subs.sort((a, b) => a.id - b.id) }for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() // 执行subs数组的每一个成员watcher上的update方法 } } }Dep.target = null // 当前watcherconst targetStack = [] // 存放watcher的栈结构,后进先出export function pushTarget (target: ?Watcher) { targetStack.push(target) // watcher 入栈Dep.target = target // 赋值最新的watcher }export function popTarget () { targetStack.pop() // watcher 出栈Dep.target = targetStack[targetStack.length - 1] // 前一个watcher }
- Watcher类 - src/core/observer/dep.js
export default class Watcher { constructor ( vm: Component, // vm实例 expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean // 是否是renderWatcher ) { this.vm = vm if (isRenderWatcher) { // 是renderWatcher, 则把该watcher实例赋值给 vm._watcher vm._watcher = this } vm._watchers.push(this) // push// parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn // 是函数赋值 getter } else { // 不是函数 this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = noop process.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 = https://www.it610.com/article/this.lazy ? undefined : this.get() }get () { pushTarget(this) let value const vm = this.vm try { 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 watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value }addDep (dep: Dep) { const id = dep.id // dep实例的id属性 if (!this.newDepIds.has(id)) { // ------------ newDepIds中不存在该id this.newDepIds.add(id) // 向 ( Watcher类 ) 的 newDepIds中添加 id this.newDeps.push(dep) // 向 ( Watcher类 ) 的 newDeps 中添加 dep if (!this.depIds.has(id)) { // ------------- depIds 中不存在该id dep.addSub(this)// 向 ( Dep类 ) 的 subs 中添加 该watcher } } }update () { /* istanbul ignore else */ if (this.lazy) { // 用于computed watcher this.dirty = true } else if (this.sync) { // 同步watcher this.run() } else { queueWatcher(this) // nextTick(flushSchedulerQueue)将watcher放入队列,在下一轮tick去更新 } } }
文章图片
(2-1) vue的不同构建版本
- 独立构建 (包括template编译的过程) -
runtime+compiler完整版
- 渲染过程:
template -> render函数 -> vnode -> 真实的dom
- 渲染过程:
- 运行时候构建(不包括template编译的过程) -
runtime版
- 渲染过程:
render函数 -> vnode -> 真实的dom
- 渲染过程:
- 打包后dist文件夹中的文件会有所不同
- 独立构建的完整版
vue.js
- 运行时版本
vue.runtime.js
- 运行时版本相比完整版体积要小大约 30%
- 独立构建的完整版
vm.$mount(vm.$options.el)
- 执行
new Vue(options)
=>this._init(options)
=>Vue.prototype._init
=>initState(vm)
=>vm.$mount(vm.$options.el)
- 即在
this._init
中先初始化initState()
把data变成响应式后,就会执行dom挂载vm.$mount(vm.$options.el)
- 即在
- vm.$mount
- 有两种版本,runtime版和runtime+compiler版本,但最终都会调用 public mount method 的$mount
- 初始化渲染过程
- 执行
vm.$mount(vm.$options.el)
方法
- 如果是runtime版本就是直接调用 mountComponent(this, el, hydrating) 方法
- 如果是runtime+compiler版本(即传入new Vue()的参数对象中不存在render方法)就会先处理template,将template通过 compileToFunctions(template, options) 函数编译成render方法,然后调用 mountComponent(this, el, hydrating) 方法
- 执行 mountComponent(this, el, hydrating) 函数
- 实例化render watcher即并把updateComponent=()=>{vm._update(vm._render(), hydrating)}函数作为new Watcher(vm, updateComponent, noop,{})的第二个参数传入
- new Watcher(vm, updateComponent, noop,{})
- 执行get()方法
- 在get方法中将当前watcher赋值给Dep.target
- 在get方法中执行updateComponent方法,从而执行vm._update(vm._render(), hydrating)方法
- vm._update(vm._render(), hydrating)
- vm._render()会把template转成 vnode
- vm._update()则会把vnode挂载到真正的dom上,渲染出页面
- 执行
- 源码
- src/core/instance/index.js
src/core/instance/index.js ---function Vue (options) { this._init(options) }
- src/core/instance/init.js
src/core/instance/init.js ---Vue.prototype._init = function (options?: Object) { // _init方法初始化 initState(vm) if (vm.$options.el) { vm.$mount(vm.$options.el) // mount方法 - 负责页面的挂载 // 传入el,el是 new Vue({el: '#app'}) 这里的el// vm.$mount(vm.$options.el) // 最终就是调用 mountComponent(this, el, hydrating) 方法 } }
- src/platforms/web/entry-runtime-with-compiler.js
总结: vm.$mount() 的作用就是:调用 mountComponent(this, el, hydrating) 方法
src/platforms/web/entry-runtime-with-compiler.js 总结: - vm.$mount(vm.$options.el) 的作用就是:调用 mountComponent(this, el, hydrating) 方法 ---const mount = Vue.prototype.$mount // mount // mout的主要作用:缓存 runtime 版本的 $mount,代码如下: // mount = Vue.prototype.$mount = function ( //el?: string | Element, //hydrating?: boolean // ): Component { //el = el && inBrowser ? query(el) : undefined //return mountComponent(this, el, hydrating) // }Vue.prototype.$mount = function ( // 这里是runtime+compiler版本的$mount方法 el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) // query(el) // 主要作用:就是将el转成Element节点 // 具体是: // 1. 是字符串并且存在,就查找id对应的dom // 2. 是字符串但是dom没有对应的元素,开发环境会抛出警告,然后就创建一个空的div返回 // 3. 不是字符串,而是dom元素直接返回 // function query (el: string | Element): Element { //if (typeof el === 'string') { //const selected = document.querySelector(el) //if (!selected) { //process.env.NODE_ENV !== 'production' && warn( //'Cannot find element: ' + el //) //return document.createElement('div') //} //return selected //} else { //return el //} // }/* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue toor - mount to normal elements instead.` ) return this // el不能是body或html标签,因为会被覆盖 }const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template // new Vue(options)时,options中没有render方法,就会去看是不是有template属性 if (template) { if (typeof template === 'string') { // -------------------------------------------- template在options对象参数中存在,并且是字符串 if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { // -------------------------------------------- template在options对象参数中存在,是dom结构 template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { // ---------------------------------------------- template在options对象参数中不存在,就寻找el,el存在 template = getOuterHTML(el) // 获取el对应的dom,并且赋值给template } // 其实上面一大段 if (template) 都是在处理 templateif (template) { // 此时的template是经过上面处理过后的template/* istanbul ignore if */ // mark直接滤过 // if (process.env.NODE_ENV !== 'production' && config.performance && mark) { //mark('compile') // }const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) // compileToFunctions // 主要就是将模板 template 编译成 render 函数options.render = render options.staticRenderFns = staticRenderFns // 将render函数挂载到$options上/* istanbul ignore if */ // if (process.env.NODE_ENV !== 'production' && config.performance && mark) { //mark('compile end') //measure(`vue ${this._name} compile`, 'compile', 'compile end') // } } } return mount.call(this, el, hydrating) // 调用上面缓存的 mount 方法 // mount 中会调用mountComponent(this, el, hydrating)// mount = Vue.prototype.$mount = function ( //el?: string | Element, //hydrating?: boolean // ): Component { //el = el && inBrowser ? query(el) : undefined //return mountComponent(this, el, hydrating) // }// 这里执行 mount 方法时,还会再次执行 query(el) 方法,多了一次,只是runtime版本考虑的 }
- src/core/instance/lifecycle.js
src/core/instance/lifecycle.js ---export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { vm.$options.render = createEmptyVNode ... } callHook(vm, 'beforeMount') // ------------------------ beforeMount 生命周期钩子let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { ... } else { updateComponent = () => { vm._update(vm._render(), hydrating) } // updateComponent 的赋值 // 赋值:是在这里赋值的 // 执行:是触发了响应式的 set 方法,调用watcher.update()方法时执行的 }new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher 这里的watcher就是渲染watcher*/) // watcher分为三种 // 1. computed watcher // 2. normal watcher 用户自定义的 watcher - 即 watch 对象中的方法 // 3. render watcher // wathcer的执行顺序是固定的 // computed watcher -> normal watcher -> render watcher // 这样就能保证在渲染时能拿到最新的 computed 和 执行了 watche 中定义的函数hydrating = false// manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted')// ------------------------ mounted 生命周期钩子 } return vm }
- src/core/observer/watcher.js
src/core/observer/watcher.js ---export default class Watcher { constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { // 如果是渲染watcher - renderWatcher vm._watcher = this // 就在组件实例上添加 _watcher 属性,值就是该renderWatcher实例 } vm._watchers.push(this) // 向 _watchers 数组中添加该renderWatcherif (typeof expOrFn === 'function') { this.getter = expOrFn // expOrFn 是函数就赋值给 getter // 1. 如果是renderWatcher的话 this.getter = updateComponent方法 // updateComponent 方法返回 vm._update(vm._render(), hydrating) } else { ... } this.value = https://www.it610.com/article/this.lazy ? undefined : this.get() // lazy属性只有 computed Watcher 才有 // 1. render watcher 就会调用 get() 方法并赋值给 this.value }/** * Evaluate the getter, and re-collect dependencies. */ get () { pushTarget(this) // pushTarget 两个作用 // 1. 向 targetStack 数组中添加该 watcher,这里是render watcher // 2. Dep.target = this 将该render watcher 赋值给 Dep.target,表示正在执行的是渲染watcherlet value const vm = this.vm try { value = this.getter.call(vm, vm) // 调用 getter 函数 // 这里因为是渲染watcher,所以执行的是 updateComponent // updateComponent = vm._update(vm._render(), hydrating)// 注意这里是重点 // 执行 vm._update(vm._render(), hydrating) // 1. 因为在执行 vm._update 方法的过程中,会获取响应式data中的属性,触发get进行依赖收集 // 2. vm._render() 将template转成 vnode // 2. vm._update() 对比后将 vnode 转成真正的 DOM } 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 watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } }
三种 watcher https://juejin.im/post/684490...
大滴滴 https://juejin.im/post/684490...
推荐阅读
- vue-cli|vue-cli 3.x vue.config.js 配置
- 2020-04-07vue中Axios的封装和API接口的管理
- Android事件传递源码分析
- 数据库总结语句
- VueX--VUE核心插件
- Quartz|Quartz 源码解析(四) —— QuartzScheduler和Listener事件监听
- [源码解析]|[源码解析] NVIDIA HugeCTR,GPU版本参数服务器---(3)
- ffmpeg源码分析01(结构体)
- Java程序员阅读源码的小技巧,原来大牛都是这样读的,赶紧看看!
- vue组件中为何data必须是一个函数()