前言
大家好,我叫good。本章节适合阅读过部分源码,但理解不是很透彻的同学。
- 如果不了解JS内置API可以到
MDN
上查找 developer.mozilla.org/zh-CN/
源码分析分为以下几个部分,之后会更新后续篇章
- Vue初始化流程-数据绑定
- Vue模板编译原理
- Vue依赖收集原理
- Vue全局钩子函数以及生命周期钩子的原理
- Vue diff核心流程原理
Vue响应式原理
初始化流程分析:
初始化实例 -> 初始化状态 -> 绑定数据
这个就是我们的模板
let vm = new Vue({
el: '#app',
data() { // 根组件中data是一个函数
return {
name: 'vue',
obj: {
name: 'obj'
},
ary: ['one', {
message: 'inAry'
}]
}
}
});
复制代码
初始化实例
闲话不多扯,直接上代码
import { initMixin } from './init'
// 在new Vue的时候会执行实例中的_init方法,然后初始化配置
function Vue (options) {
// if (process.env.NODE_ENV !== 'production' &&
// !(this instanceof Vue) // 如果this不是Vue实例就抛错
// ) {
// warn('Vue is a constructor and should be called with the `new` keyword')
// }
this._init(options); // 将配置对象传递过去
}
initMixin(Vue); // new Vue之前,将Vue传递过去
复制代码
如果不分文件模块划分, 在代码特别多的情况下将难以管理
进入到_init方法
export function initMixin (Vue) {
Vue.prototype._init = function (options) {
const vm = this
// 省略校验源码...
// if (options && options._isComponent) { 我们初始化页面不是组件所以走 else
// initInternalComponent(vm, options)
// } else {
// 我们后面章节再详细说合并的事情,先知道把options挂载到实例上
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
// }
// initLifecycle(vm) 注释部分,暂且忽略,后续篇章再细说
// initEvents(vm)
// initRender(vm)
// callHook(vm, 'beforeCreate') // 生命周期钩子
// initInjections(vm) // resolve injections before data/props
initState(vm) //初始化状态
// initProvide(vm) // resolve provide after data/props
// callHook(vm, 'created') // 生命周期钩子
// 页面挂载逻辑
// 在配置中传递el 等价于 new Vue().$mount('#app')
// 最后还是会走到 $mount方法
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
复制代码
_init 做的事情主要是初始化状态和挂载页面,当然还有其他的初始化流程。我们后续会一一讲解
初始化状态
进入initState方法
export function initState (vm) {
vm._watchers = []
const opts = vm.$options
// 由于我们模板只有data就先省略其他的源码
if (opts.data) {
initData(vm) // 将vm传递进去 绑定data中的属性
} else {
observe(vm._data = {}, true /* asRootData */)
}
}
复制代码
进入 initData方法
const sharedPropertyDefinition = { // Object.defineProperty的第三个参数
enumerable: true,
configurable: true,
get: () => {},
set: () => {}
};
/* 代理函数proxy */
export function proxy (target, sourceKey, key) {
// vm , `_data`, key 为了清晰表明参数,注意看`_data`是字符串其他是变量
// 重新对sharedPropertyDefinition的get/set赋值
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]; // 相当于代理到 vm['_data'][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val; // 等价于 vm['_data'][key] = val
}
// 调用defineProperty进行代理,当我们调用 vm[key] 时就会调用get/set方法
Object.defineProperty(target, key, sharedPropertyDefinition)
}
/* initData 方法 */
function initData (vm) {
let data = vm.$options.data; // 将vm.$options.data 给到 data
// 将结果赋值给 data 和 vm._data
data = vm._data = typeof data === 'function' // 如果是一个函数那么先将他执行再返回
? getData(data, vm) // 我们的data是一个函数,所以先执行在返回结果
: data || {}
// 省略校验源码...
const keys = Object.keys(data); // 获取data中的key
let i = keys.length
while (i--) {
const key = keys[i]; //获取keys中的当前i这一项
// 省略校验源码...
// 如果不是 _ 和 $ 开头我们才将data[key] 代理到 vm 上
if (!isReserved(key)) {
proxy(vm, `_data`, key); // 通过vm.xxx直接能直接访问到data.xxx的属性
}
}
// 数据绑定的核心方法
observe(data, true /* asRootData */)
}
复制代码
绑定数据
- 数据绑定的核心
进入observer(核心方法)
export function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) { // 如果不是一个对象类型或者是一个VNode实例
return
}
let ob;
// 如果已经有__ob__这个歌属性,并且属性值是Observer的实例
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__; // 直接将值赋值到ob
} else if (
shouldObserve && // 是否观察,默认是true
!isServerRendering() && // 不是服务器渲染
(Array.isArray(value) || isPlainObject(value)) && // vlaue是一个数组或对象
Object.isExtensible(value) && // vlaue是个可增加新属性的对象
!value._isVue // value不是Vue
) {
ob = new Observer(value); // 创建一个观察者观察value
}
if (asRootData && ob) { // 初始化绑定数据asRootData是true
ob.vmCount++
}
return ob
}
复制代码
进入Observer方法
import { arrayMethods } from './array'
export const hasProto = '__proto__' in {}; // 是否可用__proto__
function protoAugment (target, src) { // 将目标原型链的指向src
target.__proto__ = src
}
export class Observer {
constructor (value) {
this.value = value
// this.dep = new Dep(); 依赖收集,下边会讲但现在先忽略
// this.vmCount = 0
def(value, '__ob__', this); // 粗略的认为将value['__ob__'] = this
if (Array.isArray(value)) { // 对数组进行不同处理
if (hasProto) { // 可用__proto__
protoAugment(value, arrayMethods) // 将value的原型链指向arrayMethods
}
this.observeArray(value); // 监听数组中含有对象类型的值
} else { // 对象直接获取到key监听属性
this.walk(value)
}
}
walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]); // 将对象每一个值进行绑定
}
}
observeArray (items) {
for (let i = 0, l = items.length; i < l; i++) {
// 递归观察数组每一项,如果不是对象会退回来
observe(items[i]);
}
}
}
export function defineReactive (obj, key, val) {
// const dep = new Dep(); 对象依赖收集,这个后续再讲
// getOwnPropertyDescriptor 获取一个自身描述对象
const property = Object.getOwnPropertyDescriptor(obj, key);
// 自身描述对象属性不可被改变和删除,什么都不做直接返回
if (property && property.configurable === false) {
return
}
const getter = property && property.get;
const setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
/*
* 这里有几种情况:
* 1. 我们在外面对属性定义了getter,那么vue会将我们的getter存储起来,并且有setter
* 2. 没有getter那么vue就会将obj[key] 赋值给 val,并且有setter
* 3. getter/setter都没有,直接 赋值val
* 4. getter有但setter没有,不会走到这里
*/
val = obj[key];
}
// 递归监听val
let childOb = !shallow && observe(val);
Object.defineProperty(obj, key, { // 绑定数据
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 只要我们传递了getter函数,就以我们传递getter返回值为准
// 但没传递getter的话,它就会去让 val = obj[key]
const value = getter ? getter.call(obj) : val
// if (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
// 参数校验
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return
if (setter) { // 通知我们定义的setter执行,并且将newVal传递过去
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal); // 将newVal重新监听起来
// dep.notify() 更新视图,后面说依赖收集原理再说
}
})
}
复制代码
arrayMethods数组变异方法
- 上面处理数组的
protoAugment(value, arrayMethods)
中arrayMethods就是来自这里的 - 变异方法为可以改变原数组的数组原型方法
const arrayProto = Array.prototype; // 获取数组的原型
export const arrayMethods = Object.create(arrayProto); // 创建一个空对象,并且原型指向arrayProto
const methodsToPatch = [ // 除了这些7个方法其他的都无法改变原数组
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]; // 获取数组原型上的方法
// 粗略的认为将arrayMethods[method] = function mutator (...args) {...}
def(arrayMethods, method, function mutator (...args) { // args是一个数组,里面的参数是数组方法的参数
// 谁调用这个mutator方法,this就是谁,当前只是声明
const result = original.apply(this, args); // 调用了源数组的方法,只是通过apply将this改成了当前调用它的数组
const ob = this.__ob__; // 获取 __ob__属性,之前在创建Observer中赋值过了。如果忘记可以去上个代码块回顾
let inserted;
switch (method) {
case 'push':
case 'unshift':
// 将数组新增的值赋值给inserted
inserted = args;
break
case 'splice':
// 将数组新增的值第二项以上截取出来赋值给 inserted
inserted = args.slice(2); // 例如: [0, 0, 4].slice(2) 截取出 [4]
break
}
// 如果inserted有值,将数组新增的值监听起来
if (inserted) ob.observeArray(inserted);
// ob.dep.notify() 通知依赖视图更新数据,这个后续篇章会讲到
return result
});
});
复制代码
总结
- Vue之所以可以直接通过
vm.xxx
访问到data
中的属性,全都是proxy
将vm._data
的属性代理到vm上。 - Vue劫持配置对象中的data对象,然后获取data的key监听绑定。如果
data[key]
也是一个对象数据类型的值,将其递归observe(data[key])
,如果是一个数组的话,不会直接循环索引监听,而是重定向数组原型链,增加7个变异方法push/pop/unshift/shift/splice/reverse/sort
,让观察者实例的observeArray
内部循环数组每一项,将每一项调用observe
递归监听
结尾
感谢您看到最后!如果有什么不正确的地方欢迎在评论区或私信本人改正,谢谢您!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END