MVC的全称是Model(模型)-View(视图)-Controller(控制器)
Model:这是数据层,存储项目所需的数据。Model的作用是返回或者更新数据。在应用中常用于数据库存储数据。
View:视图层,用于想用户显示数据。View本身不显示任何数据,而是Controller或者Model让View显示数据。
Controller:控制层,MVC的核心部分。控制器相当于用户和系统的链接,接收用户的输入,处理完成后,要求Model更新数据。
MVP模式将Controller更名为Presenter,同时改变了通讯方向。全称是Model(模型)-Presenter(呈现器)-View(视图)。
MVP中的数据通信均是双向的。
Model:数据层。
View:视图层。
Presenter:Presenter层。Pressenter作为View和Model的中间层起到了桥梁的作用。Presenter从Model层获取数据通过接口发送给View层展示。View层将用户操作发送给Presenter,借由Presenter将数据发送给Model进行数据更新。
MVVP模式将Presenter更名为View-Model(对应MVC中的C-Controller),基本上于MVP模式一致。但是MVVM采用的是双向数据绑定,View的变动自动反应到ViewModel上。
Model:数据层。
View:视图层。
ViewModel:在vue中指的是vue的实例对象,是一个公开公共属性和命令的抽象view;
View中的变化会自动更新到ViewModel,ViewModel的变化也会自动反应到View视图中。这种自动更新是通过vue中的Observer观察者实现的。
数据双向绑定的意思是view的变化能反应到ViewModel中,ViewModel中的变化能同步更新View视图。
Vue双向数据绑定的原理是数据劫持+订阅发布模式实现
数据劫持指的是在访问或者修改某个属性时,通过Object.defineProperty()或者Proxy对象拦截这个行为,扩展额外的操作和行为然后返回结果。
定义:对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有与该对象产生依赖关系的对象都会接收到通知。
优点:耦合性低,便于维护
缺点:创建订阅者可能会消耗一定时间和内存,但是订阅事件不一定会发生,订阅者则会一直存在于内存中。
在之前的源码分析中我们知道,Vue的构造函数中实现了一个_init()方法,这个方法是用来初始化一些选项的:
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')
}
//这里初始化了Vue传入的选项
this._init(options)
}
上面的_init(options)方法的实现在src/core/instance/init.js中的initMixin(Vue)中,分析其中的源码,我们可以根据方法名称做一下简单的判断:
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
}
// a flag to avoid this being observed
vm._isVue = true
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 初始化声明周期,跟生命周期有关
initLifecycle(vm)
// 初始化事件,实现处理父组件传递的监听事件的监听器
initEvents(vm)
// 初始化渲染器$slots scopedSlots、_c、$createElement
initRender(vm)
// 调用生命周期钩子函数
callHook(vm, 'beforeCreate')
// 获取注入的数据
initInjections(vm) // resolve injections before data/props
// 初始化状态props、methods、data、computed、watch
initState(vm)
// 提供数据
initProvide(vm) // resolve provide after data/props
// 调用生命周期钩子函数
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
// 如果存在el则执行$mount
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
通过分析initMixin(Vue)我们可以得到一个大概的结果,那就是在Vue初始化的时候我们完成了生命周期、事件、渲染器、状态的初始化,同时还获取了祖先组件注入的数据,同时为后代组件的注入提供了数据。
其中initState(vm)方法则是对数据还有其他东西的一些初始化操作,跳入该方法中查看一下其内容src/core/instance/state.js
export function initState (vm: Component) {
// 在当前实例中创建了一个watcher的空数组
vm._watchers = []
// 保存了当前Vue实例的选项options
const opts = vm.$options
// 如果选项中的props存在的化则初始化props
if (opts.props) initProps(vm, opts.props)
// 如果选项中methods存在则初始化方法
if (opts.methods) initMethods(vm, opts.methods)
// 如果选项中的data存在则进行data的初始化操作
// data的处理,响应化处理
if (opts.data) {
// 一般情况下我们初始化Vue实例的时候都会传入data,所以大部分情况是走这个方法的
// 初始化data
initData(vm)
} else {
// 数据响应化
observe(vm._data = {}, true /* asRootData */)
}
// 初始化computed
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
// 初始化watch
initWatch(vm, opts.watch)
}
}
分析上面这段代码我们可以知道,在initState(vm)中是对数据进行了初始化的(先考虑传入data的情况),那么我们继续顺着代码往下看:
function initData (vm: Component) {
// 取出data
let data = vm.$options.data
// 判断data是否为方法,如果是一个方法,则处理完毕后返回
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
//如果data不是一个object则警告
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
// 分别取出data的key,选项中的props、methods
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
)
}
}
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 */)
}
废话不多说,直接上代码:from src/core/observer/index.js
//该方法,接收了vue实例中的data数据,和一个boolean,返回了一个Observer(观察者)实例
export function observe (value: any, asRootData: ?boolean): Observer | void {
//如果data数据不是一个对象或者是一个虚拟domVNode,直接结束
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
// 如果当前对象已经存在observer则返回
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
// 已经存在的Observer则会保存在value.__ob__中
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {// 否则,我们要新创建一个Observer将其返回
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
// 无论结果如何,最终都会返回一个observer
return ob
}
所以接下来我们要看一下Observer的构造方法完成了什么事情fromsrc/core/observer/index.js
//Observer的构造方法
// 接收传入的Vue中的data数据
constructor (value: any) {
this.value = value
// 新建了一个Dep的实例,这个Dep是用来做依赖收集的,后面会用到
this.dep = new Dep()
//当前的vmCount数量
this.vmCount = 0
// 给当前data对象定义了一个__ob__属性
def(value, '__ob__', this)
// 判断当前对象是否为数组,如果是数组单独处理,
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {// 如果不是数组的话
//普通对象则用walk遍历
this.walk(value)
}
}
walk (obj: Object) {
// 拿出obj(data对象)的key
const keys = Object.keys(obj)
// 对所有的key进行遍历
for (let i = 0; i < keys.length; i++) {
// 实现数据响应式
// 传入data对象和当前key,进行响应化处理
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])
}
}
export function defineReactive (
obj: Object,// 接收一个object,就是vue实例的data
key: string,// data的key
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// 这里传进来的是data对象,所以没有一个data对象就有一个dep与之对应
const dep = new Dep()
// 查看对象上是否有该属性,如果没有则停止执行
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// 获取属性的getter和setter
// 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)
// 定义数据的拦截
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 依赖收集,这里的Dep.target是watcher的实例
if (Dep.target) {
dep.depend()// 追加依赖关系,简单来说就是将watcher加入到dep中,但是实际操作要复杂一点
// 如果childOb存在,说明该属性是一个对象
if (childOb) {
// 继续追加依赖
childOb.dep.depend()
// 如果是数组,继续处理
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
// 如果getter存在则调用getter否则返回当前val
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
// 如果setter存在则执行setter执行更新,否则用新值覆盖老值
if (setter) {
setter.call(obj, newVal)
} else {
// 更新本地的val
val = newVal
}
// 如果新传入的val是一个数组的化,则递归进行响应话处理observe
childOb = !shallow && observe(newVal)
// 通知更新
dep.notify()
}
})
}
简单总结一下defineReactive()
♣ 通过Object.defineProperty方法对data属性的set和get方法进行数据劫持
♣ 创建Dep实例,每有一个data属性则有一个dep与之对应
♣ 扩展了data属性的get方法,将Dep.target静态属性中的watcher加入到dep实例中(依赖收集过程)
♣ 扩展了data属性中的set方法,当数据被更新时,执行dep.notify()方法通知数据更新
所以我们顺着代码看一下依赖收集的过程和通知更新的方法
依赖收集的过程通过**dep.depend()**完成,我们来看一下它的实现from src\core\observer\dep.js,由于代码较多,我们只粘贴关键代码
export default class Dep{
static target: ?Watcher;// 静态属性中的watcher实例
id: number;
subs: Array<Watcher>;// 维护了一个watcher数组
depend () {
// 这里的Dep.target是watcher的实例,
if (Dep.target) {
// 建立和watcher之间的关系,将当前Dep实例加入watcher中
Dep.target.addDep(this)
}
}
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
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
// 通知watcher进行数据更新
subs[i].update()
}
}
}
// 将当前watcher实例赋值到Dep.target的静态属性上
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
// 将watcher实例赋值到Dep的静态属性上
Dep.target = target
}
上面代码中的Dep.target.addDep(this),是将当前的Dep实例加入到了watcher实例中,这里有一个细节:理论上是每有一个Data则有一个Dep,当同一个Data多次被调用的时候,只需要创建多个watcher对其进行监听,然后Dep进行依赖收集,通知watcher更新,所以理论上Dep和watcher是一对多的关系.
但是上面的代码是将Dep实例添加到了Wtacher中,所以这就形成了多对多的关系.出现这种情况是因为真正使用的时候,有的时候一个组件
这里的Watcher主要是讲Render Watcher,组件实例化的时候会产生一个Watcher的实例,在组件$mount过程中的mountComponent()方法中new Watcher:
这里只粘贴部分核心代码
// 定义组件更新函数
// _render()执行可以获得虚拟dom,VNode
// _update()将虚拟DOM转换为真实DOM
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// 创建Watcher实例
//vm:当前vue实例
//updateComponent:组件更新函数
// noop:
// {}:回调函数
//true:是否是浏览器的watcher
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
上面的updateComponent()函数调用了Vue._render函数,最终会调用data属性的get函数,最终完成依赖收集。
只粘贴部分核心代码
constructor(){
// watcher创建的时候会执行当前watcher实例的get函数,这样会出发依赖收集的过程
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {
//将当前watcher实例赋值到Dep.target静态属性中
pushTarget(this)
let value
// 当前vue实例
const vm = this.vm
try {
// getter函数是上面的updateComponent()函数,会触发依赖收集过程
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)
}
// 清空Dep.target静态属性中的watcher实例
popTarget()
this.cleanupDeps()
}
return value
}
/**
* Add a dependency to this directive.
*/
//依赖收集
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
// 维护了一个depid映射关系
this.newDepIds.add(id)
this.newDeps.push(dep)
// 如果当前dep里面没有watcher,则将该watcher加入到dep中建立联系
if (!this.depIds.has(id)) {
// dep中维护了一个watcher的数组
// 将当前watcher加入到dep中的watcher数组中,实现dep对watcher的收集
dep.addSub(this)
}
}
}
/**
* 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 {
// 将当前watcher实例push到更新队列中实现数据的更新
queueWatcher(this)
}
}
根据Observer的构造方法得知,当data为数组时
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
// 给当前对象定义一个__ob__属性
def(value, '__ob__', this)
// 判断当前data对象是否为数组
if (Array.isArray(value)) {
if (hasProto) {
// 覆盖数组的原型方法
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 如果是数组则进行数组响应化的处理
this.observeArray(value)
} else {
//普通对象则用walk遍历
this.walk(value)
}
}
// 数组响应化处理
observeArray (items: Array<any>) {
//遍历数组
for (let i = 0, l = items.length; i < l; i++) {
// 取出数组的每一项进行响应化处理
observe(items[i])
}
}
上述代码的核心功能主要是,protoAugment()方法扩展了当前data数组的原型方法,arrayMethods
直接上核心代码
//src/core/observer/array.js:
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]
// 拦截,添加额外行为
// arrayMethods:数组的原型对象,定义特殊方法
def(arrayMethods, method, function mutator (...args) {
// 执行原先的任务
const result = original.apply(this, args)
// 额外任务:通知更新
// 从this.__ob__中取出观察者
const ob = this.__ob__
let inserted
// 以下三个操作需要额外处理
// 如果是新添加的元素,还需要额外的做响应化的处理
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 如果inserted存在说明元素是新添加的,额外响应化的处理
if (inserted) ob.observeArray(inserted)
// notify change
// 核心:添加通知更新方法,每一个ob中都有一个dep和这个对象或者数组对应
ob.dep.notify()
return result
})
})
♣ 注意,通过上面的代码我们可以看出,只有通过扩展的这七个方法才能实现数组的响应化:pop、push、shift、unshift、splice、sort、reverse
因为是自己边分析源码边写的一些东西,所以可能有点乱。为了捋清思路做了张图片,聊胜于无吧:
下面附上一张官方数据响应化的工作流程图:
图片来源:https://vuejs.org/v2/guide/reactivity.html
响应式的基本机制:
以上,根据自己对源码的理解,和网上一些大神的分析整理出来的,如有不对的地方,欢迎各位大神指正。