准备工作
-
下载
vue
源码,可以先将vue
项目fork
到自己的github
仓库,然后在clone
自己仓库的vue
,这样在解读源码的时候可以随时添加注释,并将注释提交到自己的仓库。 -
源码代码主要结构说明:
-
dist
:打包生成的文件 -
examples
:实例代码目录 -
src
:源码文件目录-
compiler
:编译器相关代码,把template
模板转化成render
函数 -
core
:核心代码-
components
:定义vue
自带的keep-alive
组件 -
global-api
:定义vue
中的静态方法,包括mixin
、extend
、use
等 -
instance
:创建vue
实例成员,包括构造函数、初始化和生命周期函数。 -
observer
:响应式实现 -
util
:工具方法 -
vdom
:虚拟DOM
实现,重写了Snabbdom
,增加了组件的机制。
-
-
platforms
:平台相关处理-
web
:web
平台 -
weex
:基于vue
的移动端框架
-
-
server
:服务器端渲染 -
sfc
:单文件组件,将单文件组件转换成js
模块
-
-
-
Vue2.x
使用了Flow
来进行代码静态类型检查。 -
Vue
使用rollup
进行打包,相对于webpack
来说,rollup
打包不会生成冗余代码。rollup
命令行参数说明:-
-w
:开启监视模式 -
-c
:指定配置文件 -
--sourcemap
:开启sourceMap
功能,之后能在浏览器中的源码中看到源码对应的src
文件夹 -
--environment
:指定运行环境参数,TARGET
指定打包生成的版本。"rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
-
-
Vue
构建版本之间的差别
术语说明:
完整版(Full): 同时包含编译器和运行时的版本
编译器: 将模板字符串编译成javascript
渲染函数的代码
运行时(Runtime-only): 用来创建Vue
实例、渲染并处理虚拟DOM
等的代码。基本上就是除去编译器的其它一切。运行时版本体积小,运行效率更高。
UMD: 可以通过<script>
标签直接用在浏览器中。
CommonJS:CommonJS
版本用来配合老的打包工具比如Browserify
或webpack 1
。这些打包工具的默认文件(pkg.main)
是只包含运行时的CommonJS
版本
ES Module: 会提供两个ES Modules (ESM)
构建文件:- 为打包工具提供的
ESM
:为诸如webpack 2
或Rollup
提供的现代打包工具。ESM
格式被设计为可以被静态分析(编译时解析模块依赖),所以打包工具可以利用这一点来进行tree-shaking
并将用不到的代码排除出最终的包。为这些打包工具提供的默认文件pkg.module
是只有运行时的ES Module
构建(vue.runtime.esm.js)
。 - 为浏览器提供的
ESM (2.6+)
:用于在现代浏览器中通过<script type="module">
直接导入。
- 为打包工具提供的
-
寻找入口文件(以
dev
命令为例)-
根据
package.json
中的命令行找到配置文件 -
找到配置文件中最后的导出语句,然后向前解析找到最后导出的内容
// 判断环境变量是否有 TARGET // 如果有的话 使用 genConfig() 生成 rollup 配置文件 if (process.env.TARGET) { // 如果有 TARGET 则打包生成指定的目标版本 module.exports = genConfig(process.env.TARGET) } else { // 如果没有则打包生成所有版本 exports.getBuild = genConfig exports.getAllBuilds = () => Object.keys(builds).map(genConfig) }
这里关键是是
genConfig
方法,查看方法,定位到builds
,builds
存储了不同TARGET
对应的配置。const builds = { // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify 'web-runtime-cjs-dev': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.common.dev.js'), format: 'cjs', env: 'development', banner }, 'web-runtime-cjs-prod': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.common.prod.js'), format: 'cjs', env: 'production', banner }, }
resolve
是用来解析模块路径中可能存在的alias
别名,并返回一个绝对路径。const resolve = p => { // 根据路径中的前半部分去alias中找别名 const base = p.split('/')[0] if (aliases[base]) { return path.resolve(aliases[base], p.slice(base.length + 1)) } else { return path.resolve(__dirname, '../', p) } }
alias
别名配置module.exports = { vue: resolve('src/platforms/web/entry-runtime-with-compiler'), compiler: resolve('src/compiler'), core: resolve('src/core'), shared: resolve('src/shared'), web: resolve('src/platforms/web'), weex: resolve('src/platforms/weex'), server: resolve('src/server'), sfc: resolve('src/sfc') }
-
根据配置找到入口文件,从入口文件开始解读。
-
-
按照上面的方法找到入口文件
entry-runtime-with-compiler.js
,通过阅读入口文件了解到该文件的主要功能是重写Vue
的$mount
方法,用来渲染DOM
。通过源码的阅读可以知道以下几处注意事项:-
Vue
实例创建时传入的el
不能是body
或html
// el 不能是 body 或者 html if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this }
-
创建
Vue
实例时如果同时传入了template
和render
,则会忽略template
-
通过调试确定
$mount
是在什么时候使用的。编译时开启sourceMap
,直接在浏览器中定位到入口文件,并打下断点,刷新浏览器,运行到断点处,可以看到右侧Call Stack
调用栈中当前正在执行Vue.$mount
,下面一行则是调用Vue.$mount
的运行环境,即在Vue.init
中,一直向下可以追溯到Vue
实例的创建的执行环境。在调用栈中可以查看函数的调用过程。
-
-
根据导入的
Vue
查看runtime/index.js
-
为
vue.config
定义一些属性。// 判断是否是关键属性(表单元素的 input/checked/selected/muted) // 如果是这些属性,设置el.props属性(属性不设置到标签上) Vue.config.mustUseProp = mustUseProp Vue.config.isReservedTag = isReservedTag Vue.config.isReservedAttr = isReservedAttr Vue.config.getTagNamespace = getTagNamespace Vue.config.isUnknownElement = isUnknownElement
-
定义平台环境上的指令
(v-show、v-modal)
和组件(Transition,TransitionGroup)
// install platform runtime directives & components extend(Vue.options.directives, platformDirectives) extend(Vue.options.components, platformComponents)
-
定义
patch
方法Vue.prototype.__patch__ = inBrowser ? patch : noop
-
定义原型上的
$mount
方法// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) // 渲染组件 }
-
执行
devtools
的init
钩子,并对运行环境的不足做出提示。if (inBrowser) { setTimeout(() => { if (config.devtools) { if (devtools) { devtools.emit('init', Vue) } else if ( process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test' ) { console[console.info ? 'info' : 'log']( 'Download the Vue Devtools extension for a better development experience:\n' + 'https://github.com/vuejs/vue-devtools' ) } } if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test' && config.productionTip !== false && typeof console !== 'undefined' ) { console[console.info ? 'info' : 'log']( `You are running Vue in development mode.\n` + `Make sure to turn on production mode when deploying for production.\n` + `See more tips at https://vuejs.org/guide/deployment.html` ) } }, 0) }
-
-
Vue
的初始化:通过runtime/index.js
中依赖的追溯,找到core/index.js
,这个文件中定义了Vue
的初始化的过程。-
初始化
Vue
的静态成员。initGlobalAPI(Vue)
-
初始化
Vue.config
对象// config const configDef = {} configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } // 初始化 Vue.config 对象 Object.defineProperty(Vue, 'config', configDef)
-
暴露
util
方法,但不将其作为Vue API
的一部分,尽量不要依赖他们// exposed util methods. // NOTE: these are not considered part of the public API - avoid relying on // them unless you are aware of the risk. // 这些工具方法不视作全局API的一部分,除非你已经意识到某些风险,否则不要去依赖他们 Vue.util = { warn, extend, mergeOptions, defineReactive }
-
定义静态
set
、del
和nextTick
方法// 静态方法 set/delete/nextTick Vue.set = set Vue.delete = del Vue.nextTick = nextTick
set
为对象设置为响应式的属性,实现原理解析:function set (target: Array<any> | Object, key: any, val: any): any {}
-
确保
target
是对象或数组if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) }
-
target
是数组时,确保key
是有效索引,并扩展数组长度,使数组包含key
索引,通过splice
方法设置值。这里的splice
方法是经过处理的方法,在方法内部会调用dep.notify()
发送通知。// 判断 target 是否是数组,key 是否是合法的索引 if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) // 扩展数组长度 // 通过 splice 对key位置的元素进行替换 // splice 在 array.js 进行了响应化的处理 target.splice(key, 1, val) return val }
-
key
是target
对象中的属性,直接赋值。// 如果 key 在对象中已经存在直接赋值 if (key in target && !(key in Object.prototype)) { target[key] = val return val }
-
如果
target
是vue
实例或$data
,直接返回// 获取 target 中的 observer 对象 const ob = (target: any).__ob__ // 如果 target 是 vue 实例或者 $data 直接返回 if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val }
-
target
不是响应式的对象,直接赋值// 如果 ob 不存在,target 不是响应式对象直接赋值 if (!ob) { target[key] = val return val }
-
target
是响应式的对象,将key
设置为响应式的属性,并发送通知,返回val
。// 把 key 设置为响应式属性 defineReactive(ob.value, key, val) // 发送通知 ob.dep.notify() return val
del
方法删除对象或数组的属性,并发送通知更新视图,对象不能是Vue
实例或Vue
实例的根数据对象。实现和set
方法类似就不在赘述了。nextTick
在DOM
更新完之后调用传入的回调函数,代码解析:function nextTick (cb?: Function, ctx?: Object) {}
- 将
cb
做异步处理,并保存到回调数组中callbacks.push(() => { if (cb) { try { // 调用 cb() cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } })
- 如果不是
pending
状态(即正在执行回调),这挨个调用回调数组中的函数。if (!pending) { pending = true // 调用 timerFunc() }
- 回调数组中的函数的调用是放在
Promise、MutationObserver、setImmediate或setTimeout
中执行的,首先会优先考虑使用微任务执行,如果不能使用则降级到宏任务中执行。保证在DOM
更新完成之后执行。浏览器视图更新和DOM
树上的数据更新是两个步骤,DOM
树的数据更新是同步进行了,而视图更新则是异步的。if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // In problematic UIWebViews, Promise.then doesn't completely break, but // it can get stuck in a weird state where callbacks are pushed into the // microtask queue but the queue isn't being flushed, until the browser // needs to do some other work, e.g. handle a timer. Therefore we can // "force" the microtask queue to be flushed by adding an empty timer. if (isIOS) setTimeout(noop) } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // Use MutationObserver where native Promise is not available, // e.g. PhantomJS, iOS7, Android 4.4 // (#6466 MutationObserver is unreliable in IE11) let counter = 1 // MutationObserver用于监听DOM树的变化,并执行回调,也是微任务。 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // Fallback to setImmediate. // Technically it leverages the (macro) task queue, // but it is still a better choice than setTimeout. timerFunc = () => { setImmediate(flushCallbacks) //setimemediate性能比setTimeout好,会在setTimeout之前执行。 } } else { // Fallback to setTimeout. timerFunc = () => { setTimeout(flushCallbacks, 0) } }
- 如果没有传入
cb
,则返回一个Promise
,该Promise
将在回调数组中的函数执行完成之后变为resolve
状态。// $flow-disable-line if (!cb && typeof Promise !== 'undefined') { // 返回 promise 对象 return new Promise(resolve => { _resolve = resolve }) }
-
-
定义
observable
方法// 2.6 explicit observable API // 让一个对象可响应 Vue.observable = <T>(obj: T): T => { observe(obj) return obj }
-
初始化
options
对象,定义并初始化其中的components
、directives
和filters
属性// 初始化 Vue.options 对象,并给其扩展 // components/directives/filters Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) })
-
保存
Vue
构造函数,// this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. Vue.options._base = Vue
-
设置
Vue
的keep-alive
组件// 设置 keep-alive 组件 extend(Vue.options.components, builtInComponents)
-
注册
Vue.use()
Vue.use
是用来注册第三方插件的,第三方插件要求必须是对象或函数,如果是对象必须具有install
方法。- 首先需要初始化保存插件的列表,并判断是否已经注册该插件。
// 保存安装的组件 const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) if (installedPlugins.indexOf(plugin) > -1) { return this }
- 执行插件提供的
install
方法或插件函数。 这里需要对参数进行处理,install
执行时第一个参数是Vue
的构造函数。// additional parameters // 如果有多个参数,把数组中的第一个元素(plugin)去除,第一个参数是plugin,剩余的参数是plugin执行时需要传入的参数。 const args = toArray(arguments, 1) // 把this(Vue)插入第一个元素的位置,这里的this也是Vue的构造函数 args.unshift(this) if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args) // install 执行时第一个参数是Vue的构造函数。 } else if (typeof plugin === 'function') { plugin.apply(null, args) } installedPlugins.push(plugin) return this
- 首先需要初始化保存插件的列表,并判断是否已经注册该插件。
-
注册
Vue.mixin()
-
注册
Vue.extend()
Vue.extend()
返回一个Vue
的子类,接受一个包含组件选项对象的参数。代码解析:-
尝试从缓存中加载组件的构造函数
// Vue 构造函数 const Super = this const SuperId = Super.cid // 从缓存中加载组件的构造函数 const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) if (cachedCtors[SuperId]) { return cachedCtors[SuperId] } const name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== 'production' && name) { // 如果是开发环境验证组件的名称 validateComponentName(name) }
-
定义子类的构造函数,设置原型继承
Vue
。const Sub = function VueComponent (options) { // 调用 _init() 初始化 this._init(options) } // 原型继承自 Vue Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub Sub.cid = cid++
-
合并
options
选项// 合并 options Sub.options = mergeOptions( Super.options, extendOptions ) Sub['super'] = Super
-
将
options
中的props
和computed
转化为响应式。if (Sub.options.props) { initProps(Sub) } if (Sub.options.computed) { initComputed(Sub) }
-
复制
Vue
中的extend
、mixin
、use
、component
、directive
、filter
属性到子类// allow further extension/mixin/plugin usage Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use // create asset registers, so extended classes // can have their private assets too. ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type] })
-
保存子类的构造函数
if (name) { Sub.options.components[name] = Sub }
-
保存配置选项副本,方便后续配置修改之后检查
// keep a reference to the super options at extension time. // later at instantiation we can check if Super's options have // been updated. Sub.superOptions = Super.options Sub.extendOptions = extendOptions Sub.sealedOptions = extend({}, Sub.options)
-
将组件的构造函数缓存到
options._Ctor
中// cache constructor // 把组件的构造函数缓存到 options._Ctor cachedCtors[SuperId] = Sub return Sub
-
-
注册
Vue.directive()
、Vue.component()
、Vue.filter()
注册或返回全局的指令、组件和过滤器,这三个方法的功能和实现是类似的,所以放在一起定义。这里主要两部分- 如果只传入了
id
,则返回对应的指令、组件或过滤器 - 如果传入了第二个参数,则将第二个参数转换成合适的格式保存到
Vue.directives
、Vue.components
或Vue.filters
中,并返回。// 遍历 ASSET_TYPES 数组,为 Vue 定义相应方法 // ASSET_TYPES 包括了directive、 component、filter ASSET_TYPES.forEach(type => { Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void { if (!definition) { return this.options[type + 's'][id] } else { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && type === 'component') { validateComponentName(id) } // Vue.component('comp', { template: '' }) if (type === 'component' && isPlainObject(definition)) { definition.name = definition.name || id // 把组件配置转换为组件的构造函数 this.options._base = Vue definition = this.options._base.extend(definition) } if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition } } // 全局注册,存储资源并赋值 // this.options['components']['comp'] = definition this.options[type + 's'][id] = definition return definition } } })
// 注册 Vue.use() 用来注册插件 initUse(Vue) // 注册 Vue.mixin() 实现混入 initMixin(Vue) // 注册 Vue.extend() 基于传入的options返回一个组件的构造函数 initExtend(Vue) // 注册 Vue.directive()、 Vue.component()、Vue.filter() initAssetRegisters(Vue)
- 如果只传入了
-
-
定义服务端渲染的属性
// 服务端渲染的处理 Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering }) Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext } }) // expose FunctionalRenderContext for ssr runtime helper installation Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext })
-
-
core/index.js
依赖instance/index.js
,在此处定义Vue
的构造函数,并定义定义Vue
的实例成员。-
定义构造函数,在构造函数中调用
_init()
方法。 这里定义构造函数而不是class
是为了方便下面通过混入定义实例成员。// 此处不用 class 的原因是因为方便后续给 Vue 实例混入实例成员 function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } // 调用 _init() 方法 this._init(options) }
-
调用
initMixin(Vue)
定义_init
方法。-
合并选项参数,将传入的
options
和Vue
构造函数的options
(即runtime/index.js
中定义的options
)合并。 -
调用
beforeCreate
和created
生命周期函数,并进行一系列初始化。vm._self = vm // vm 的生命周期相关变量初始化 // $children/$parent/$root/$refs initLifecycle(vm) // vm 的事件监听初始化, 父组件绑定在当前组件上的事件 initEvents(vm) // vm 的编译render初始化 // $slots/$scopedSlots/_c/$createElement/$attrs/$listeners initRender(vm) // beforeCreate 生命钩子的回调 callHook(vm, 'beforeCreate') // 把 inject 的成员注入到 vm 上 initInjections(vm) // resolve injections before data/props // 初始化 vm 的 _props/methods/_data/computed/watch initState(vm) // 初始化 provide initProvide(vm) // resolve provide after data/props // created 生命钩子的回调 callHook(vm, 'created')
-
调用
$mount
挂载元素。
-
-
调用
stateMixin(Vue)
定义state
相关的实例成员,包括$data
、$props
、$set
、$delete
和$watch
。 -
调用
eventsMixin(Vue)
初始化事件相关的方法,包括$on
、$once
、$off
和$emit
。 -
调用
lifecycleMixin(Vue)
初始化生命周期相关的方法,包括_update
、$forceUpdate
和$destroy
。 -
调用
renderMixin(Vue)
定义render
相关的方法,包括$nextTick
和_render
。
-
数据响应式原理
-
入口:在调用
_init
方法进行初始化的时候,会调用initState
方法进行state
相关的初始化,在其中对data
进行初始化时,主要是调用observe
方法将data
中的属性转换为响应式的属性,observe
方法的核心这是创建一个Observer
类。 -
Observer
类: 将对象的所有属性转化为setter/getter
,并且收集依赖,派发更新(即发送消息,通知更新)。- 初始化依赖对象,将实例挂载到
__ob__
属性上 - 判断是否是数组,如果是数组,则对数组中的每个元素调用
observe
方法,将其转化为响应式对象。 - 如果是对象,则遍历对象属性,调用
defineReactive
方法将属性转化为setter/getter
。
export class Observer { // 观测对象 value: any; // 依赖对象 dep: Dep; // 实例计数器 vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() // 初始化实例的 vmCount 为0 this.vmCount = 0 // 将实例挂载到观察对象的 __ob__ 属性 def(value, '__ob__', this) // 数组的响应式处理 if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } // 为数组中的每一个对象创建一个 observer 实例 this.observeArray(value) } else { // 遍历对象中的每一个属性,转换成 setter/getter 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]) } } }
- 初始化依赖对象,将实例挂载到
-
defineReactive
:为对象定义一个响应式的属性,在get
中收集依赖,在set
中派发更新。export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { // 创建依赖对象实例 const dep = new Dep() // 获取 obj 的属性描述符对象 const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // 提供预定义的存取器函数 // 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] } // 判断是否递归观察子对象,并将子对象属性都转换成 getter/setter,返回子观察对象 let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { // 如果预定义的 getter 存在则 value 等于getter 调用的返回值 // 否则直接赋予属性值 const value = getter ? getter.call(obj) : val // 如果存在当前依赖目标,即 watcher 对象,则建立依赖 if (Dep.target) { dep.depend() // 如果子观察目标存在,建立子对象的依赖关系 if (childOb) { childOb.dep.depend() // 如果属性是数组,则特殊处理收集数组对象依赖 if (Array.isArray(value)) { dependArray(value) } } } // 返回属性值 return value }, set: function reactiveSetter (newVal) { // 如果预定义的 getter 存在则 value 等于getter 调用的返回值 // 否则直接赋予属性值 const value = getter ? getter.call(obj) : val // 如果新值等于旧值或者新值旧值为NaN则不执行 /* 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() } // 如果没有 setter 直接返回 // #7981: for accessor properties without setter if (getter && !setter) return // 如果预定义setter存在则调用,否则直接更新新值 if (setter) { setter.call(obj, newVal) } else { val = newVal } // 如果新值是对象,观察子对象并返回 子的 observer 对象 childOb = !shallow && observe(newVal) // 派发更新(发布更改通知) dep.notify() } }) }
-
Dep.target
保存的是观察者(Watcher
),是在创建Watcher
实例时保存的,而Watcher
实例则是发生在lifecycle.js
下的mountComponent
被调用时创建的。 - 依赖收集就是将
Watcher
实例watcher
保存到Dep
实例dep
的subs
中,并在此过程中将dep
保存到watcher
的depIds
中。依赖收集调用过程:Dep.depend --> Watcher.addDep(dep) --> dep.addSub(watcher)
//Dep // 将观察对象和 watcher 建立依赖 depend () { if (Dep.target) { // 如果 target 存在,把 dep 对象添加到 watcher 的依赖中 Dep.target.addDep(this) } } // 添加新的订阅者 watcher 对象 addSub (sub: Watcher) { this.subs.push(sub) } //Watcher addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } }
-
-
数组响应式原理
-
将改变数组的方法进行二次封装+,然后重新赋值给数组。
const arrayProto = Array.prototype // 使用数组的原型创建一个新的对象 export const arrayMethods = Object.create(arrayProto) // 修改数组元素的方法 const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method // 保存数组原方法 const original = arrayProto[method] // 调用 Object.defineProperty() 重新定义修改数组的方法 def(arrayMethods, method, function mutator (...args) { // 执行数组的原始方法 const result = original.apply(this, args) // 获取数组对象的 ob 对象 const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } // 对插入的新元素,重新遍历数组元素设置为响应式数据 if (inserted) ob.observeArray(inserted) // notify change // 调用了修改数组的方法,调用数组的ob对象发送通知 ob.dep.notify() return result }) })
- 首先调用数组原始的方法
- 记录新增的元素,并将其中的对象元素转化为响应式的对象
- 发送通知,更新视图。
-
为数组中的每个对象元素创建
Observer
实例。即将数组中的对象转化成响应式的。observeArray (items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } }
-
-
Watcher
类-
Watcher
分三种:Computed Watcher
(计算属性的Watcher
)、用户Watcher
(即Watcher
侦听器和$watcher
)、Render Watcher
,前两种都是在initState
时初始化的。 -
首次渲染时
Watcher
的执行过程。首次渲染时会调用lifecycle.js
中的mountComponent
方法,方法内容会创建Watcher
的实例。constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !!options.deep this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync this.before = options.before } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { // expOrFn 是字符串的时候,例如 watch: { 'person.name': function... } // parsePath('person.name') 返回一个函数获取 person.name 的值 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 = this.lazy ? undefined : this.get() }
创建
Watcher
实例时主要完成了下面几件事:- 记录当前的
Watcher
实例。 - 初始化变量,并记录选项参数
- 然后调用
get
方法,在get
方法中首先会去设置Dep.target
的值,然后调用getter
方法获取value
,最后清理Dep.target
。/** * Evaluate the getter, and re-collect dependencies. */ get () { // 设置Dep.target pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm)// 如果是renderWatcher,这里将调用updateComponent挂载虚拟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
的执行过程:-
数据更新时会调用绑定的
Dep
实例的notify
方法,挨个调用watcher
的update
方法。// 发布通知 notify () { // stabilize the subscriber list first const 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 // order // 保证Watcher按照新建的顺序执行 subs.sort((a, b) => a.id - b.id) } // 调用每个订阅者的update方法实现更新 for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }
-
在
update
中将调用queueWatcher
将watcher
加入到队列,并调用flushSchedulerQueue
方法处理watcher
队列。 -
在
flushSchedulerQueue
方法里将挨个调用队列里watcher
的before
以及run
方法,之后初始化queue
调度的状态,然后去触发组件的activated
钩子和updated
钩子。/** * Flush both queues and run the watchers. */ function flushSchedulerQueue () { currentFlushTimestamp = getNow() flushing = true let watcher, id // Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. queue.sort((a, b) => a.id - b.id) // do not cache length because more watchers might be pushed // as we run existing watchers // queue在执行过程中可能还会发生变化 for (index = 0; index < queue.length; index++) { watcher = queue[index] // 执行传入的before方法 if (watcher.before) { watcher.before() } id = watcher.id has[id] = null watcher.run() // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? `in watcher with expression "${watcher.expression}"` : `in a component render function.` ), watcher.vm ) break } } } // keep copies of post queues before resetting state const activatedQueue = activatedChildren.slice() const updatedQueue = queue.slice() resetSchedulerState() // call component updated and activated hooks callActivatedHooks(activatedQueue) callUpdatedHooks(updatedQueue) // devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush') } }
-
before
方法是创建Watcher
实例是传入的,RenderWatcher
在before
中将触发组件的beforeUpdate
钩子。// we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */)
-
run
方法则是调用get
方法获取值,更新value
的值,然后调用传入的cb
更新数据和视图。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 value const oldValue = this.value this.value = value if (this.user) { // 如果是用户Watcher 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) } } } }
-
-
-
-
$watcher
- 三种类型的
Watcher
的创建过程:-
Computed Watcher
:计算属性的Watcher
实例是在创建Vue
实例执行_init
方法时创建的,在初始化state
时,会判断选项参数中是否存在computed
属性,如果存在则执行initComputed
方法为computed
中的属性创建Watcher
实例,并将属性转化为响应式的数据,添加到Vue
实例中。const computedWatcherOptions = { lazy: true } function initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create(null) // computed properties are just getters during SSR const isSSR = isServerRendering() for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } } } } export function defineComputed ( target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : createGetterInvoker(userDef.get) : noop sharedPropertyDefinition.set = userDef.set || noop } if (process.env.NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, this ) } } Object.defineProperty(target, key, sharedPropertyDefinition) }
- 创建
Computed Watcher
时选项参数中的lazy
为true
,创建时并不会直接调用Watcher
的get
方法获取值。因为计算属性的Watcher
的get
是在模板中调用的。
- 创建
-
User Watcher
:用户Watcher
是和计算属性的Watcher
一起创建的,都是在创建Vue
实例执行_init
方法时创建的,在初始化state
时,如果选项参数中的watch
属性存在且不为空对象,则执行initWatch
为watch
属性对象中的每一项创建Watcher
实例。function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } } } function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { // methods中对应的函数 handler = vm[handler] } return vm.$watch(expOrFn, handler, options) }
-
watch
中属性对应的值如果是数组,则会为该属性创建多个Watcher
实例。 -
watch
中属性对应的值是一个对象时,对象中handler指定数据改变时的回调函数,也可以指定deep
和immediate
等参数。如果指定了deep
选项,则会递归将对象中的属性都转化为响应式的对象,如果指定了immediate
,则会立即执行一次handler
函数。
-
-
Render Watcher
:在web
环境下,Render Watcher
是在$mount
中调用mountComponent
时创建的,mountComponent
是在lifecycle.js
中定义的。Render Watcher
在绑定到视图的数据发生改变时会触发视图更新,即updateComponent
,并触发beforeUpdate
的组件钩子。if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */)
-
三种
Watcher
的创建顺序:Computed Watcher --> User Watcher --> RenderWatcher
,执行顺序和创建顺序一致。 -
User Watcher
是最终是通过调用$watcher
实例方法实现的,所以方法内部会标记Watcher
为User Watcher
,然后创建Watcher
实例,如果用户传入了immediate
且为true
,则会立即执行一次数据变化之后的回调函数。最后返回一个取消监听的方法。Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { // 获取 Vue 实例 this const vm: Component = this if (isPlainObject(cb)) { // 判断如果 cb 是对象执行 createWatcher return createWatcher(vm, expOrFn, cb, options) } options = options || {} // 标记为用户 watcher options.user = true // 创建用户 watcher 对象 const watcher = new Watcher(vm, expOrFn, cb, options) // 判断 immediate 如果为 true if (options.immediate) { // 立即执行一次 cb 回调,并且把当前值传入 try { cb.call(vm, watcher.value) } catch (error) { handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`) } } // 返回取消监听的方法 return function unwatchFn () { watcher.teardown() } }
-
- 三种类型的