Vue 2.6.13 源码解析
Posted 白瑕
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue 2.6.13 源码解析相关的知识,希望对你有一定的参考价值。
文章目录
前言
首篇提到initProvide
和initInjections
中间为何调用initState
, 那么initState
里做了什么?
这篇因为函数调用很多所以篇幅长, 但因为只是一个子模块, 所以函数的复杂性不及上篇.
思路的话, 这些函数的大部都是在处理数据格式问题, 核心基本就是最后的一两次调用.
比如initData
和initProps
的observe
和Observer
判断和处理数据格式问题, 响应式核心依靠defineReactive()
(核心也是defineProperty), initComputed
核心defineProperty()
.
规范了一下结构, 第一篇感觉写的很乱, 这次采用了多级标题, 子标题函数
为父标题函数
的内部调用.
不用自己写例子, Vue源码example
目录下的页面基本能满足本章的输出测试需求, 记得修改引入的vue.min.js
为dist
下的vue.js
, 打包完用LiveServer
打开页面即可.
一、initState
初始化Props
, methods
, data
, computed
, watch
export function initState (vm: Component)
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
// 初始化props
if (opts.methods) initMethods(vm, opts.methods)
// 初始化methods
if (opts.data)
// 如果data存在, 那么初始化data
initData(vm)
else
// data不存在则返回defineReactive()的返回值
observe(vm._data = , true /* asRootData */)
if (opts.computed) initComputed(vm, opts.computed)
// computed存在则初始化computed
if (opts.watch && opts.watch !== nativeWatch)
// watch存在则初始化watch
initWatch(vm, opts.watch)
二、支线
2.1.initProps
function initProps (vm: Component, propsOptions: Object) // vm, vm.$options.props
const propsData = vm.$options.propsData ||
/*
propsData :[
todoList: [
0:
id: 0,
text: '工作'
,
1:
id: 1,
text: '早饭'
]
]
*/
const props = vm._props =
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
if (!isRoot)
toggleObserving(false)
for (const key in propsOptions)
/*
propsOptions:
color: type: null ,
visible: type: null
*/
keys.push(key) // keys内为所有props参数名
const value = validateProp(key, propsOptions, propsData, vm) // 所有遍历的props的新值
/* value: [
0:
id: 1,
text: 'Hello',
,
0:
id: 2,
text: 'World',
] */
if (process.env.NODE_ENV !== 'production')
// 生产模式略
else
// 上面提到如果没有data就直接将data设为defineReactive(), 现在有的话将每个值设置为响应式
defineReactive(props, key, value)
if (!(key in vm)) // 如果指定的属性在指定的对象或其原型链中, in运算符返回true
proxy(vm, `_props`, key) // // 代理key到vm
toggleObserving(true)
2.1.1.initProps—defineReactive
/**
* 在一个对象上定义响应式属性
*/
export function defineReactive (
obj: Object,
key: string, // 当前遍历的props属性名
val: any, // 当前遍历的props值, 不一定最新
customSetter?: ?Function,
shallow?: boolean
)
const dep = new Dep() // 实例化Dep
// Dep接收Observer的变化通知并向订阅它的Watcher分发变化通知
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false)
return
// cater for pre-defined getter/setters
const getter = property && property.get // getter setter这俩在很多时候是undefined, 也可以是undefined
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2)
val = obj[key]
let childOb = !shallow && observe(val) // val每次是data中的一个属性值
Object.defineProperty(obj, key,
enumerable: true,
configurable: true,
get: function reactiveGetter ()
const value = getter ? getter.call(obj) : val
// props情况和computed情况因为有更新值的需求所以有set和get方法, 由sharedPropertyDefinition所设, 参考2.1.2和2.4.1
if (Dep.target)
dep.depend() // 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 setter
if (getter && !setter) return
if (setter)
setter.call(obj, newVal) // 如果有setter就将当前newVal更新到当前key上
else
// 没有setter那就不是props或者computed情况, data之类直接设置当前值为最新值
val = newVal
childOb = !shallow && observe(newVal) // 见2.3.2.1
dep.notify() // 见2.4.1.1
)
2.1.2.initProps—proxy
将props
属性名定义到vm
上并且挨个对应他们自己的set
和get
, sharedPropertyDefinition
全局数组.
const sharedPropertyDefinition =
enumerable: true,
configurable: true,
get: noop,
set: noop
export function proxy (target: Object, sourceKey: string, key: string) // vm, `_props`, 当前props名
sharedPropertyDefinition.get = function proxyGetter ()
return this[sourceKey][key] // sharedPropertyDefinition[_`props`][props名]
sharedPropertyDefinition.set = function proxySetter (val)
this[sourceKey][key] = val // sharedPropertyDefinition[_`props`][props名] = val
Object.defineProperty(target, key, sharedPropertyDefinition)
这种情况下defineReactive
中的property.get
和set
就不是undefined
了.
2.1.3.initProps—dependArray
特殊的 用于收集数组元素依赖的方法.
/**
* Collect dependencies on array elements when the array is touched, since
* we cannot intercept array element access like property getters.
*/
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))
// 如果还有数组就继续重来, 不是就到上一行三个值纳入依赖depend()结束
dependArray(e)
2.2. initMethods
前面都在判重, 核心是最后的bind()
.
function initMethods (vm: Component, methods: Object) // vm, vm.$options.methods
const props = vm.$options.props
/* props
color:
id: 1,
text: 'Hello'
,
text:
id: 2,
text: 'World'
,
*/
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)
// vm[key]即当前遍历的key对应的methods函数, 判定其是否为函数类型并判重
// 重的话直接vm[key] = noop, 不重的话
2.2.1.initMethods----bind
src/core/observer/index.js
export const bind = Function.prototype.bind ? nativeBind : polyfillBind
function nativeBind (fn: Function, ctx: Object): Function // methods函数, vm
return fn.bind(ctx)
function polyfillBind (fn: Function, ctx: Object): Function
function boundFn (a)
const l = arguments.length
return l
? l > 1
? fn.apply(ctx, arguments)
: fn.call(ctx, a)
: fn.call(ctx)
boundFn._length = fn.length
return boundFn
2.3.initData
完整代码:
function initData (vm: Component) // vm
let data = vm.$options.data // 函数mergedInstanceDataFn ()
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data ||
/* data格式:
options: [
id: 0, name: 'color' ,
id: 1, name: 'color'
],
selected: 0
*/
if (!isPlainObject(data)) // !true
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
)
const keys = Object.keys(data) // data中的属性名构成数组keys
const props = vm.$options.props
/* props
color:
id: 1,
text: 'Hello'
,
text:
id: 2,
text: 'World'
,
*/
const methods = vm.$options.methods
/* methods:
handleClick: function () ,
handleBlur: function()
...
*/
let i = keys.length
while (i--) // 挨个检查data中的属性名有没有和props或者methods重名
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 data
observe(data, true /* asRootData */)
/*
data最终被传入new Obsever(value), 然后因为是一个对象走了else线this.walk(value),
内部属性被循环defineReactive()
*/
2.3.1.initData—observe
需要过一遍这里基本是因为格式可能不能直接处理, 需要判定.
export function observe (value: any, asRootData: ?boolean): Observer | void
/* value格式:
options: [
id: 0, name: 'color' ,
id: 1, name: 'color'
],
selected: 0
*/
// asRootData: true
if (!isObject(value) || value instanceof VNode)
// 这一句导致进到下面的只有对象数组
return
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer)
// 如果value上有__ob__说明该value已经进行过观察, 直接返回value.__ob__
ob = value.__ob__
else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
)
// 如果没被观察过, 那么创建观察者实例, 劫持监听所有属性并向Dep通知变化
ob = new Observer(value)
if (asRootData && ob)
ob.vmCount++
return ob
2.3.2.1.initData—observe—Observer类
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() // 实例化一个Dep
this.vmCount = 0
def(value, '__ob__', this) // 为value defineProperty: __ob__
/* value格式:
input: "Hello"
__ob__:
value:
input: "Hello",
dep: Dep,
vmCount: 1
*/
if (Array.isArray(value)) // 由上不是value
if (hasProto)
protoAugment(value, arrayMethods)
else
copyAugment(value, arrayMethods, arrayKeys)
this.observeArray(value)
else
this.walk(value)
// 提取value全部键组成数组, 然后循环将每个值传入defineReactive(obj, keys[i])做响应式处理(见下walk)
walk (obj: Object)
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++)
defineReactive(obj, keys[i])
observeArray (items: Array<any>)
/* 为数组的每一项new Observer()设置观察, 如果数组内部不是对象就继续到这再来一次, 看着像个递归, 这步只是不停的依靠循环不断深入提取数据, 最终目的还是达到walk()的执行条件去执行walk(), 只有walk()里面有defineReactive(). */
for (let i = 0, l = items.length; i < l; i++)
observe(items[i])
2.4.initComputed
const computedWatcherOptions = lazy: true // watcher类的options, 有lazy的话会懒执行
function initComputed (vm: Component, computed: Object) // vm, vm.$options.computed
const watchers = vm._computedWatchers = Object.Vue 2.6.13 源码解析