初步方案:從第一個commit開始到到最近的commit,從代碼的大綱開始到細節,利用思惟導圖。
注意: 源碼閱讀是一件比較枯燥的事情,要有別的東西一塊兒作,源碼只是偶爾看看,常常發呆的話,很是浪費時間。javascript
寫在前面: 閱讀源碼並不能產生什麼實際的價值,而閱讀的源碼的過程當中,你學到的思路,分析方法,總結,纔是你花大時間閱讀源碼所能產生的實際價值。html
閱讀源碼仍是缺少一點思考,沒有結合到實際項目中源碼是怎麼產生做用的!!!vue
閱讀源碼的疑問:html5
可能的答案:
關鍵多是dep,每一個待觀察的對象都有一個Observer實例,實例都具備一個dep,每一個dep都有一個notify;set方法的時候會通知notify,notify直接調用待觀察對象的update方法。這個邏輯鏈路是符合觀察者模式的設計模式的。
觀察者模式:當一個對象變化時,其它依賴該對象的對象都會收到通知,而且隨着變化。java
constructor (options) { this.$options = options this._data = options.data const el = this._el = document.querySelector(options.el) const render = compile(getOuterHTML(el)) //編譯vue模板 this._el.innerHTML = '' Object.keys(options.data).forEach(key => this._proxy(key)) //利用Observer.defineProperty();重定義了屬性成get和set if (options.methods) { Object.keys(options.methods).forEach(key => { this[key] = options.methods[key].bind(this) //把methods 做用域綁定到this,也就是vue實例上面 }) } this._ob = observe(options.data) // 將數據轉化爲觀察者對象 this._watchers = [] this._watcher = new Watcher(this, render, this._update) // 解析表達式,收集依賴,當值變化的時候,通知回調 this._update(this._watcher.value) }
源碼編寫挺遵照規範的,類的首字母大寫啊,觀察者模式啊等等。react
數據的觀察者,每一個data屬性上面都會有一個觀察者git
/** * 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. * * @param {*} value * @param {Vue} [vm] * @return {Observer|undefined} * @static */ export function observe (value, vm) {// 此處的value 是options.data,通常來講是個json if (!value || typeof value !== 'object') { return } var ob if ( hasOwn(value, '__ob__') && //hasOwn = hasOwnproperty ,檢查是否具備`__ob__`屬性 value.__ob__ instanceof Observer //`__ob__` 是不是Observer的實例 ) { ob = value.__ob__ //已經存在`__ob__`,則賦原值 } else if ( shouldConvert && (isArray(value) || isPlainObject(value)) && //判斷是不是原生的數組或者是對象 Object.isExtensible(value) && //判斷一個對象是否可擴展,添加屬性 !value._isVue ) { ob = new Observer(value) } if (ob && vm) { ob.addVm(vm) //??添加vm實例,代理keys } return ob } /** * Add an owner vm, so that when $set/$delete mutations * happen we can notify owner vms to proxy the keys and * digest the watchers. This is only called when the object * is observed as an instance's root $data. * * @param {Vue} vm */ Observer.prototype.addVm = function (vm) { (this.vms || (this.vms = [])).push(vm) } /** * Observer class that are attached to each observed * object. Once attached, the observer converts target * object's property keys into getter/setters that * collect dependencies and dispatches updates. * * @param {Array|Object} value * @constructor */ export function Observer (value) { this.value = value this.dep = new Dep() def(value, '__ob__', this) //??將value做爲obj,觀察者的關鍵 if (isArray(value)) { var augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) //若是是數組,則攔截變異方法,通知更新 this.observeArray(value) // 遍歷value ,observer實例化每一個value的值。 } else { this.walk(value) } } /** * A dep is an observable that can have multiple * directives subscribing to it. * dep 就是一個能夠訂購多個指令的觀察者 * @constructor */ export default function Dep () { this.id = uid++ this.subs = [] } /** * Notify all subscribers of a new value. */ Dep.prototype.notify = function () { // stablize the subscriber list first var subs = this.subs.slice() for (var i = 0, l = subs.length; i < l; i++) { subs[i].update() } } /** * Augment an target Object or Array by intercepting * the prototype chain using __proto__ * * @param {Object|Array} target * @param {Object} src */ function protoAugment (target, src) { /* eslint-disable no-proto */ target.__proto__ = src /* eslint-enable no-proto */ } /** * Augment an target Object or Array by defining * hidden properties. * * @param {Object|Array} target * @param {Object} proto */ function copyAugment (target, src, keys) { for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i] def(target, key, src[key]) } } 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 var original = arrayProto[method] def(arrayMethods, method, function mutator () { // avoid leaking arguments: // http://jsperf.com/closure-with-arguments var i = arguments.length var args = new Array(i) while (i--) { args[i] = arguments[i] } var result = original.apply(this, args) var ob = this.__ob__ var 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 }) }) /** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. * * @param {Object} obj * @param {String} key * @param {*} val * @public */ export function set (obj, key, val) { if (hasOwn(obj, key)) { obj[key] = val return } if (obj._isVue) { set(obj._data, key, val) return } var ob = obj.__ob__ if (!ob) { obj[key] = val return } ob.convert(key, val) ob.dep.notify() //更新只有,調用通知更新。 if (ob.vms) { var i = ob.vms.length while (i--) { var vm = ob.vms[i] vm._proxy(key) vm._digest() } } return val } /** * Walk through each property and convert them into * getter/setters. This method should only be called when * value type is Object. * * @param {Object} obj */ Observer.prototype.walk = function (obj) { var keys = Object.keys(obj) for (var i = 0, l = keys.length; i < l; i++) { this.convert(keys[i], obj[keys[i]]) } } /** * Define a reactive property on an Object. * * @param {Object} obj * @param {String} key * @param {*} val */ export function defineReactive (obj, key, val) { var dep = new Dep() var property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get var setter = property && property.set var childOb = observe(val) Object.defineProperty(obj, key, { 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 (isArray(value)) { for (var e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() } } } return value }, set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val if (newVal === value) { return } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = observe(newVal) dep.notify() } }) }
主要是用來解析vue的字符串模板和vue內置的語法爲可處理的對象github
export function compile (html) { html = html.trim() const hit = cache[html] return hit || (cache[html] = generate(parse(html))) //若是沒有的話就生成一個 }
parse()
主要使用了一個第三方庫,將html元素轉化成了一個對象;能夠方便後一步generate處理vue內置語法v-for
,v-if
等等
htmlparser.jsexpress
** * Convert HTML string to AST * * @param {String} html * @return {Object} */ export function parse (html) { let root let currentParent let stack = [] HTMLParser(html, { html5: true, start (tag, attrs, unary) { let element = { tag, attrs, attrsMap: makeAttrsMap(attrs), parent: currentParent, children: [] } if (!root) { root = element } if (currentParent) { currentParent.children.push(element) } if (!unary) { currentParent = element stack.push(element) } }, end () { stack.length -= 1 currentParent = stack[stack.length - 1] }, chars (text) { text = currentParent.tag === 'pre' ? text : text.trim() ? text : ' ' currentParent.children.push(text) }, comment () { // noop } }) return root }
// src/compile/codepen.js // generate 主要是解析vue內置的語法,生成dom的。 export function generate (ast) { const code = genElement(ast) return new Function (`with (this) { return ${code}}`) } function genElement (el, key) { let exp if (exp = getAttr(el, 'v-for')) { return genFor(el, exp) } else if (exp = getAttr(el, 'v-if')) { return genIf(el, exp) } else if (el.tag === 'template') { return genChildren(el) } else { return `__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })` } }
解析表達式收集依賴,當值變化的時候回調json
** * A watcher parses an expression, collects dependencies, * and fires callback when the expression value changes. * This is used for both the $watch() api and directives. * * @param {Vue} vm * @param {String|Function} expOrFn * @param {Function} cb * @param {Object} options * - {Array} filters * - {Boolean} twoWay * - {Boolean} deep * - {Boolean} user * - {Boolean} sync * - {Boolean} lazy * - {Function} [preProcess] * - {Function} [postProcess] * @constructor */ export default function Watcher (vm, expOrFn, cb, options) { // mix in options if (options) { extend(this, options) } var isFn = typeof expOrFn === 'function' this.vm = vm vm._watchers.push(this) this.expression = expOrFn this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = Object.create(null) this.newDepIds = null this.prevError = null // for async error stacks // parse expression for getter/setter if (isFn) { this.getter = expOrFn this.setter = undefined } else { warn('vue-lite only supports watching functions.') } this.value = this.lazy ? undefined : this.get() // state for avoiding false triggers for deep and Array // watchers during vm._digest() this.queued = this.shallow = false } /** * Prepare for dependency collection. */ Watcher.prototype.beforeGet = function () { Dep.target = this this.newDepIds = Object.create(null) this.newDeps.length = 0 } /** * Add a dependency to this directive. * * @param {Dep} dep */ Watcher.prototype.addDep = function (dep) { var id = dep.id if (!this.newDepIds[id]) { this.newDepIds[id] = true this.newDeps.push(dep) if (!this.depIds[id]) { dep.addSub(this) } } }
這個簡直就是js的工具箱寶庫,什麼工具類都有。判斷IE,判斷是不是數組,定義defineProperty,debounce 輸入延遲觸發;
其實閱讀源碼另外有一個頗有意思的地方,就是有些語法你會,但你沒看到別人這麼用,你可能永遠或者很長一段時間都不會這麼地用,也就是驚豔!!!
將類型判斷與定義放在一塊兒
(this.vms || (this.vms = [])).push(vm) 至關於 一個建立了一個prototype擁有全部Array.prototype的object。 const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto)
建立一個原型爲空的對象
const cache = Object.create(null) 若是hit沒有值,則返回gennerate以後的值。 const hit = cache[html] return hit || (cache[html] = generate(parse(html)))