0.前情提要
_init函数中调用initState()
// core/init.js
Vue.prototype._init = function(){
// ...
initState(vm)
// ...
}
// core/instance/state.js
export function initState (vm: Component) {
vm._watchers = []
// 这个是已经合并后的options
const opts = vm.$options
if (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)
}
}
分别初始化了
options中的五个属性,增加了_watchers的实例属性
propsmethodsdatacomputedwatch下面依次看一下源码中是如何对这五个属性初始化的
1.props
1.1 initProps主要干了这么几件事
- 在实例上挂载了
_props、$options._propKeys - 通过
propsData和props声明定义的规则,过滤出真正传入的值 - 把
props定义的属性通过defineReactive函数进行响应式,且添加到_props - 把
_props上的属性代理到vm实例上(不允许覆盖本身的属性)
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// $options._propKeys属性在这里被增加
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
if (!isRoot) {
toggleObserving(false)
}
// 遍历props属性
for (const key in propsOptions) {
keys.push(key)
// 验证传的propsData和声明的props规则,过滤后确定最后拿到的值
const value = validateProp(key, propsOptions, propsData, vm)
if (process.env.NODE_ENV !== 'production') {
const hyphenatedKey = hyphenate(key)
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
// 第四个参数是自定义setter函数,props原则上不允许在子组件中直接改变,在开发环境下会报警告
defineReactive(props, key, value, () => {
if (!isRoot && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
// 给vm._props添加响应式属性
defineReactive(props, key, value)
}
// 如果vm中本身没有这个属性才代理
if (!(key in vm)) {
// 可以通过vue实例直接访问到props上了
proxy(vm, `_props`, key)
}
}
toggleObserving(true)
}
export function toggleObserving (value: boolean) {
shouldObserve = value
}
1.2 validateProp
通过之前配置的props的一些规则,和传入的propsData,返回真正传给实例的值,起到一个过滤转换的功能,主要是对以下几种特殊情况做判断
- 传了空字符串
- 传的值等于属性名本身
- 未传值,需要去获取默认值
export function validateProp (key: string,propOptions: Object,propsData: Object,vm?: Component): any {
const prop = propOptions[key]
// propsData是否传了改prop
const absent = !hasOwn(propsData, key)
// 获取propsData值
let value = propsData[key]
// 该类型是否是Boolean类型
const booleanIndex = getTypeIndex(Boolean, prop.type)
if (booleanIndex > -1) {
// 没传propsData且也没有默认的情况下,值是false
if (absent && !hasOwn(prop, 'default')) {
value = false
// 当值传了,但是是空字符串或则是这个属性名本身的驼峰命名(经常通过这样简写的方式来表示true)
//那么就要考虑一下props的定义里面有没有别的接受类型了
} else if (value === '' || value === hyphenate(key)) {
// 不是string类型,或则boolean类型在数组前面的时候,那么boolean的优先级更高,才可以置true
const stringIndex = getTypeIndex(String, prop.type)
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true
}
}
}
// 没有传值
if (value === undefined) {
// 获取到默认值,当然也可能没有默认值,那就是undefined了
value = getPropDefaultValue(vm, prop, key)
// 对默认值进行一个观测,这个observe算是重头戏,放到后面讲
const prevShouldObserve = shouldObserve
toggleObserving(true)
observe(value)
toggleObserving(prevShouldObserve)
}
if (
process.env.NODE_ENV !== 'production' &&
// skip validation for weex recycle-list child component props
!(__WEEX__ && isObject(value) && ('@binding' in value))
) {
assertProp(prop, key, value, vm, absent)
}
return value
}
1.3 getPropDefaultValue
根据props的声明规则去获取实际应该拿到的默认值
注意:默认值是对象的情况,需要定义工厂函数来返回
// 获取默认值
function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
// 没有声明默认值
if (!hasOwn(prop, 'default')) {
return undefined
}
const def = prop.default
// 对象或数组默认值必须从一个工厂函数获取
if (process.env.NODE_ENV !== 'production' && isObject(def)) {
warn(
'Invalid default value for prop "' + key + '": ' +
'Props with type Object/Array must use a factory function ' +
'to return the default value.',
vm
)
}
// 这里还没看懂,应该是和渲染更新的顺序有关
if (vm && vm.$options.propsData &&
vm.$options.propsData[key] === undefined &&
vm._props[key] !== undefined
) {
return vm._props[key]
}
// 入股默认值是工厂函数的时候,判断一下类型本省不是函数,那么就执行这个工厂函数把其返回值当做默认值
return typeof def === 'function' && getType(prop.type) !== 'Function'
? def.call(vm)
: def
}
1.4 assertProp验证prop属性是否有效
无效也不会怎么样,还是会传值,只不过会开发环境下报一些警告,可跳过
function assertProp (prop: PropOptions,name: string,value: any,vm: ?Component,absent: boolean) {
// 要求传,却没传
if (prop.required && absent) {
warn(
'Missing required prop: "' + name + '"',
vm
)
return
}
if (value == null && !prop.required) {
return
}
let type = prop.type
let valid = !type || type === true
const expectedTypes = []
if (type) {
// 强制转换成数组
if (!Array.isArray(type)) {
type = [type]
}
for (let i = 0; i < type.length && !valid; i++) {
const assertedType = assertType(value, type[i], vm)
// 返回期待的类型,和现在传入的值是否符合有效
expectedTypes.push(assertedType.expectedType || '')
valid = assertedType.valid
}
}
// 只有有规定过一种类型以上
const haveExpectedTypes = expectedTypes.some(t => t)
// 且最终传入的值和规定的一种都不符合
if (!valid && haveExpectedTypes) {
// 发出警告
warn(
getInvalidTypeMessage(name, value, expectedTypes),
vm
)
return
}
// 自定义的验证器
const validator = prop.validator
if (validator) {
if (!validator(value)) {
warn(
'Invalid prop: custom validator check failed for prop "' + name + '".',
vm
)
}
}
}
1.5 defineReactive
对于props声明的每一个属性进项响应式处理,对于对象需要递归进行响应式处理,这里的递归方式很特别,通过传入的shallow参数来决定是否要深度的监听,如果是,那么就observe继续观测这个对象,观测过程中再调用defineReavtive,来响应式他这个对象的每一个属性。
- 每一个属性就生成一个
Dep实例,来收集订阅 - 这个属性的定义
configurable必须是可配置的 - 如果原来就有
setter,重新刚给他设置一遍(个人理解,待斟酌) - 通过
Object.definePrototype来重写setter,getter钩子,实现发布订阅模式
export function defineReactive (obj: Object,key: string,val: any,customSetter?: ?Function,shallow?: boolean) {
// 每一个属性就生成一个dep实例,来收集订阅
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key
// 描述符无法改变
if (property && property.configurable === false) {
return
}
// 原先setter函数存在,且现在也没有自定义的setter,那么value重新取一遍
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// shallow参数表示是否深度监听
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 模板解析的时候会赋值,由于js单线程解析,所以全局只需要判断Dep.target就行了
if (Dep.target) {
// 会把push(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
// 当数据没有发生变化就不需要发布通知了
// 后面这个逻辑我怀疑是自定义的getter重写了之后让他的值发生变化,导致死循环渲染,所以干脆不渲染了
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
// 原来这个自定义的sette只在开发环境起作用啊
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// 没看懂,源码好像为了解决一个issue加的
if (getter && !setter) return
// 更改设置的值
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 新值有可能是对象或者数组,还是要重新观察一下
childOb = !shallow && observe(newVal)
// 通知订阅者
dep.notify()
}
})
}
// 如果属性值是一个数组,那么给数组的每一个元素都去加订阅
// 代码较短大家自己看一下
function dependArray (value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
// 递归调用一下
if (Array.isArray(e)) {
dependArray(e)
}
}
}
1.6 Observe
- 对于一个属性是对象才会观测,观测结果是给属性值增加一个
__ob__属性 - 如果是数组的话那么对每个元素循环观测就行了,但还是要符合条件(1)
- 数组直接下标修改不会触发响应,源码重写了数组的原型方法,按照是否可以挂载
__proto__,分别通过继承和definePrototype挂载重写的方法
export function observe (value: any, asRootData: ?boolean): Observer | void {
// ...
// 该属性下存在__ob__属性就返回,否则创建一个
if (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)
}
// ...
return ob
}
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()
this.vmCount = 0
// 挂载__ob__属性
def(value, '__ob__', this)
// 改属性值是一个数组
if (Array.isArray(value)) {
if (hasProto) {
// 原型链继承方式
protoAugment(value, arrayMethods)
} else {
// definePrototype声明方式
copyAugment(value, arrayMethods, arrayKeys)
}
// 数组每一个元素都需要要观察
this.observeArray(value)
} else {
this.walk(value)
}
}
// 遍历每个属性都给他加响应式处理
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
// 至此defineReactive和observe两个函数形成了递归调用的关系
defineReactive(obj, keys[i])
}
}
// 数组元素循环观测
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
// 如果这个值是简单数据的话,那么就不会调用defineReactive了,也就是说数组里面的东西不会再响应式了
observe(items[i])
}
}
}
1.7 Dep
addSub:添加订阅remove:移除订阅depend:将全局遍历Dep.target添加订阅notify:通知订阅者该更新视图了
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 () {
// 为了下面排序会打乱,先拿一份拷贝
const subs = this.subs.slice()
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()
}
}
}
简单小结
- 对
props每个属性做defineReactive defineReactive为每一个属性维护一个Dep实例,且重写这个属性的钩子,并对这个属性值做观察observeobserve只会观察对象或者数组,而不会观察简单数据,观察的时候给属性值增加一个属性__ob__ = new Observe()Observe实例会重写数组的一些方法来使得数组变化可以响应,对属性值的每一个属性递归调用(1),如果是数组那再对每一个元素循环调用(3)而不是(1)因为数组里的元素可能还是数组
2. methods
2.1 initMethods
- 开发环境下的一些警告
- 必须是函数
- 不能和props同名
- 和vue实例中已经定义过的api同名
- 生产环境下将不是函数的情况转换为空函数
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
if (typeof methods[key] !== 'function') {
warn(
`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
if (props && hasOwn(props, key)) {
warn(
`Method "${key}" has already been defined as a prop.`,
vm
)
}
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
3. data
3.1 initData
- 对函数声明和对象声明两种方式的统一
- 对最终返回数据类型的容错
- 对同名的警告
- 把
_data代理到vm实例上 observe(data)使得其响应式
props是将每一个属性defineReactive而data是直接observe(data, true),直接带来的区别是vm._data.__ob__有而vm._props___ob__没有,后续再看看这里为啥要这么高
function initData (vm: Component) {
let data = vm.$options.data
data = 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 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]
// 和methods同名
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
// 和props同名
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)) {
把_data代理带vue实例上
proxy(vm, `_data`, key)
}
}
// 使得data响应式
observe(data, true /* asRootData */)
}
4. computed
类似于data,他所依赖的响应式数据更新的时候,他也会更新,可以自定义set逻辑
4.1 initComputed
- 给
vm挂载_computedWatchers属性 - 格式化输入(有两种声明方式),获取getter
new Watcher()挂载到_computedWatchersdefineComputed()该函数做主要工作- 开发环境下同名警告
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
// computed 有函数和对象两种声明方式
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) {
// 新建watcher
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
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)
} else if (vm.$options.methods && key in vm.$options.methods) {
warn(`The computed property "${key}" is already defined as a method.`, vm)
}
}
}
}
4.2 defineComputed
computed可以直接声明函数,或者声明对象{get:function(){},set:function(){}}set可以直接赋值给definePrototype中的set
export function defineComputed (target: any,key: string,userDef: Object | Function) {
const shouldCache = !isServerRendering()
// 看起来比较长,其实就是判断是否有自带的set,然后包装一下get
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
)
}
}
// 把computed属性挂载到vm实例上,并定义了getter和setter钩子
Object.defineProperty(target, key, sharedPropertyDefinition)
}
4.3 createComputedGetter、createGetterInvoker
类似于defineReactive函数重写getter和setter,在getter中depend。
提一嘴,这个setter用来v-model的时候挺好用的,特别是如果想直接用vuex里的state来做双向绑定的话
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 暂时没看懂,等看到watcher类的时候再回来看看
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
5 watch
声明方式:[key:string]: (function|Object)|(function|Object)[]
注意属性可以是一个表达式
5.1 initWatch
适应不同声明方式并调用creatWatcher()
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)
}
}
}
5.2 createWatcher
function createWatcher (vm: Component,expOrFn: string | Function,handler: any,options?: Object) {
// 改声明是一个对象的话,那么整个都可以当做是配置
if (isPlainObject(handler)) {
options = handler
// 回调函数是其中的handler属性
handler = handler.handler
}
// handel是字符串,那去vm的实例中找
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
望大佬多指正,未完待续~