6.最俗學習之-Vue原始碼學習-資料篇(上)
阿新 • • 發佈:2019-01-26
這篇重點學習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的原因,下面的例子可以測試
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
</div>
</body>
<script type="text/javascript" src="vue.js"></script>
<script>
var mixin = {
beforeCreate () {
console.log('beforeCreate from mixin')
}
}
let vm = new Vue({
el: '#app',
data: {
a: 1,
b: [1, 2, 3]
},
beforeCreate () {
console.log('beforeCreate 2')
},
mixins: [mixin]
})
console.log(vm)
</script>
</html>
// 然而這個就有點陌生了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鉤子,因為這個鉤子不會再執行
// 這個大概就是可以在例項上面註冊生命週期事件吧,幾乎沒怎麼用到過,暫時不明白這個設計得用途
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<p>{{ a }}</p>
</div>
</body>
<script type="text/javascript" src="vue.js"></script>
<script>
var mixin = {
beforeCreate () {
console.log('beforeCreate from mixin')
}
}
let vm = new Vue({
el: '#app',
data: {
a: 1,
b: [1, 2, 3]
},
beforeCreate () {
console.log('beforeCreate 2')
},
mixins: [mixin]
})
console.log(vm)
vm.$on('hook:updated', function () {
console.log('hook:updated')
})
setInterval( ()=> {
vm.a++
}, 1000)
</script>
</html>
最後主角要登場了!!!噔噔蹬
這裡還是看大神的文章,主線路還是這裡的
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<any>) {
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 {}
// 這個是判斷當前的環境能否使用這個東東,然後決定取那個方法
這裡還是推薦這位大神的文章,分析的非常到位,而且圖文並茂
// 首先是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
})
})
// 這裡弄個自己理解的例子,這個有點不好說,有點繞,還是看大神的分析,不能看懂也不勉強,
// 反正可以理解為在呼叫原生的陣列方法前做點什麼事情,資料繫結更新什麼的等等
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
</div>
</body>
<script type="text/javascript" src="vue.js"></script>
<script>
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
const arrayProto = Array.prototype
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)
let inserted
switch (method) {
case 'push':
inserted = args
break
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
return result
})
})
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
console.log(arrayProto)
console.log(arrayMethods)
// ----------------------上面是官方的例子----------------------------------------
// ---------------------個人理解的一個例子---------------------------------------
var myArr = Object.create(Array.prototype);
var original = Array.prototype;
myArr.push = function (argument) {
console.log('自己的方法,先做點什麼,最後在呼叫原生的方法')
original.push.apply(this, [argument])
}
var arr = [1,2,3]
arr.__proto__ = myArr;
arr.push(7)
console.log(arr)
// ---------------------個人理解的一個例子,然後對每個方法做處理-------------------------------
const original_arr_methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
var myArr2 = Object.create(Array.prototype);
var original2 = Array.prototype;
original_arr_methods.forEach(function (method) {
myArr2[method] = function (argument) {
console.log('自己的方法,先做點什麼,最後在呼叫原生的方法')
original2[method].apply(this, [argument])
}
})
var arr2 = [1,2,3,4,5,6,7,8,9]
arr2.__proto__ = myArr2;
arr2.push(0)
arr2.shift()
arr2.unshift(1)
arr2.reverse()
arr2.splice(3)
console.log(arr2)
</script>
</html>
// 回到例子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<string>) {
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) // 水平有限,看不懂 - -#