源码地址
这篇重点学习Vue的数据响应系统,文件路径src/core/instance
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
callHook(vm, 'beforeCreate')
initState(vm)
callHook(vm, 'created')
initRender(vm)
// 先看event.js,只有这么一段
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
// 这里只做了两件事,至于这个vm.$options._parentListeners暂时是没有的,作用暂时不明
vm._events = Object.create(null)
vm._hasHookEvent = false
// 再看lifecycle.js,同样的这个if语句相关的也是不会执行的,因为没有这个parent,也是在vm实例上
// 添加各种内部的属性
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
// 然后到了最下面的callHook方法,执行生命周期,也就是上面两步后执行callHook(vm, 'beforeCreate')
export function callHook (vm: Component, hook: string) {
const handlers = vm.$options[hook]
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
handlers[i].call(vm)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
}
// 这里一开始不太明白handlers为什么会是个数组,后来想了下,是mixin的原因,下面的例子可以测试
Document
// 然而这个就有点陌生了vm._hasHookEvent,看了下构建后的源码,使用到这个的只有三处地方
// 这里添加的vm._hasHookEvent
function initEvents (vm) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
// init parent attached events
var listeners = vm.$options._parentListeners;
if (listeners) {
updateComponentListeners(vm, listeners);
}
}
// 这里添加事件方法(vm._events[event] || (vm._events[event] = [])).push(fn);
var hookRE = /^hook:/;
Vue.prototype.$on = function (event, fn) {
var vm = this;(vm._events[event] || (vm._events[event] = [])).push(fn);
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true;
}
return vm
};
// 这里触发这个vm._hasHookEvent
function callHook (vm, hook) {
var handlers = vm.$options[hook];
if (handlers) {
for (var i = 0, j = handlers.length; i < j; i++) {
handlers[i].call(vm);
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook);
}
}
// 根据官方文档api,下面用一个例子测试下,这里不能用beforeCreate钩子,因为这个钩子不会再执行
// 这个大概就是可以在实例上面注册生命周期事件吧,几乎没怎么用到过,暂时不明白这个设计得用途
Document
{{ a }}
最后主角要登场了!!!噔噔蹬
这里还是看大神的文章,主线路还是这里的
Vue2.1.7源码学习
initState(vm)
// 我们用最简单的例子说起,之后慢慢丰富内容
let vm = new Vue({
el: '#app',
data: {
a: 1,
b: [1, 2, 3]
}
})
// 这里只有data,则只会走initData(vm)
function initData (vm: Component) {
// 获取data
let data = vm.$options.data
// 获取data,若是函数形式则执行,无值则是空对象
data = vm._data = typeof data === 'function'
? data.call(vm)
: 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
const keys = Object.keys(data)
const props = vm.$options.props
let i = keys.length
// 检测data和props不能冲突
while (i--) {
if (props && hasOwn(props, keys[i])) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${keys[i]}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else {
// 代理数据,这样我们就能通过 this.a 来访问 data.a 了
proxy(vm, keys[i])
}
}
// observe data
observe(data, true /* asRootData */) // 重点是这个
}
src/core/observer/index.js
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
function observe (value, asRootData) {
// 若不是object,返回,注意这里,这里不是标准的检测方式,这里数组也会被看做是object的
if (!isObject(value)) {
return
}
var ob;
// 如果已经有这个属性,则说明已经绑定过数据了
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
observerState.shouldConvert && // 这个是定义的一个变量,为true
!isServerRendering() && // 是否为服务端渲染
(Array.isArray(value) || isPlainObject(value)) && // 是Array或者是Object
Object.isExtensible(value) && // 是否为可拓展属性的对象
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) { // 如果是根数据,那么ob这个实例的属性vmCount++
ob.vmCount++;
}
return ob
}
// 重点看这个ob = new Observer(value);
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value // value就是data值
this.dep = new Dep() // 这个是另一个类,依赖收集器
this.vmCount = 0 // 自身的一个属性
def(value, '__ob__', this) // 定义一个属性,用的def方法,后面再说
if (Array.isArray(value)) { // 如果是数组,则走这里处理
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else { // 非数组走这里
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
// 遍历对象的所有属性,执行defineReactive(obj, keys[i], obj[keys[i]])
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
// 按照例子
let vm = new Vue({
el: '#app',
data: {
a: 1,
b: [1, 2, 3]
}
})
// 就是这个样子了
ob = new Observer({
a: 1,
b: [1, 2, 3]
})
// 然后就是
this.walk({
a: 1,
b: [1, 2, 3]
})
// 再然后
defineReactive(obj, a, 1)
defineReactive(obj, b, [1, 2, 3])
// 找到defineReactive方法
function defineReactive$$1 (
obj,
key,
val,
customSetter
) {
var dep = new Dep(); // 先不管
// getOwnPropertyDescriptor方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
var property = Object.getOwnPropertyDescriptor(obj, key);
// 如果是不可操作的
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 获取对象上的get和set方法,这个应该和Object.defineProperty有关,这个方法网上教程很多,后面也会简单说下,这里应该就是之前自定义的方法吧,注释也说了pre-defined getter/setters,预定义的方法
var getter = property && property.get;
var setter = property && property.set;
var childOb = observe(val); // 检测它的子属性
Object.defineProperty(obj, key, { // 定义这个属性的get和set方法
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var 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) {
var 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 ("development" !== 'production' && customSetter) {
customSetter();
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = observe(newVal);
dep.notify();
}
});
}
// 重点在这个var childOb = observe(val); // 检测它的子属性,根据我们的例子这里会这样子
var childOb = observe(1);
var childOb = observe([1,2,3]);
// 然后再次进入observer方法,注意这个
// 若不是object,返回,注意这里,这里不是标准的检测方式,这里数组也会被看做是object的
if (!isObject(value)) {
return
}
// 所以第一个observe(1)直接就return了,重点在observe([1,2,3]),这里会ob = new Observer(value)
// 即ob = new Observer([1,2,3]),这个时候数个数组就会走这一段
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
}
// can we use __proto__?
export const hasProto = '__proto__' in {}
// 这个是判断当前的环境能否使用这个东东,然后决定取那个方法
这里还是推荐这位大神的文章,分析的非常到位,而且图文并茂
JavaScript实现MVVM之我就是想监测一个普通对象的变化
// 首先是protoAugment,根据例子即
protoAugment([1,2,3], arrayMethods, arrayKeys)
// 然后找到arrayMethods和arrayKeys,分别如下
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
/**
* Intercept mutating methods and emit events
*/
;[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator () {
// avoid leaking arguments:
// http://jsperf.com/closure-with-arguments
let i = arguments.length
const args = new Array(i)
while (i--) {
args[i] = arguments[i]
}
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
inserted = args
break
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
// 这里弄个自己理解的例子,这个有点不好说,有点绕,还是看大神的分析,不能看懂也不勉强,
// 反正可以理解为在调用原生的数组方法前做点什么事情,数据绑定更新什么的等等
Document
// 回到例子protoAugment([1,2,3], arrayMethods, arrayKeys)
// 这里arrayMethods我们已经知道了
arrayMethods = {
pop: function () {} // 自己重写的方法
push: function () {} // 自己重写的方法
shift: function () {} // 自己重写的方法
unshift: function () {} // 自己重写的方法
splice: function () {} // 自己重写的方法
sort: function () {} // 自己重写的方法
reverse: function () {} // 自己重写的方法
__proto__: {
// 这里面是原生数组的方法,也就是Array.prototype
}
}
arrayKeys = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"]
// 所以这个方法实际上就是,这个方法不需要第三个参数,也就是不需要arrayKeys
function protoAugment (target, src: Object) {
target.__proto__ = src
}
[1,2,3].__proto__ = arrayMethods // 这个操作类似我上面的那个例子的这一步
arr.__proto__ = myArr;
arr2.__proto__ = myArr2;
// 最后到了另一种情况就是,hasProto没有这个东东的情况,那就是这个形式
copyAugment([1,2,3], arrayMethods, arrayKeys)
// 对应的方法是这个,这个方法需要arrayKeys这个值
function copyAugment (target: Object, src: Object, keys: Array) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
// 这里的def方法也有说过,这一步的操作就不解释了,如果到了这里看不懂的话,就说明你上面的都没看懂,
// 思路应该还是比较乱的,也可能是我表达不好0.0,这里一定要弄懂,不然后面的数组操作会很懵逼
// 再次回到主线路
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
console.log(value.__proto__) // 到了这里打印出来看下其实就能明白了
this.observeArray(value)
}
// 最后剩下这个this.observeArray(value)了,对数组的每一项进行observe,又是这个东东- -#
observeArray (items) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
// 但是我们这个例子会被直接return的,因为里面有这一段
if (!isObject(value)) {
return
}
// 因为我们的例子是
b: [1,2,3]
// 假如是这样的数据,则会继续递归调用
b: [{key1: value1}, {key2: value2}, {key3: value3}]
b: [[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15]]
至此大概的思路应该都是这样了,也不知道说的对不对,看到最后自己脑袋都有点懵逼了,希望各路大神指点指点,感激不尽,最后还有个Dep的东东没有说,后面再看!
剩下的几个问题
Vue.set = set // 涉及到Vue的数据响应式系统,先保留
Vue.delete = del // 涉及到Vue的数据响应式系统,先保留
Vue.nextTick = util.nextTick // 水平有限,看不懂 - -#
initExtend(Vue) // 水平有限,看不懂 - -#
extend(Vue.options.directives, platformDirectives) // 水平有限,看不懂 - -#
extend(Vue.options.components, platformComponents) // 水平有限,看不懂 - -#
Vue.prototype.__patch__ // 水平有限,看不懂 - -#
compileToFunctions // 水平有限,看不懂 - -#
const extendsFrom = child.extends // 水平有限,看不懂 - -#
initProxy(vm) // 水平有限,看不懂 - -#