From e05fba9ae8201e06501e46cce47555b2b5df3ccb Mon Sep 17 00:00:00 2001 From: gogoend Date: Sun, 28 Jun 2020 16:51:41 +0800 Subject: [PATCH 01/32] =?UTF-8?q?=E6=BA=90=E7=A0=81=E8=A7=A3=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/vue.js | 109 +++++++++-------------------- examples/commits/index.html | 2 +- package.json | 2 +- src/core/index.js | 2 + src/core/instance/index.js | 27 +++++-- src/core/instance/init.js | 2 + src/platforms/web/runtime/index.js | 14 +++- 7 files changed, 73 insertions(+), 85 deletions(-) diff --git a/dist/vue.js b/dist/vue.js index 0b4d59d2876..c4b57558e14 100644 --- a/dist/vue.js +++ b/dist/vue.js @@ -1,6 +1,6 @@ /*! * Vue.js v2.5.0 - * (c) 2014-2017 Evan You + * (c) 2014-2020 Evan You * Released under the MIT License. */ (function (global, factory) { @@ -610,7 +610,6 @@ function logError (err, vm, info) { /* */ /* globals MessageChannel */ -// can we use __proto__? var hasProto = '__proto__' in {}; // Browser environment sniffing @@ -812,9 +811,6 @@ Dep.prototype.notify = function notify () { } }; -// the current target watcher being evaluated. -// this is globally unique because there could be only one -// watcher being evaluated at any time. Dep.target = null; var targetStack = []; @@ -1204,11 +1200,6 @@ function dependArray (value) { /* */ -/** - * Option overwriting strategies are functions that handle - * how to merge a parent option value and a child option - * value into the final value. - */ var strats = config.optionMergeStrategies; /** @@ -2053,18 +2044,6 @@ function checkProp ( /* */ -// The template compiler attempts to minimize the need for normalization by -// statically analyzing the template at compile time. -// -// For plain HTML markup, normalization can be completely skipped because the -// generated render function is guaranteed to return Array. There are -// two cases where extra normalization is needed: - -// 1. When the children contains components - because a functional component -// may return an Array instead of a single root. In this case, just a simple -// normalization is needed - if any child is an Array, we flatten the whole -// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep -// because functional components already normalize their own children. function simpleNormalizeChildren (children) { for (var i = 0; i < children.length; i++) { if (Array.isArray(children[i])) { @@ -3143,11 +3122,6 @@ Watcher.prototype.teardown = function teardown () { } }; -/** - * Recursively traverse an object to evoke all converted - * getters, so that every nested property inside the object - * is collected as a "deep" dependency. - */ var seenObjects = new _Set(); function traverse (val) { seenObjects.clear(); @@ -3574,9 +3548,6 @@ function resolveInject (inject, vm) { /* */ -/** - * Runtime helper for rendering v-for lists. - */ function renderList ( val, render @@ -3608,9 +3579,6 @@ function renderList ( /* */ -/** - * Runtime helper for rendering - */ function renderSlot ( name, fallback, @@ -3647,20 +3615,12 @@ function renderSlot ( /* */ -/** - * Runtime helper for resolving filters - */ function resolveFilter (id) { return resolveAsset(this.$options, 'filters', id, true) || identity } /* */ -/** - * Runtime helper for checking keyCodes from config. - * exposed as Vue.prototype._k - * passing in eventKeyName as last argument separately for backwards compat - */ function checkKeyCodes ( eventKeyCode, key, @@ -3681,9 +3641,6 @@ function checkKeyCodes ( /* */ -/** - * Runtime helper for merging v-bind="object" into a VNode's data. - */ function bindObjectProps ( data, tag, @@ -3735,9 +3692,6 @@ function bindObjectProps ( /* */ -/** - * Runtime helper for rendering static trees. - */ function renderStatic ( index, isInFor @@ -3933,7 +3887,6 @@ function mergeProps (to, from) { /* */ -// hooks to be invoked on component VNodes during patch var componentVNodeHooks = { init: function init ( vnode, @@ -4391,6 +4344,7 @@ var uid$1 = 0; function initMixin (Vue) { Vue.prototype._init = function (options) { + /* 当前Vue实例 */ var vm = this; // a uid vm._uid = uid$1++; @@ -4520,20 +4474,30 @@ function dedupe (latest, extended, sealed) { } } +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-28 16:45:11 + * @FilePath: \vue\src\core\instance\index.js + * @Description: + */ function Vue$3 (options) { if ("development" !== 'production' && !(this instanceof Vue$3) ) { + // 函数有五种调用模式!! + // 判断是否是用new调用的 warn('Vue is a constructor and should be called with the `new` keyword'); } this._init(options); } - -initMixin(Vue$3); -stateMixin(Vue$3); -eventsMixin(Vue$3); -lifecycleMixin(Vue$3); -renderMixin(Vue$3); +console.log(Vue$3); +initMixin(Vue$3); // 挂载初始化方法 +stateMixin(Vue$3);// 挂载状态处理方法 +eventsMixin(Vue$3);// 挂载事件的方法 +lifecycleMixin(Vue$3);// 挂载生命周期 +renderMixin(Vue$3);// 挂载渲染 /* */ @@ -4873,6 +4837,7 @@ function initGlobalAPI (Vue) { initAssetRegisters(Vue); } +// Vue 构造函数 initGlobalAPI(Vue$3); Object.defineProperty(Vue$3.prototype, '$isServer', { @@ -4890,8 +4855,6 @@ Vue$3.version = '2.5.0'; /* */ -// these are reserved for web because they are directly compiled away -// during template compilation var isReservedAttr = makeMap('style,class'); // attributes that should be using props for binding @@ -5088,9 +5051,6 @@ var isTextInputType = makeMap('text,number,password,search,email,tel,url'); /* */ -/** - * Query an element selector if it's not an element already. - */ function query (el) { if (typeof el === 'string') { var selected = document.querySelector(el); @@ -6694,10 +6654,6 @@ function genDefaultModel ( /* */ -// normalize v-model event tokens that can only be determined at runtime. -// it's important to place the event as the first in the array because -// the whole point is ensuring the v-model callback gets called before -// user-attached handlers. function normalizeEvents (on) { /* istanbul ignore if */ if (isDef(on[RANGE_TOKEN])) { @@ -7578,8 +7534,6 @@ var platformModules = [ /* */ -// the directive module should be applied last, after all -// built-in modules have been applied. var modules = platformModules.concat(baseModules); var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules }); @@ -7589,7 +7543,6 @@ var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules }); * properties to Elements. */ -/* istanbul ignore if */ if (isIE9) { // http://www.matts411.com/post/internet-explorer-9-oninput/ document.addEventListener('selectionchange', function () { @@ -7719,7 +7672,6 @@ function trigger (el, type) { /* */ -// recursively search for possible transition defined inside the component root function locateNode (vnode) { return vnode.componentInstance && (!vnode.data || !vnode.data.transition) ? locateNode(vnode.componentInstance._vnode) @@ -8143,9 +8095,18 @@ var platformComponents = { TransitionGroup: TransitionGroup }; +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:54 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-28 16:25:07 + * @FilePath: \vue\src\platforms\web\runtime\index.js + * @Description: + * 1.配置__patch__ + * 2.定义原始$mount方法 + */ /* */ -// install platform specific utils Vue$3.config.mustUseProp = mustUseProp; Vue$3.config.isReservedTag = isReservedTag; Vue$3.config.isReservedAttr = isReservedAttr; @@ -8157,9 +8118,11 @@ extend(Vue$3.options.directives, platformDirectives); extend(Vue$3.options.components, platformComponents); // install platform patch function -Vue$3.prototype.__patch__ = inBrowser ? patch : noop; +// 使用虚拟DOM更新真正的DOM的核心算法 +Vue$3.prototype.__patch__ = inBrowser ? patch : noop; // noop 无操作 // public mount method +// 原始的$mount方法,调用挂载的组件的方法 Vue$3.prototype.$mount = function ( el, hydrating @@ -8195,7 +8158,6 @@ Vue$3.nextTick(function () { /* */ -// check whether current browser encodes a char inside attribute values function shouldDecode (content, encoded) { var div = document.createElement('div'); div.innerHTML = "
"; @@ -8377,7 +8339,6 @@ var isNonPhrasingTag = makeMap( * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js */ -// Regular Expressions for parsing tags and attributes var attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName // but for Vue templates we can enforce a simple charset @@ -10135,8 +10096,6 @@ function transformSpecialNewlines (text) { /* */ -// these keywords should not appear inside expressions, but operators like -// typeof, instanceof and in are allowed var prohibitedKeywordRE = new RegExp('\\b' + ( 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' + 'super,throw,while,yield,delete,export,import,return,switch,default,' + @@ -10385,9 +10344,6 @@ function createCompilerCreator (baseCompile) { /* */ -// `createCompilerCreator` allows creating compilers that use alternative -// parser/optimizer/codegen, e.g the SSR optimizing compiler. -// Here we just export a default compiler using the default parts. var createCompiler = createCompilerCreator(function baseCompile ( template, options @@ -10501,3 +10457,4 @@ Vue$3.compile = compileToFunctions; return Vue$3; }))); +//# sourceMappingURL=vue.js.map diff --git a/examples/commits/index.html b/examples/commits/index.html index 0b355a28748..dfe40aef5bc 100644 --- a/examples/commits/index.html +++ b/examples/commits/index.html @@ -19,7 +19,7 @@ } - +
diff --git a/package.json b/package.json index a4bfbf7c7a6..675f7bcdf21 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "types/*.d.ts" ], "scripts": { - "dev": "rollup -w -c build/config.js --environment TARGET:web-full-dev", + "dev": "rollup -w -c build/config.js --sourcemap --environment TARGET:web-full-dev", "dev:cjs": "rollup -w -c build/config.js --environment TARGET:web-runtime-cjs", "dev:esm": "rollup -w -c build/config.js --environment TARGET:web-runtime-esm", "dev:test": "karma start test/unit/karma.dev.config.js", diff --git a/src/core/index.js b/src/core/index.js index b2a0cb8c5a2..7e3cf798c17 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -1,7 +1,9 @@ +// Vue 构造函数 import Vue from './instance/index' import { initGlobalAPI } from './global-api/index' import { isServerRendering } from 'core/util/env' +// 初始化全局API initGlobalAPI(Vue) Object.defineProperty(Vue.prototype, '$isServer', { diff --git a/src/core/instance/index.js b/src/core/instance/index.js index 8a4402317f5..1c9a7440185 100644 --- a/src/core/instance/index.js +++ b/src/core/instance/index.js @@ -1,3 +1,11 @@ +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-28 16:45:11 + * @FilePath: \vue\src\core\instance\index.js + * @Description: + */ import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from './render' @@ -5,19 +13,26 @@ import { eventsMixin } from './events' import { lifecycleMixin } from './lifecycle' import { warn } from '../util/index' +/** + * Vue 构造函数 + * @param {*} options + * + */ function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { + // 函数有五种调用模式!! + // 判断是否是用new调用的 warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) } - -initMixin(Vue) -stateMixin(Vue) -eventsMixin(Vue) -lifecycleMixin(Vue) -renderMixin(Vue) +console.log(Vue) +initMixin(Vue) // 挂载初始化方法 +stateMixin(Vue)// 挂载状态处理方法 +eventsMixin(Vue)// 挂载事件的方法 +lifecycleMixin(Vue)// 挂载生命周期 +renderMixin(Vue)// 挂载渲染 export default Vue diff --git a/src/core/instance/init.js b/src/core/instance/init.js index f4b4db5742a..b52ed7795d9 100644 --- a/src/core/instance/init.js +++ b/src/core/instance/init.js @@ -10,10 +10,12 @@ import { initLifecycle, callHook } from './lifecycle' import { initProvide, initInjections } from './inject' import { extend, mergeOptions, formatComponentName } from '../util/index' +// 每个类型的实例都有一个唯一值 let uid = 0 export function initMixin (Vue: Class) { Vue.prototype._init = function (options?: Object) { + /* 当前Vue实例 */ const vm: Component = this // a uid vm._uid = uid++ diff --git a/src/platforms/web/runtime/index.js b/src/platforms/web/runtime/index.js index 3751acecc53..c501f778498 100644 --- a/src/platforms/web/runtime/index.js +++ b/src/platforms/web/runtime/index.js @@ -1,3 +1,13 @@ +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:54 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-28 16:25:07 + * @FilePath: \vue\src\platforms\web\runtime\index.js + * @Description: + * 1.配置__patch__ + * 2.定义原始$mount方法 + */ /* @flow */ import Vue from 'core/index' @@ -31,9 +41,11 @@ extend(Vue.options.directives, platformDirectives) extend(Vue.options.components, platformComponents) // install platform patch function -Vue.prototype.__patch__ = inBrowser ? patch : noop +// 使用虚拟DOM更新真正的DOM的核心算法 +Vue.prototype.__patch__ = inBrowser ? patch : noop // noop 无操作 // public mount method +// 原始的$mount方法,调用挂载的组件的方法 Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean From e620bc4ba651c926454a30b31611717385ede8ac Mon Sep 17 00:00:00 2001 From: gogoend Date: Sun, 28 Jun 2020 18:10:21 +0800 Subject: [PATCH 02/32] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=8A=A0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=20=E5=88=B0initState?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/vue.js | 49 ++++++++----------- src/core/instance/init.js | 37 +++++--------- src/core/instance/render.js | 4 +- src/core/instance/state.js | 6 ++- .../web/entry-runtime-with-compiler.js | 4 +- 5 files changed, 43 insertions(+), 57 deletions(-) diff --git a/dist/vue.js b/dist/vue.js index c4b57558e14..6c2b54fa2a2 100644 --- a/dist/vue.js +++ b/dist/vue.js @@ -3173,8 +3173,10 @@ function proxy (target, sourceKey, key) { function initState (vm) { vm._watchers = []; var opts = vm.$options; + // 处理options.props的成员,一般定义组件的时候,用于定义对外的成员,初学少用,其处理逻辑与data 类似 if (opts.props) { initProps(vm, opts.props); } if (opts.methods) { initMethods(vm, opts.methods); } + // 响应式化data if (opts.data) { initData(vm); } else { @@ -3224,7 +3226,7 @@ function initProps (vm, propsOptions) { // during Vue.extend(). We only need to proxy props defined at // instantiation here. if (!(key in vm)) { - proxy(vm, "_props", key); + proxy(vm, "_props", key); // 将_props 上的成员映射到Vue实例上,是的不需要app._props.xxx 来方法,直接使用app.xxx来访问 } }; @@ -4249,10 +4251,10 @@ function initRender (vm) { // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize // internal version is used by render functions compiled from templates - vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); }; + vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };// 系统创建元素 // normalization is always applied for the public version, used in // user-written render functions. - vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); }; + vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); }; // 用户提供了option.render(函数)属性时创建元素的方法。如果手动提供了 option.render 就不会触发扩展的$mount 方法去生成render // $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated @@ -4349,14 +4351,6 @@ function initMixin (Vue) { // a uid vm._uid = uid$1++; - var startTag, endTag; - /* istanbul ignore if */ - if ("development" !== '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 @@ -4366,7 +4360,7 @@ function initMixin (Vue) { // internal component options needs special treatment. initInternalComponent(vm, options); } else { - vm.$options = mergeOptions( + vm.$options = mergeOptions( // 合并,为option增加属性 resolveConstructorOptions(vm.constructor), options || {}, vm @@ -4378,24 +4372,21 @@ function initMixin (Vue) { } // expose real self vm._self = vm; - initLifecycle(vm); - initEvents(vm); - initRender(vm); - callHook(vm, 'beforeCreate'); - initInjections(vm); // resolve injections before data/props - initState(vm); + initLifecycle(vm);// 初始化生命周期的一些状态变量 + initEvents(vm);// 初始化事件的容器 + initRender(vm);// 初始化创建元素的方法 + callHook(vm, 'beforeCreate');// 调用生命周期函数 + initInjections(vm); // resolve injections before data/props //初始化注入器略 + initState(vm); // 重点,初始化状态数据( data,property 等) initProvide(vm); // resolve provide after data/props - callHook(vm, 'created'); - - /* istanbul ignore if */ - if ("development" !== 'production' && config.performance && mark) { - vm._name = formatComponentName(vm, false); - mark(endTag); - measure(("vue " + (vm._name) + " init"), startTag, endTag); - } - + callHook(vm, 'created');// 生命周期函数的调用 + // 上方是组件的创建 if (vm.$options.el) { vm.$mount(vm.$options.el); + // 创建完成后进行挂载 + // 会先调用扩展的那个$mount 方法,生成render + // 再调用原始的$mount 方法,获得元素,再调用mountComponent方法 + // 这两个方法都定义在platforms/web 里面 } }; } @@ -10370,6 +10361,8 @@ var idToTemplate = cached(function (id) { return el && el.innerHTML }); +// 将原始的$mount 存储起来,然后实现了新的$moute +// 然后再调用$mount 就是在扩展原有的$mount 方法 var mount = Vue$3.prototype.$mount; Vue$3.prototype.$mount = function ( el, @@ -10417,7 +10410,7 @@ Vue$3.prototype.$mount = function ( if ("development" !== 'production' && config.performance && mark) { mark('compile'); } - + // 这里就是 生成带有缓存功能的函数,该函数用 于生成虚拟DON var ref = compileToFunctions(template, { shouldDecodeNewlines: shouldDecodeNewlines, delimiters: options.delimiters, diff --git a/src/core/instance/init.js b/src/core/instance/init.js index b52ed7795d9..123d0f88144 100644 --- a/src/core/instance/init.js +++ b/src/core/instance/init.js @@ -20,14 +20,6 @@ export function initMixin (Vue: Class) { // 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 @@ -37,7 +29,7 @@ export function initMixin (Vue: Class) { // internal component options needs special treatment. initInternalComponent(vm, options) } else { - vm.$options = mergeOptions( + vm.$options = mergeOptions( // 合并,为option增加属性 resolveConstructorOptions(vm.constructor), options || {}, vm @@ -51,24 +43,21 @@ export function initMixin (Vue: Class) { } // expose real self vm._self = vm - initLifecycle(vm) - initEvents(vm) - initRender(vm) - callHook(vm, 'beforeCreate') - initInjections(vm) // resolve injections before data/props - initState(vm) + initLifecycle(vm)// 初始化生命周期的一些状态变量 + initEvents(vm)// 初始化事件的容器 + initRender(vm)// 初始化创建元素的方法 + callHook(vm, 'beforeCreate')// 调用生命周期函数 + initInjections(vm) // resolve injections before data/props //初始化注入器略 + initState(vm) // 重点,初始化状态数据( data,property 等) 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) - } - + callHook(vm, 'created')// 生命周期函数的调用 + // 上方是组件的创建 if (vm.$options.el) { vm.$mount(vm.$options.el) + // 创建完成后进行挂载 + // 会先调用扩展的那个$mount 方法,生成render + // 再调用原始的$mount 方法,获得元素,再调用mountComponent方法 + // 这两个方法都定义在platforms/web 里面 } } } diff --git a/src/core/instance/render.js b/src/core/instance/render.js index 5b7df25c8dd..57a3d466697 100644 --- a/src/core/instance/render.js +++ b/src/core/instance/render.js @@ -26,10 +26,10 @@ export function initRender (vm: Component) { // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize // internal version is used by render functions compiled from templates - vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) + vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)// 系统创建元素 // normalization is always applied for the public version, used in // user-written render functions. - vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) + vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) // 用户提供了option.render(函数)属性时创建元素的方法。如果手动提供了 option.render 就不会触发扩展的$mount 方法去生成render // $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated diff --git a/src/core/instance/state.js b/src/core/instance/state.js index 4fa6f15bac8..63c3321c30c 100644 --- a/src/core/instance/state.js +++ b/src/core/instance/state.js @@ -48,8 +48,10 @@ export function proxy (target: Object, sourceKey: string, key: string) { export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options + // 处理options.props的成员,一般定义组件的时候,用于定义对外的成员,初学少用,其处理逻辑与data 类似 if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) + // 响应式化data if (opts.data) { initData(vm) } else { @@ -95,13 +97,13 @@ function initProps (vm: Component, propsOptions: Object) { } }) } else { - defineReactive(props, key, value) + defineReactive(props, key, value) // 属性响应式化 } // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. if (!(key in vm)) { - proxy(vm, `_props`, key) + proxy(vm, `_props`, key) // 将_props 上的成员映射到Vue实例上,是的不需要app._props.xxx 来方法,直接使用app.xxx来访问 } } observerState.shouldConvert = true diff --git a/src/platforms/web/entry-runtime-with-compiler.js b/src/platforms/web/entry-runtime-with-compiler.js index 5ca67b6e8e2..fbe22d26ec1 100644 --- a/src/platforms/web/entry-runtime-with-compiler.js +++ b/src/platforms/web/entry-runtime-with-compiler.js @@ -14,6 +14,8 @@ const idToTemplate = cached(id => { return el && el.innerHTML }) +// 将原始的$mount 存储起来,然后实现了新的$moute +// 然后再调用$mount 就是在扩展原有的$mount 方法 const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, @@ -61,7 +63,7 @@ Vue.prototype.$mount = function ( if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } - + // 这里就是 生成带有缓存功能的函数,该函数用 于生成虚拟DON const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, delimiters: options.delimiters, From cd50e4ec053824bbdc0dd0d544426a97dbdf6430 Mon Sep 17 00:00:00 2001 From: gogoend Date: Sun, 28 Jun 2020 18:34:27 +0800 Subject: [PATCH 03/32] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E5=8A=A0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=20=E5=88=B0initData?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/vue.js | 12 ++++++++---- src/core/instance/state.js | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/dist/vue.js b/dist/vue.js index 6c2b54fa2a2..897797de582 100644 --- a/dist/vue.js +++ b/dist/vue.js @@ -3204,7 +3204,7 @@ function initProps (vm, propsOptions) { { var hyphenatedKey = hyphenate(key); if (isReservedAttribute(hyphenatedKey) || - config.isReservedAttr(hyphenatedKey)) { + config.isReservedAttr(hyphenatedKey)) { warn( ("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."), vm @@ -3269,14 +3269,17 @@ function initData (vm) { vm ); } else if (!isReserved(key)) { - proxy(vm, "_data", key); + proxy(vm, "_data", key); // 循环data所有属性,映射到Vue实例上, + // 就无需使用 app._data.xxx来访问属性 + // 而是直接使用app.data访问 } } // observe data - observe(data, true /* asRootData */); + observe(data, true /* asRootData */); // 响应式化 } function getData (data, vm) { + // 新版本修复了相关问题 #7573 disable dep collection, when invoking data getters try { return data.call(vm, vm) } catch (e) { @@ -3349,7 +3352,7 @@ function defineComputed ( : noop; } if ("development" !== 'production' && - sharedPropertyDefinition.set === noop) { + sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( ("Computed property \"" + key + "\" was assigned to but it has no setter."), @@ -3400,6 +3403,7 @@ function initMethods (vm, methods) { } } vm[key] = methods[key] == null ? noop : bind(methods[key], vm); + // 将methods 属性中的方法绑定上下文后挂载到vue实例上 } } diff --git a/src/core/instance/state.js b/src/core/instance/state.js index 63c3321c30c..09cc1ac2516 100644 --- a/src/core/instance/state.js +++ b/src/core/instance/state.js @@ -79,7 +79,7 @@ function initProps (vm: Component, propsOptions: Object) { if (process.env.NODE_ENV !== 'production') { const hyphenatedKey = hyphenate(key) if (isReservedAttribute(hyphenatedKey) || - config.isReservedAttr(hyphenatedKey)) { + config.isReservedAttr(hyphenatedKey)) { warn( `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`, vm @@ -144,14 +144,17 @@ function initData (vm: Component) { vm ) } else if (!isReserved(key)) { - proxy(vm, `_data`, key) + proxy(vm, `_data`, key) // 循环data所有属性,映射到Vue实例上, + // 就无需使用 app._data.xxx来访问属性 + // 而是直接使用app.data访问 } } // observe data - observe(data, true /* asRootData */) + observe(data, true /* asRootData */) // 响应式化 } function getData (data: Function, vm: Component): any { + // 新版本修复了相关问题 #7573 disable dep collection, when invoking data getters try { return data.call(vm, vm) } catch (e) { @@ -224,7 +227,7 @@ export function defineComputed ( : noop } if (process.env.NODE_ENV !== 'production' && - sharedPropertyDefinition.set === noop) { + sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, @@ -275,6 +278,7 @@ function initMethods (vm: Component, methods: Object) { } } vm[key] = methods[key] == null ? noop : bind(methods[key], vm) + // 将methods 属性中的方法绑定上下文后挂载到vue实例上 } } From ae8777a0700512902c3eccac03ebe7a83745c0bf Mon Sep 17 00:00:00 2001 From: gogoend Date: Mon, 29 Jun 2020 21:37:01 +0800 Subject: [PATCH 04/32] =?UTF-8?q?=E5=AF=B9=E8=BF=99=E4=BA=9B=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E8=BF=9B=E8=A1=8C=E6=B3=A8=E9=87=8A=EF=BC=8C=E7=9C=8B?= =?UTF-8?q?=E4=BA=86=E7=9C=8BArray=E9=87=8D=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/vue.js | 59 +++++++++++++++++++++++++++++++++- src/core/instance/state.js | 4 ++- src/core/observer/array.js | 11 +++++++ src/core/observer/dep.js | 9 ++++++ src/core/observer/index.js | 10 ++++++ src/core/observer/scheduler.js | 9 ++++++ src/core/observer/watcher.js | 9 ++++++ src/core/util/lang.js | 10 ++++++ 8 files changed, 119 insertions(+), 2 deletions(-) diff --git a/dist/vue.js b/dist/vue.js index 897797de582..373c80c265e 100644 --- a/dist/vue.js +++ b/dist/vue.js @@ -426,6 +426,14 @@ var config = ({ _lifecycleHooks: LIFECYCLE_HOOKS }); +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:33:31 + * @FilePath: \vue\src\core\util\lang.js + * @Description: + */ /* */ var emptyObject = Object.freeze({}); @@ -440,6 +448,8 @@ function isReserved (str) { /** * Define a property. + * 使用参数,将可枚举性变为可控的;递归,使用循环,获得key时,不可枚举属性是访问不到的 + * 在vue中实例存在大量的循环引用,递归遍历对象成员的时候,避免死递归 */ function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { @@ -775,6 +785,15 @@ if (typeof Set !== 'undefined' && isNative(Set)) { }()); } +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:22:47 + * @FilePath: \vue\src\core\observer\dep.js + * @Description:Dep类 + */ + /* */ @@ -918,6 +937,14 @@ function cloneVNodes (vnodes, deep) { return res } +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:35:11 + * @FilePath: \vue\src\core\observer\array.js + * @Description:重写数组方法,创建含有重写数组方法的数组,让所有的响应式数据数组继承自该数组 + */ /* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype @@ -959,6 +986,16 @@ var arrayMethods = Object.create(arrayProto);[ }); }); +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:26:23 + * @FilePath: \vue\src\core\observer\index.js + * @Description:Observer类,observe的工厂函数. + * traverse.js递归遍历响应式数据.目的是触发依赖收集. + */ + /* */ var arrayKeys = Object.getOwnPropertyNames(arrayMethods); @@ -2775,6 +2812,15 @@ function callHook (vm, hook) { } } +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:24:40 + * @FilePath: \vue\src\core\observer\scheduler.js + * @Description:vue中的任务调度的工具,watcher执行的核心 + */ + /* */ @@ -2915,6 +2961,15 @@ function queueWatcher (watcher) { } } +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:26:43 + * @FilePath: \vue\src\core\observer\watcher.js + * @Description:Watcher类 + */ + /* */ var uid$2 = 0; @@ -3236,8 +3291,9 @@ function initProps (vm, propsOptions) { function initData (vm) { var data = vm.$options.data; + // 将data挂载到实例的_data上 data = vm._data = typeof data === 'function' - ? getData(data, vm) + ? getData(data, vm) // 调用函数,获得返回值,计算data : data || {}; if (!isPlainObject(data)) { data = {}; @@ -3254,6 +3310,7 @@ function initData (vm) { var i = keys.length; while (i--) { var key = keys[i]; + /** 这里判断只是为了避免props, data, method 等数据发生冲突,同名的问题*/ { if (methods && hasOwn(methods, key)) { warn( diff --git a/src/core/instance/state.js b/src/core/instance/state.js index 09cc1ac2516..388f6a4897b 100644 --- a/src/core/instance/state.js +++ b/src/core/instance/state.js @@ -111,8 +111,9 @@ function initProps (vm: Component, propsOptions: Object) { function initData (vm: Component) { let data = vm.$options.data + // 将data挂载到实例的_data上 data = vm._data = typeof data === 'function' - ? getData(data, vm) + ? getData(data, vm) // 调用函数,获得返回值,计算data : data || {} if (!isPlainObject(data)) { data = {} @@ -129,6 +130,7 @@ function initData (vm: Component) { let i = keys.length while (i--) { const key = keys[i] + /** 这里判断只是为了避免props, data, method 等数据发生冲突,同名的问题*/ if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { warn( diff --git a/src/core/observer/array.js b/src/core/observer/array.js index 1d22870a9c6..54e5f42a8da 100644 --- a/src/core/observer/array.js +++ b/src/core/observer/array.js @@ -1,3 +1,11 @@ +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:35:11 + * @FilePath: \vue\src\core\observer\array.js + * @Description:重写数组方法,创建含有重写数组方法的数组,让所有的响应式数据数组继承自该数组 + */ /* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype @@ -8,6 +16,9 @@ import { def } from '../util/index' const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) +// arrayMethods就是继承自Array. prototype的数组 +// 只需要让响应式数组继承自arrayMethods + /** * Intercept mutating methods and emit events */ diff --git a/src/core/observer/dep.js b/src/core/observer/dep.js index 5d55e9cd421..f87c4b01c7d 100644 --- a/src/core/observer/dep.js +++ b/src/core/observer/dep.js @@ -1,3 +1,12 @@ +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:22:47 + * @FilePath: \vue\src\core\observer\dep.js + * @Description:Dep类 + */ + /* @flow */ import type Watcher from './watcher' diff --git a/src/core/observer/index.js b/src/core/observer/index.js index 11de2d89f79..1bba53fb530 100644 --- a/src/core/observer/index.js +++ b/src/core/observer/index.js @@ -1,3 +1,13 @@ +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:26:23 + * @FilePath: \vue\src\core\observer\index.js + * @Description:Observer类,observe的工厂函数. + * traverse.js递归遍历响应式数据.目的是触发依赖收集. + */ + /* @flow */ import Dep from './dep' diff --git a/src/core/observer/scheduler.js b/src/core/observer/scheduler.js index fce86e5f40b..df65dab28d5 100644 --- a/src/core/observer/scheduler.js +++ b/src/core/observer/scheduler.js @@ -1,3 +1,12 @@ +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:24:40 + * @FilePath: \vue\src\core\observer\scheduler.js + * @Description:vue中的任务调度的工具,watcher执行的核心 + */ + /* @flow */ import type Watcher from './watcher' diff --git a/src/core/observer/watcher.js b/src/core/observer/watcher.js index 40253149c34..2ad6ed6c6c6 100644 --- a/src/core/observer/watcher.js +++ b/src/core/observer/watcher.js @@ -1,3 +1,12 @@ +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:26:43 + * @FilePath: \vue\src\core\observer\watcher.js + * @Description:Watcher类 + */ + /* @flow */ import { queueWatcher } from './scheduler' diff --git a/src/core/util/lang.js b/src/core/util/lang.js index 3be32d1076b..9d659a32148 100644 --- a/src/core/util/lang.js +++ b/src/core/util/lang.js @@ -1,3 +1,11 @@ +/* + * @Author: gogoend + * @Date: 2020-02-02 01:34:53 + * @LastEditors: gogoend + * @LastEditTime: 2020-06-29 21:33:31 + * @FilePath: \vue\src\core\util\lang.js + * @Description: + */ /* @flow */ export const emptyObject = Object.freeze({}) @@ -12,6 +20,8 @@ export function isReserved (str: string): boolean { /** * Define a property. + * 使用参数,将可枚举性变为可控的;递归,使用循环,获得key时,不可枚举属性是访问不到的 + * 在vue中实例存在大量的循环引用,递归遍历对象成员的时候,避免死递归 */ export function def (obj: Object, key: string, val: any, enumerable?: boolean) { Object.defineProperty(obj, key, { From a909fceffef9ae935d8b03ad41539755e353560e Mon Sep 17 00:00:00 2001 From: gogoend Date: Mon, 29 Jun 2020 22:26:01 +0800 Subject: [PATCH 05/32] =?UTF-8?q?=E4=B8=BA=E5=B1=9E=E6=80=A7=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=93=8D=E5=BA=94=E5=BC=8F=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/vue.js | 27 ++++++++++++++++++++------- src/core/observer/dep.js | 7 +++++-- src/core/observer/index.js | 22 ++++++++++++++++------ 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/dist/vue.js b/dist/vue.js index 373c80c265e..d03e926928f 100644 --- a/dist/vue.js +++ b/dist/vue.js @@ -789,7 +789,7 @@ if (typeof Set !== 'undefined' && isNative(Set)) { * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 21:22:47 + * @LastEditTime: 2020-06-29 21:38:24 * @FilePath: \vue\src\core\observer\dep.js * @Description:Dep类 */ @@ -821,7 +821,10 @@ Dep.prototype.depend = function depend () { Dep.target.addDep(this); } }; - +/** +*每一个属性都会包含一个dep实例 +*这个dep实例会记录下参与计算或渲染的watcher +*/ Dep.prototype.notify = function notify () { // stabilize the subscriber list first var subs = this.subs.slice(); @@ -990,7 +993,7 @@ var arrayMethods = Object.create(arrayProto);[ * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 21:26:23 + * @LastEditTime: 2020-06-29 22:08:04 * @FilePath: \vue\src\core\observer\index.js * @Description:Observer类,observe的工厂函数. * traverse.js递归遍历响应式数据.目的是触发依赖收集. @@ -1020,15 +1023,18 @@ var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; - def(value, '__ob__', this); + def(value, '__ob__', this); // 技巧:逻辑上等价于value.__ob__ = this + + // 真正响应式化的逻辑 if (Array.isArray(value)) { + // 判断浏览器是否兼容__proto__ var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); - this.observeArray(value); + this.observeArray(value); // 遍历数组的元素,进行递归observe } else { - this.walk(value); + this.walk(value); // 遍历对象的属性,递归observe } }; @@ -1058,16 +1064,18 @@ Observer.prototype.observeArray = function observeArray (items) { /** * Augment an target Object or Array by intercepting * the prototype chain using __proto__ + * 浏览器支持__proto__ */ function protoAugment (target, src, keys) { /* eslint-disable no-proto */ - target.__proto__ = src; + target.__proto__ = src; // 完成数组原型链修改 从而使得数组变成响应式的 pop, push, shift, unshift, /* eslint-enable no-proto */ } /** * Augment an target Object or Array by defining * hidden properties. + * 浏览器不支持__proto__ 就将这些方法直接混入到当前数组中,属性访问元素 */ /* istanbul ignore next */ function copyAugment (target, src, keys) { @@ -1081,6 +1089,11 @@ function copyAugment (target, src, keys) { * 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. + * 传入数据变为响应式对象 + * 算法描述: + * -先看对象是否含有 _ob__ ,并且是Observer 的实例( Vue中响应式对象的标记) + * -有,忽略 + * -没有,调用new Observer( value )。进行响应式化 */ function observe (value, asRootData) { if (!isObject(value) || value instanceof VNode) { diff --git a/src/core/observer/dep.js b/src/core/observer/dep.js index f87c4b01c7d..dee1cab513c 100644 --- a/src/core/observer/dep.js +++ b/src/core/observer/dep.js @@ -2,7 +2,7 @@ * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 21:22:47 + * @LastEditTime: 2020-06-29 21:38:24 * @FilePath: \vue\src\core\observer\dep.js * @Description:Dep类 */ @@ -41,7 +41,10 @@ export default class Dep { Dep.target.addDep(this) } } - +/** +*每一个属性都会包含一个dep实例 +*这个dep实例会记录下参与计算或渲染的watcher +*/ notify () { // stabilize the subscriber list first const subs = this.subs.slice() diff --git a/src/core/observer/index.js b/src/core/observer/index.js index 1bba53fb530..82a35b1cf9f 100644 --- a/src/core/observer/index.js +++ b/src/core/observer/index.js @@ -2,7 +2,7 @@ * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 21:26:23 + * @LastEditTime: 2020-06-29 22:08:04 * @FilePath: \vue\src\core\observer\index.js * @Description:Observer类,observe的工厂函数. * traverse.js递归遍历响应式数据.目的是触发依赖收集. @@ -43,7 +43,7 @@ export const observerState = { * collect dependencies and dispatches updates. */ export class Observer { - value: any; + value: any; // 循环引用:对象.__ob__, ob.value dep: Dep; vmCount: number; // number of vms that has this object as root $data @@ -51,15 +51,18 @@ export class Observer { this.value = value this.dep = new Dep() this.vmCount = 0 - def(value, '__ob__', this) + def(value, '__ob__', this) // 技巧:逻辑上等价于value.__ob__ = this + + // 真正响应式化的逻辑 if (Array.isArray(value)) { + // 判断浏览器是否兼容__proto__ const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) - this.observeArray(value) + this.observeArray(value) // 遍历数组的元素,进行递归observe } else { - this.walk(value) + this.walk(value) // 遍历对象的属性,递归observe } } @@ -90,16 +93,18 @@ export class Observer { /** * Augment an target Object or Array by intercepting * the prototype chain using __proto__ + * 浏览器支持__proto__ */ function protoAugment (target, src: Object, keys: any) { /* eslint-disable no-proto */ - target.__proto__ = src + target.__proto__ = src // 完成数组原型链修改 从而使得数组变成响应式的 pop, push, shift, unshift, /* eslint-enable no-proto */ } /** * Augment an target Object or Array by defining * hidden properties. + * 浏览器不支持__proto__ 就将这些方法直接混入到当前数组中,属性访问元素 */ /* istanbul ignore next */ function copyAugment (target: Object, src: Object, keys: Array) { @@ -113,6 +118,11 @@ function copyAugment (target: Object, src: Object, keys: Array) { * 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. + * 传入数据变为响应式对象 + * 算法描述: + * -先看对象是否含有 _ob__ ,并且是Observer 的实例( Vue中响应式对象的标记) + * -有,忽略 + * -没有,调用new Observer( value )。进行响应式化 */ export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { From f4fc526e8dc5e8911a3875bff0fca4a271792bd6 Mon Sep 17 00:00:00 2001 From: gogoend Date: Mon, 29 Jun 2020 23:11:54 +0800 Subject: [PATCH 06/32] =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=BC=8F=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/vue.js | 15 +++++++++------ src/core/observer/index.js | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/dist/vue.js b/dist/vue.js index d03e926928f..9602794c2b1 100644 --- a/dist/vue.js +++ b/dist/vue.js @@ -993,7 +993,7 @@ var arrayMethods = Object.create(arrayProto);[ * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 22:08:04 + * @LastEditTime: 2020-06-29 23:11:29 * @FilePath: \vue\src\core\observer\index.js * @Description:Observer类,observe的工厂函数. * traverse.js递归遍历响应式数据.目的是触发依赖收集. @@ -1129,6 +1129,7 @@ function defineReactive ( ) { var dep = new Dep(); + // 获得对象属性描述符 var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return @@ -1143,10 +1144,11 @@ function defineReactive ( enumerable: true, configurable: true, get: function reactiveGetter () { - var value = getter ? getter.call(obj) : val; + var value = getter ? getter.call(obj) : val; // 保证了如果已经定义的get方法可以被继承下来,不会丢失 if (Dep.target) { - dep.depend(); + dep.depend(); // 关联的当前属性 if (childOb) { + // 收集子属性 childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); @@ -1158,6 +1160,7 @@ function defineReactive ( set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ + // 若数据无变化,就不会派发更新 if (newVal === value || (newVal !== newVal && value !== value)) { return } @@ -1166,12 +1169,12 @@ function defineReactive ( customSetter(); } if (setter) { - setter.call(obj, newVal); + setter.call(obj, newVal); // 保证了如果已经定义的set方法可以被继承下来,不会丢失 } else { val = newVal; } - childOb = !shallow && observe(newVal); - dep.notify(); + childOb = !shallow && observe(newVal); // 对新值进行响应式化 + dep.notify(); // 派发更新 } }); } diff --git a/src/core/observer/index.js b/src/core/observer/index.js index 82a35b1cf9f..2f9ac8f9f21 100644 --- a/src/core/observer/index.js +++ b/src/core/observer/index.js @@ -2,7 +2,7 @@ * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 22:08:04 + * @LastEditTime: 2020-06-29 23:11:29 * @FilePath: \vue\src\core\observer\index.js * @Description:Observer类,observe的工厂函数. * traverse.js递归遍历响应式数据.目的是触发依赖收集. @@ -158,6 +158,7 @@ export function defineReactive ( ) { const dep = new Dep() + // 获得对象属性描述符 const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return @@ -172,10 +173,11 @@ export function defineReactive ( enumerable: true, configurable: true, get: function reactiveGetter () { - const value = getter ? getter.call(obj) : val + const value = getter ? getter.call(obj) : val // 保证了如果已经定义的get方法可以被继承下来,不会丢失 if (Dep.target) { - dep.depend() + dep.depend() // 关联的当前属性 if (childOb) { + // 收集子属性 childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) @@ -187,6 +189,7 @@ export function defineReactive ( set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ + // 若数据无变化,就不会派发更新 if (newVal === value || (newVal !== newVal && value !== value)) { return } @@ -195,12 +198,12 @@ export function defineReactive ( customSetter() } if (setter) { - setter.call(obj, newVal) + setter.call(obj, newVal) // 保证了如果已经定义的set方法可以被继承下来,不会丢失 } else { val = newVal } - childOb = !shallow && observe(newVal) - dep.notify() + childOb = !shallow && observe(newVal) // 对新值进行响应式化 + dep.notify() // 派发更新 } }) } From 207121db47700ad519bc573df798f17b2d9edf43 Mon Sep 17 00:00:00 2001 From: gogoend Date: Mon, 29 Jun 2020 23:13:22 +0800 Subject: [PATCH 07/32] =?UTF-8?q?initData=20=E8=87=AA=E4=B8=8A=E6=AC=A1?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=90=8E=E5=A4=A7=E8=87=B4=E5=91=8A=E4=B8=80?= =?UTF-8?q?=E6=AE=B5=E8=90=BDover?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/vue.js | 2 +- src/core/instance/state.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/vue.js b/dist/vue.js index 9602794c2b1..7498b81769b 100644 --- a/dist/vue.js +++ b/dist/vue.js @@ -3249,7 +3249,7 @@ function initState (vm) { if (opts.methods) { initMethods(vm, opts.methods); } // 响应式化data if (opts.data) { - initData(vm); + initData(vm); // ~自上次提交后大致告一段落over } else { observe(vm._data = {}, true /* asRootData */); } diff --git a/src/core/instance/state.js b/src/core/instance/state.js index 388f6a4897b..aee5206bba4 100644 --- a/src/core/instance/state.js +++ b/src/core/instance/state.js @@ -53,7 +53,7 @@ export function initState (vm: Component) { if (opts.methods) initMethods(vm, opts.methods) // 响应式化data if (opts.data) { - initData(vm) + initData(vm) // ~自上次提交后大致告一段落over } else { observe(vm._data = {}, true /* asRootData */) } From 9c96d2fc4069a6791dedfdbda6d242c9b96d0b66 Mon Sep 17 00:00:00 2001 From: gogoend Date: Tue, 30 Jun 2020 00:11:29 +0800 Subject: [PATCH 08/32] =?UTF-8?q?=E6=B5=8B=E8=AF=95Watcher=E3=80=82?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E6=92=AD=E6=94=BE=E5=88=B0P19=2030min?= =?UTF-8?q?=EF=BC=8C=E6=99=95=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/vue.js | 21 +++++++++++++++------ examples/modal/index.html | 17 +++++++++++++++-- src/core/observer/watcher.js | 36 ++++++++++++++++++++++++++---------- src/core/util/lang.js | 4 ++-- 4 files changed, 58 insertions(+), 20 deletions(-) diff --git a/dist/vue.js b/dist/vue.js index 7498b81769b..a12ab179784 100644 --- a/dist/vue.js +++ b/dist/vue.js @@ -430,7 +430,7 @@ var config = ({ * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 21:33:31 + * @LastEditTime: 2020-06-30 00:04:26 * @FilePath: \vue\src\core\util\lang.js * @Description: */ @@ -463,7 +463,7 @@ function def (obj, key, val, enumerable) { /** * Parse simple path. */ -var bailRE = /[^\w.$]/; +var bailRE = /[^\w.$]/; // 用于匹配xx.xx.xx.x function parsePath (path) { if (bailRE.test(path)) { return @@ -2981,7 +2981,7 @@ function queueWatcher (watcher) { * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 21:26:43 + * @LastEditTime: 2020-06-30 00:09:56 * @FilePath: \vue\src\core\observer\watcher.js * @Description:Watcher类 */ @@ -3022,12 +3022,12 @@ var Watcher = function Watcher ( this.newDepIds = new _Set(); this.expression = expOrFn.toString(); // parse expression for getter - if (typeof expOrFn === 'function') { + if (typeof expOrFn === 'function') { // watch前面那个key,如果找到了是函数,就是render 函数 this.getter = expOrFn; } else { - this.getter = parsePath(expOrFn); + this.getter = parsePath(expOrFn); // 找不到的话需要去解析路径看一看 if (!this.getter) { - this.getter = function () {}; + this.getter = function () { }; "development" !== 'production' && warn( "Failed watching path: \"" + expOrFn + "\" " + 'Watcher only accepts simple dot-delimited paths. ' + @@ -3036,6 +3036,7 @@ var Watcher = function Watcher ( ); } } + // 如果是lazy就什么也不做,否则就立即调用getter 函数求值( expOrFn ) this.value = this.lazy ? undefined : this.get(); @@ -3045,6 +3046,7 @@ var Watcher = function Watcher ( * Evaluate the getter, and re-collect dependencies. */ Watcher.prototype.get = function get () { + // 执行前把watcher放到全局作用域 pushTarget(this); var value; var vm = this.vm; @@ -3062,7 +3064,9 @@ Watcher.prototype.get = function get () { if (this.deep) { traverse(value); } + // 执行后把watcher从全局作用域移除 popTarget(); + // "清空"关联的dep数据 this.cleanupDeps(); } return value @@ -3193,6 +3197,11 @@ Watcher.prototype.teardown = function teardown () { } }; +/** + * Recursively traverse an object to evoke all converted + * getters, so that every nested property inside the object + * is collected as a "deep" dependency. + */ var seenObjects = new _Set(); function traverse (val) { seenObjects.clear(); diff --git a/examples/modal/index.html b/examples/modal/index.html index e2121617646..fcfb78dba2c 100644 --- a/examples/modal/index.html +++ b/examples/modal/index.html @@ -1,10 +1,18 @@ + Vue.js modal component example - + @@ -61,10 +69,15 @@

custom header

}) // start app - new Vue({ + a = new Vue({ el: '#app', data: { showModal: false + }, + watch: { + showModal (n, o) { + debugger + } } }) diff --git a/src/core/observer/watcher.js b/src/core/observer/watcher.js index 2ad6ed6c6c6..4dc373579b4 100644 --- a/src/core/observer/watcher.js +++ b/src/core/observer/watcher.js @@ -2,7 +2,7 @@ * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 21:26:43 + * @LastEditTime: 2020-06-30 00:09:56 * @FilePath: \vue\src\core\observer\watcher.js * @Description:Watcher类 */ @@ -32,21 +32,33 @@ let uid = 0 */ export default class Watcher { vm: Component; - expression: string; - cb: Function; + expression: string; // 关联表达式或渲染方法体 + cb: Function;// 回调函数 id: number; deep: boolean; user: boolean; - lazy: boolean; + lazy: boolean;// 计算属性,和watch 来控制不要让Watcher 立即执行 sync: boolean; dirty: boolean; active: boolean; + + // 在Vue中使用了二次提交的概念 + // 每次在数据渲染或计算的时候就会访问响应式的数据,就会进行依赖收集 + // 就将关联的Watcher 与dep相关联, + // 在数据发生变化的时候,根据dep找到关联的watcher, 依次调用update + // 执行完成后会清空watcher + deps: Array; - newDeps: Array; depIds: ISet; + + newDeps: Array; newDepIds: ISet; - getter: Function; - value: any; + + // 旧版本(2.5.0)里没有 + // before: ?Function; //在定义Vue 构造函数的时候,传入的watch函数体(值发生变化时的回调函数,function(nVal,oVal){}) + + getter: Function; // 就是渲染函数(模板或组件的渲染)或计算函数(watch) // 计算函数,watch中的对象路径'a,b,c'写法 + value: any;// 如果是渲染函数,value 无效;如果是计算属性,就会有一个值,值就存储在value 中 constructor ( vm: Component, @@ -77,12 +89,12 @@ export default class Watcher { ? expOrFn.toString() : '' // parse expression for getter - if (typeof expOrFn === 'function') { + if (typeof expOrFn === 'function') { // watch前面那个key,如果找到了是函数,就是render 函数 this.getter = expOrFn } else { - this.getter = parsePath(expOrFn) + this.getter = parsePath(expOrFn) // 找不到的话需要去解析路径看一看 if (!this.getter) { - this.getter = function () {} + this.getter = function () { } process.env.NODE_ENV !== 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + @@ -91,6 +103,7 @@ export default class Watcher { ) } } + // 如果是lazy就什么也不做,否则就立即调用getter 函数求值( expOrFn ) this.value = this.lazy ? undefined : this.get() @@ -100,6 +113,7 @@ export default class Watcher { * Evaluate the getter, and re-collect dependencies. */ get () { + // 执行前把watcher放到全局作用域 pushTarget(this) let value const vm = this.vm @@ -117,7 +131,9 @@ export default class Watcher { if (this.deep) { traverse(value) } + // 执行后把watcher从全局作用域移除 popTarget() + // "清空"关联的dep数据 this.cleanupDeps() } return value diff --git a/src/core/util/lang.js b/src/core/util/lang.js index 9d659a32148..935aabb70aa 100644 --- a/src/core/util/lang.js +++ b/src/core/util/lang.js @@ -2,7 +2,7 @@ * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 21:33:31 + * @LastEditTime: 2020-06-30 00:04:26 * @FilePath: \vue\src\core\util\lang.js * @Description: */ @@ -35,7 +35,7 @@ export function def (obj: Object, key: string, val: any, enumerable?: boolean) { /** * Parse simple path. */ -const bailRE = /[^\w.$]/ +const bailRE = /[^\w.$]/ // 用于匹配xx.xx.xx.x export function parsePath (path: string): any { if (bailRE.test(path)) { return From 0b93f2387daaac46efe2c9e8c1fb72e14db9cdbb Mon Sep 17 00:00:00 2001 From: gogoend Date: Tue, 30 Jun 2020 00:23:26 +0800 Subject: [PATCH 09/32] =?UTF-8?q?addDeep=E5=BC=80=E5=A7=8B=EF=BC=9BWatcher?= =?UTF-8?q?=E5=91=8A=E4=B8=80=E6=AE=B5=E8=90=BD=E3=80=82cb:=20Function?= =?UTF-8?q?=E5=92=8C=20before:=20=3FFunction=20=E6=9C=89=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/vue.js | 25 +++++++++++-------------- src/core/observer/watcher.js | 24 +++++++++++++----------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/dist/vue.js b/dist/vue.js index a12ab179784..13ec9c903b4 100644 --- a/dist/vue.js +++ b/dist/vue.js @@ -2981,7 +2981,7 @@ function queueWatcher (watcher) { * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-30 00:09:56 + * @LastEditTime: 2020-06-30 00:22:23 * @FilePath: \vue\src\core\observer\watcher.js * @Description:Watcher类 */ @@ -3079,9 +3079,9 @@ Watcher.prototype.addDep = function addDep (dep) { var id = dep.id; if (!this.newDepIds.has(id)) { this.newDepIds.add(id); - this.newDeps.push(dep); + this.newDeps.push(dep); // 让watcher关联到dep if (!this.depIds.has(id)) { - dep.addSub(this); + dep.addSub(this); // 让dep关联到watcher } } }; @@ -3095,7 +3095,7 @@ Watcher.prototype.cleanupDeps = function cleanupDeps () { var i = this.deps.length; while (i--) { var dep = this$1.deps[i]; - if (!this$1.newDepIds.has(dep.id)) { + if (!this$1.newDepIds.has(dep.id)) { // 在二次提交中归档就是让旧的deps 和新的 newDeps-致 dep.removeSub(this$1); } } @@ -3104,7 +3104,7 @@ Watcher.prototype.cleanupDeps = function cleanupDeps () { this.newDepIds = tmp; this.newDepIds.clear(); tmp = this.deps; - this.deps = this.newDeps; + this.deps = this.newDeps; // 同步处理 this.newDeps = tmp; this.newDeps.length = 0; }; @@ -3115,22 +3115,24 @@ Watcher.prototype.cleanupDeps = function cleanupDeps () { */ Watcher.prototype.update = function update () { /* istanbul ignore else */ - if (this.lazy) { + if (this.lazy) { // 主要针对计算属性,一 般用于求值计算 this.dirty = true; - } else if (this.sync) { + } else if (this.sync) { // 同步,主要用于SSR,同步就表示立即计算 this.run(); } else { - queueWatcher(this); + queueWatcher(this); // 一般浏览器中的异步运行,本质上就是异步执行run //类比: setTimeout( () => this . run(), } }; /** * Scheduler job interface. * Will be called by the scheduler. + * 调用get求值或渲染,如果求值,新旧值不同,触发cb */ Watcher.prototype.run = function run () { if (this.active) { - var value = this.get(); + var value = this.get(); // 要么渲染,要么求值 + // 如果值不一样,触发cb if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even @@ -3197,11 +3199,6 @@ Watcher.prototype.teardown = function teardown () { } }; -/** - * Recursively traverse an object to evoke all converted - * getters, so that every nested property inside the object - * is collected as a "deep" dependency. - */ var seenObjects = new _Set(); function traverse (val) { seenObjects.clear(); diff --git a/src/core/observer/watcher.js b/src/core/observer/watcher.js index 4dc373579b4..28ad9336f5a 100644 --- a/src/core/observer/watcher.js +++ b/src/core/observer/watcher.js @@ -2,7 +2,7 @@ * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-30 00:09:56 + * @LastEditTime: 2020-06-30 00:22:23 * @FilePath: \vue\src\core\observer\watcher.js * @Description:Watcher类 */ @@ -33,7 +33,7 @@ let uid = 0 export default class Watcher { vm: Component; expression: string; // 关联表达式或渲染方法体 - cb: Function;// 回调函数 + cb: Function; // 在定义Vue 构造函数的时候,传入的watch函数体(值发生变化时的回调函数,function(nVal,oVal){}) id: number; deep: boolean; user: boolean; @@ -55,7 +55,7 @@ export default class Watcher { newDepIds: ISet; // 旧版本(2.5.0)里没有 - // before: ?Function; //在定义Vue 构造函数的时候,传入的watch函数体(值发生变化时的回调函数,function(nVal,oVal){}) + // before: ?Function; // Watcher触发之前的,类似于生命周期 getter: Function; // 就是渲染函数(模板或组件的渲染)或计算函数(watch) // 计算函数,watch中的对象路径'a,b,c'写法 value: any;// 如果是渲染函数,value 无效;如果是计算属性,就会有一个值,值就存储在value 中 @@ -146,9 +146,9 @@ export default class Watcher { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) - this.newDeps.push(dep) + this.newDeps.push(dep) // 让watcher关联到dep if (!this.depIds.has(id)) { - dep.addSub(this) + dep.addSub(this) // 让dep关联到watcher } } } @@ -160,7 +160,7 @@ export default class Watcher { let i = this.deps.length while (i--) { const dep = this.deps[i] - if (!this.newDepIds.has(dep.id)) { + if (!this.newDepIds.has(dep.id)) { // 在二次提交中归档就是让旧的deps 和新的 newDeps-致 dep.removeSub(this) } } @@ -169,7 +169,7 @@ export default class Watcher { this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps - this.deps = this.newDeps + this.deps = this.newDeps // 同步处理 this.newDeps = tmp this.newDeps.length = 0 } @@ -180,22 +180,24 @@ export default class Watcher { */ update () { /* istanbul ignore else */ - if (this.lazy) { + if (this.lazy) { // 主要针对计算属性,一 般用于求值计算 this.dirty = true - } else if (this.sync) { + } else if (this.sync) { // 同步,主要用于SSR,同步就表示立即计算 this.run() } else { - queueWatcher(this) + queueWatcher(this) // 一般浏览器中的异步运行,本质上就是异步执行run //类比: setTimeout( () => this . run(), } } /** * Scheduler job interface. * Will be called by the scheduler. + * 调用get求值或渲染,如果求值,新旧值不同,触发cb */ run () { if (this.active) { - const value = this.get() + const value = this.get() // 要么渲染,要么求值 + // 如果值不一样,触发cb if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even From 69dfe01a4cce157402f156359c55f12b2db011d9 Mon Sep 17 00:00:00 2001 From: gogoend Date: Wed, 1 Jul 2020 00:27:16 +0800 Subject: [PATCH 10/32] =?UTF-8?q?=E4=BA=86=E8=A7=A3scheduler=20-=20Vue?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E8=B0=83=E5=BA=A6=20=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/observer/scheduler.js | 20 ++++++++++++++++---- src/core/observer/watcher.js | 4 +++- src/core/util/env.js | 1 + 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/core/observer/scheduler.js b/src/core/observer/scheduler.js index df65dab28d5..b42a68e767c 100644 --- a/src/core/observer/scheduler.js +++ b/src/core/observer/scheduler.js @@ -2,9 +2,10 @@ * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 21:24:40 + * @LastEditTime: 2020-07-01 00:26:01 * @FilePath: \vue\src\core\observer\scheduler.js * @Description:vue中的任务调度的工具,watcher执行的核心 + * 据说这东西在jQuery的.ready()里也有体现 */ /* @flow */ @@ -15,22 +16,31 @@ import { callHook, activateChildComponent } from '../instance/lifecycle' import { warn, - nextTick, + nextTick, // vue不同的版本号 实现是不一样的。理想实现:process.nextTick (微任务)但是浏览器不支持, + // 因此可用Promise.resolve来代替,降级使用setTimeout进行模拟。 + // 先当作setTimeout,具体可以转到定义 devtools } from '../util/index' export const MAX_UPDATE_COUNT = 100 +// Watcher队列,可简单理解为事件队列 -- 宏任务、微任务 const queue: Array = [] const activatedChildren: Array = [] let has: { [key: number]: ?true } = {} let circular: { [key: number]: number } = {} + +// 异步触发未开始,类似setTimeout还未执行 let waiting = false + +// 开始渲染,清空队列,执行队列中的 watcher 的 run let flushing = false + let index = 0 /** * Reset the scheduler's state. + * 清空队列 */ function resetSchedulerState () { index = queue.length = activatedChildren.length = 0 @@ -43,6 +53,7 @@ function resetSchedulerState () { /** * Flush both queues and run the watchers. + * // */ function flushSchedulerQueue () { flushing = true @@ -64,7 +75,7 @@ function flushSchedulerQueue () { watcher = queue[index] id = watcher.id has[id] = null - watcher.run() + watcher.run() // 循环调用QueueWatcher里的 run方法 // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1 @@ -151,7 +162,8 @@ export function queueWatcher (watcher: Watcher) { // queue the flush if (!waiting) { waiting = true - nextTick(flushSchedulerQueue) + nextTick(flushSchedulerQueue) // 让任务队列中的watcher在“下一次事件循环”(宏任务的话就是下一次事件循环)中触发 + // 不阻塞当前的处理逻辑 } } } diff --git a/src/core/observer/watcher.js b/src/core/observer/watcher.js index 28ad9336f5a..1a5493fce99 100644 --- a/src/core/observer/watcher.js +++ b/src/core/observer/watcher.js @@ -2,7 +2,7 @@ * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-30 00:22:23 + * @LastEditTime: 2020-07-01 00:24:36 * @FilePath: \vue\src\core\observer\watcher.js * @Description:Watcher类 */ @@ -179,6 +179,7 @@ export default class Watcher { * Will be called when a dependency changes. */ update () { + // 本质就是调用run方法 /* istanbul ignore else */ if (this.lazy) { // 主要针对计算属性,一 般用于求值计算 this.dirty = true @@ -186,6 +187,7 @@ export default class Watcher { this.run() } else { queueWatcher(this) // 一般浏览器中的异步运行,本质上就是异步执行run //类比: setTimeout( () => this . run(), + // 转到相关定义,可发现是在 -- 循环调用run方法 } } diff --git a/src/core/util/env.js b/src/core/util/env.js index ca14c30264a..9926b8069ae 100644 --- a/src/core/util/env.js +++ b/src/core/util/env.js @@ -79,6 +79,7 @@ export const nextTick = (function () { } } + // nextTick兼容处理 // An asynchronous deferring mechanism. // In pre 2.4, we used to use microtasks (Promise/MutationObserver) // but microtasks actually has too high a priority and fires in between From c31c3f8816c0cfbef98e0f1657f2693d3786a25a Mon Sep 17 00:00:00 2001 From: gogoend Date: Wed, 1 Jul 2020 00:39:54 +0800 Subject: [PATCH 11/32] =?UTF-8?q?=E8=AE=B2=E4=BA=86computed=E4=BB=A5?= =?UTF-8?q?=E5=8F=8Awatcher=EF=BC=8CinitState=E5=A4=A7=E8=87=B4=E5=AE=8C?= =?UTF-8?q?=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/instance/state.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/instance/state.js b/src/core/instance/state.js index aee5206bba4..d62b774fe54 100644 --- a/src/core/instance/state.js +++ b/src/core/instance/state.js @@ -215,8 +215,8 @@ export function defineComputed ( const shouldCache = !isServerRendering() if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache - ? createComputedGetter(key) - : userDef + ? createComputedGetter(key) // 浏览器中触发的情况 + : userDef // 新版本中此处是 createGetterInvoker( userDef ) 服务端渲染的时候触发,里面直接计算,不会涉及到watcher处理 sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get @@ -240,6 +240,7 @@ export function defineComputed ( Object.defineProperty(target, key, sharedPropertyDefinition) } +// 浏览器中出发的情况,里面会对数据的访问关联一个watcher function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] @@ -311,6 +312,7 @@ function createWatcher ( handler = vm[handler] } return vm.$watch(keyOrFn, handler, options) + // new 了一个Watcher?? } export function stateMixin (Vue: Class) { From 3e21c2767b6f26ee1b4f48c6b34532e69cb0f4f9 Mon Sep 17 00:00:00 2001 From: gogoend Date: Wed, 1 Jul 2020 00:48:41 +0800 Subject: [PATCH 12/32] =?UTF-8?q?beforeMount=E3=80=81mounted=E7=9A=84?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E8=BF=87=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/instance/lifecycle.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/instance/lifecycle.js b/src/core/instance/lifecycle.js index db7bf286f11..f17b1dcab56 100644 --- a/src/core/instance/lifecycle.js +++ b/src/core/instance/lifecycle.js @@ -140,6 +140,7 @@ export function lifecycleMixin (Vue: Class) { } } +// export function mountComponent ( vm: Component, el: ?Element, @@ -189,10 +190,13 @@ export function mountComponent ( } } else { updateComponent = () => { + // _render用于生成虚拟DOM + // update 内部调用patch 方法将虚拟DOM与真实的DOM同步 (diff算法) vm._update(vm._render(), hydrating) } } + // 渲染Watcher初始化完后,调用下方生命周期函数mounted vm._watcher = new Watcher(vm, updateComponent, noop) hydrating = false From f50226b6d150a1fa711e55d71e6c7af5e80bc71c Mon Sep 17 00:00:00 2001 From: gogoend Date: Wed, 1 Jul 2020 00:58:12 +0800 Subject: [PATCH 13/32] =?UTF-8?q?=E5=A4=A7=E8=87=B4=E7=9C=8B=E7=9C=8Bpatch?= =?UTF-8?q?=EF=BC=88diff=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/vdom/patch.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/vdom/patch.js b/src/core/vdom/patch.js index f06a1feb079..573ec7e79d3 100644 --- a/src/core/vdom/patch.js +++ b/src/core/vdom/patch.js @@ -628,6 +628,13 @@ export function createPatchFunction (backend) { } } + // !!! 据说其它都是工具函数直接看这里 + // 二次提交的逻辑 + // 每次数据更新->生成新的虚拟DOM-> diff旧的虚拟DOM(与真实DOM一一对应)-> 更新旧的虚拟DOM -> 同步真的DOM + // looseEqual? + // 分而治之,每一个虚拟DOM都和页面中的DOM一一对应 + // 我们只需要将VNode与DOMNode 建立一个更新的关系 + // 递归触发每一个虚拟DOM的update,来更新对应的真的DOM的数据 return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { if (isUndef(vnode)) { if (isDef(oldVnode)) invokeDestroyHook(oldVnode) From a57fd263cac09edfeb1f13423e4213662c0dff2f Mon Sep 17 00:00:00 2001 From: gogoend Date: Sat, 15 Aug 2020 11:22:49 +0800 Subject: [PATCH 14/32] =?UTF-8?q?createElement=20-=20normalizeChildren?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/vue.js | 55 ++++++++++++++++----- examples/modal/index.html | 1 + src/core/instance/render.js | 2 + src/core/vdom/helpers/normalize-children.js | 15 ++++-- 4 files changed, 57 insertions(+), 16 deletions(-) diff --git a/dist/vue.js b/dist/vue.js index 13ec9c903b4..d455e76ea33 100644 --- a/dist/vue.js +++ b/dist/vue.js @@ -695,6 +695,7 @@ var nextTick = (function () { } } + // nextTick兼容处理 // An asynchronous deferring mechanism. // In pre 2.4, we used to use microtasks (Promise/MutationObserver) // but microtasks actually has too high a priority and fires in between @@ -2111,11 +2112,16 @@ function simpleNormalizeChildren (children) { // with hand-written render functions / JSX. In such cases a full normalization // is needed to cater to all possible types of children values. function normalizeChildren (children) { - return isPrimitive(children) - ? [createTextVNode(children)] - : Array.isArray(children) - ? normalizeArrayChildren(children) - : undefined + if (isPrimitive(children)) { + debugger + return [createTextVNode(children)] + } + if (Array.isArray(children)) { + debugger + return normalizeArrayChildren(children) + } + debugger + return undefined } function isTextNode (node) { @@ -2641,6 +2647,7 @@ function lifecycleMixin (Vue) { }; } +// function mountComponent ( vm, el, @@ -2690,10 +2697,13 @@ function mountComponent ( }; } else { updateComponent = function () { + // _render用于生成虚拟DOM + // update 内部调用patch 方法将虚拟DOM与真实的DOM同步 (diff算法) vm._update(vm._render(), hydrating); }; } + // 渲染Watcher初始化完后,调用下方生命周期函数mounted vm._watcher = new Watcher(vm, updateComponent, noop); hydrating = false; @@ -2832,9 +2842,10 @@ function callHook (vm, hook) { * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-29 21:24:40 + * @LastEditTime: 2020-07-01 00:26:01 * @FilePath: \vue\src\core\observer\scheduler.js * @Description:vue中的任务调度的工具,watcher执行的核心 + * 据说这东西在jQuery的.ready()里也有体现 */ /* */ @@ -2842,16 +2853,23 @@ function callHook (vm, hook) { var MAX_UPDATE_COUNT = 100; +// Watcher队列,可简单理解为事件队列 -- 宏任务、微任务 var queue = []; var activatedChildren = []; var has = {}; var circular = {}; + +// 异步触发未开始,类似setTimeout还未执行 var waiting = false; + +// 开始渲染,清空队列,执行队列中的 watcher 的 run var flushing = false; + var index = 0; /** * Reset the scheduler's state. + * 清空队列 */ function resetSchedulerState () { index = queue.length = activatedChildren.length = 0; @@ -2864,6 +2882,7 @@ function resetSchedulerState () { /** * Flush both queues and run the watchers. + * // */ function flushSchedulerQueue () { flushing = true; @@ -2885,7 +2904,7 @@ function flushSchedulerQueue () { watcher = queue[index]; id = watcher.id; has[id] = null; - watcher.run(); + watcher.run(); // 循环调用QueueWatcher里的 run方法 // in dev build, check and stop circular updates. if ("development" !== 'production' && has[id] != null) { circular[id] = (circular[id] || 0) + 1; @@ -2972,7 +2991,8 @@ function queueWatcher (watcher) { // queue the flush if (!waiting) { waiting = true; - nextTick(flushSchedulerQueue); + nextTick(flushSchedulerQueue); // 让任务队列中的watcher在“下一次事件循环”(宏任务的话就是下一次事件循环)中触发 + // 不阻塞当前的处理逻辑 } } } @@ -2981,7 +3001,7 @@ function queueWatcher (watcher) { * @Author: gogoend * @Date: 2020-02-02 01:34:53 * @LastEditors: gogoend - * @LastEditTime: 2020-06-30 00:22:23 + * @LastEditTime: 2020-07-01 00:24:36 * @FilePath: \vue\src\core\observer\watcher.js * @Description:Watcher类 */ @@ -3114,6 +3134,7 @@ Watcher.prototype.cleanupDeps = function cleanupDeps () { * Will be called when a dependency changes. */ Watcher.prototype.update = function update () { + // 本质就是调用run方法 /* istanbul ignore else */ if (this.lazy) { // 主要针对计算属性,一 般用于求值计算 this.dirty = true; @@ -3121,6 +3142,7 @@ Watcher.prototype.update = function update () { this.run(); } else { queueWatcher(this); // 一般浏览器中的异步运行,本质上就是异步执行run //类比: setTimeout( () => this . run(), + // 转到相关定义,可发现是在 -- 循环调用run方法 } }; @@ -3417,8 +3439,8 @@ function defineComputed ( var shouldCache = !isServerRendering(); if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache - ? createComputedGetter(key) - : userDef; + ? createComputedGetter(key) // 浏览器中触发的情况 + : userDef; // 新版本中此处是 createGetterInvoker( userDef ) 服务端渲染的时候触发,里面直接计算,不会涉及到watcher处理 sharedPropertyDefinition.set = noop; } else { sharedPropertyDefinition.get = userDef.get @@ -3442,6 +3464,7 @@ function defineComputed ( Object.defineProperty(target, key, sharedPropertyDefinition); } +// 浏览器中出发的情况,里面会对数据的访问关联一个watcher function createComputedGetter (key) { return function computedGetter () { var watcher = this._computedWatchers && this._computedWatchers[key]; @@ -3513,6 +3536,7 @@ function createWatcher ( handler = vm[handler]; } return vm.$watch(keyOrFn, handler, options) + // new 了一个Watcher?? } function stateMixin (Vue) { @@ -4387,6 +4411,8 @@ function renderMixin (Vue) { // render self var vnode; try { + // debugger + // vm._renderProxy是实例;这里相当于调用实例option上的render方法,参数为vm.$createElement(用于创建vnode)函数 vnode = render.call(vm._renderProxy, vm.$createElement); } catch (e) { handleError(e, vm, "render"); @@ -5856,6 +5882,13 @@ function createPatchFunction (backend) { } } + // !!! 据说其它都是工具函数直接看这里 + // 二次提交的逻辑 + // 每次数据更新->生成新的虚拟DOM-> diff旧的虚拟DOM(与真实DOM一一对应)-> 更新旧的虚拟DOM -> 同步真的DOM + // looseEqual? + // 分而治之,每一个虚拟DOM都和页面中的DOM一一对应 + // 我们只需要将VNode与DOMNode 建立一个更新的关系 + // 递归触发每一个虚拟DOM的update,来更新对应的真的DOM的数据 return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { if (isUndef(vnode)) { if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); } diff --git a/examples/modal/index.html b/examples/modal/index.html index fcfb78dba2c..b341596e819 100644 --- a/examples/modal/index.html +++ b/examples/modal/index.html @@ -20,6 +20,7 @@ + + + +
+
+ + + + diff --git a/src/core/vdom/patch.js b/src/core/vdom/patch.js index 554f2934b34..9570395085b 100644 --- a/src/core/vdom/patch.js +++ b/src/core/vdom/patch.js @@ -152,13 +152,15 @@ export function createPatchFunction (backend) { /* istanbul ignore if */ // 先插入子节点,后插入父节点 + // eslint-disable-next-line no-lone-blocks { createChildren(vnode, children, insertedVnodeQueue) if (isDef(data)) { + // 调用所有的create钩子,同时把 vnode push 到 insertedVnodeQueue 中 invokeCreateHooks(vnode, insertedVnodeQueue) } // 插入:父节点 当前vode节点 参考节点 - debugger + // debugger insert(parentElm, vnode.elm, refElm) } if (process.env.NODE_ENV !== 'production' && data && data.pre) { @@ -627,6 +629,7 @@ export function createPatchFunction (backend) { // 我们只需要将VNode与DOMNode 建立一个更新的关系 // 递归触发每一个虚拟DOM的update,来更新对应的真的DOM的数据 return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { + debugger if (isUndef(vnode)) { if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return @@ -672,7 +675,7 @@ export function createPatchFunction (backend) { // create an empty node and replace it oldVnode = emptyNodeAt(oldVnode) // 真实DOM转VNode } - debugger + // debugger // replacing existing element const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) From 63344ad7a4187759d6f48ec93bfe9c514ff7b1ad Mon Sep 17 00:00:00 2001 From: gogoend Date: Mon, 7 Dec 2020 00:59:09 +0800 Subject: [PATCH 17/32] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E4=BA=86=E8=A7=A3creat?= =?UTF-8?q?eComponent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/gogoend/hello.html | 65 ++++++++++++++++++------------- src/core/global-api/extend.js | 5 ++- src/core/vdom/create-component.js | 7 +++- 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/examples/gogoend/hello.html b/examples/gogoend/hello.html index 74df73886d1..98ea0b99854 100644 --- a/examples/gogoend/hello.html +++ b/examples/gogoend/hello.html @@ -5,34 +5,47 @@ * @LastEditTime: 2020-06-29 23:51:00 * @FilePath: \vue\examples\modal\index.html * @Description: ---> +--> - - - CreateElement 函数 - - - - -
-
+ + + CreateElement 函数 + + + + + + +
+
+ + + - - diff --git a/src/core/global-api/extend.js b/src/core/global-api/extend.js index c8035a3d1a4..b90f989e2e8 100644 --- a/src/core/global-api/extend.js +++ b/src/core/global-api/extend.js @@ -39,12 +39,13 @@ export function initExtend (Vue: GlobalAPI) { const Sub = function VueComponent (options) { this._init(options) } + // 原型继承 Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub Sub.cid = cid++ Sub.options = mergeOptions( - Super.options, - extendOptions + Super.options, // Vue(父类??)原有的options (类似全局的指令,过滤器??) + extendOptions // 扩展的option ) Sub['super'] = Super diff --git a/src/core/vdom/create-component.js b/src/core/vdom/create-component.js index d3de4fb4c20..161261acd65 100644 --- a/src/core/vdom/create-component.js +++ b/src/core/vdom/create-component.js @@ -94,6 +94,7 @@ const componentVNodeHooks = { } } +// 要合并的钩子在这里,即 componentVNodeHooks 对象中的键 const hooksToMerge = Object.keys(componentVNodeHooks) export function createComponent ( @@ -107,8 +108,10 @@ export function createComponent ( return } + // 此处baseContror即是Vue类 const baseCtor = context.$options._base + // 如果传入的组件是 一个对象(平常写的那种方式) ,就用 Vue.extend 处理成一个新的构造函数 // plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) @@ -180,14 +183,16 @@ export function createComponent ( } } + // 合并钩子(这儿钩子是什么??) // merge component management hooks onto the placeholder node mergeHooks(data) + // 组件创建完成,返回vnode // return a placeholder vnode const name = Ctor.options.name || tag const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, - data, undefined, undefined, undefined, context, + data, undefined, undefined/* 此处children为空 */, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory ) From 5c2c58f24bb200aac508944c5c005e4520f4c4d9 Mon Sep 17 00:00:00 2001 From: gogoend Date: Sat, 12 Dec 2020 11:19:51 +0800 Subject: [PATCH 18/32] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=EF=BC=8C=E5=B0=9D=E8=AF=95=E4=BA=86=E8=A7=A3=E8=A6=81createCom?= =?UTF-8?q?ponent=E5=90=88=E5=B9=B6=E7=9A=84=E9=92=A9=E5=AD=90=E5=88=B0?= =?UTF-8?q?=E5=BA=95=E6=98=AF=E4=BB=80=E4=B9=88=E9=92=A9=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/global-api/extend.js | 2 ++ src/core/vdom/create-component.js | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/global-api/extend.js b/src/core/global-api/extend.js index b90f989e2e8..9e71942dcc5 100644 --- a/src/core/global-api/extend.js +++ b/src/core/global-api/extend.js @@ -49,6 +49,7 @@ export function initExtend (Vue: GlobalAPI) { ) Sub['super'] = Super + // 令Sub具有和Super一样的能力 // For props and computed properties, we define the proxy getters on // the Vue instances at extension time, on the extended prototype. This // avoids Object.defineProperty calls for each instance created. @@ -81,6 +82,7 @@ export function initExtend (Vue: GlobalAPI) { Sub.extendOptions = extendOptions Sub.sealedOptions = extend({}, Sub.options) + // 缓存已近创建过的组件构造器,使用时直接返回 // cache constructor cachedCtors[SuperId] = Sub return Sub diff --git a/src/core/vdom/create-component.js b/src/core/vdom/create-component.js index 161261acd65..725830411b6 100644 --- a/src/core/vdom/create-component.js +++ b/src/core/vdom/create-component.js @@ -27,6 +27,7 @@ import { deactivateChildComponent } from '../instance/lifecycle' +// 每个组件都会有的钩子 // hooks to be invoked on component VNodes during patch const componentVNodeHooks = { init ( @@ -117,6 +118,7 @@ export function createComponent ( Ctor = baseCtor.extend(Ctor) } + // 如果此处返回的不是一个构造函数,就说明组件定义存在问题 // if at this stage it's not a constructor or an async component factory, // reject. if (typeof Ctor !== 'function') { @@ -183,7 +185,7 @@ export function createComponent ( } } - // 合并钩子(这儿钩子是什么??) + // 合并钩子(这儿钩子是什么?? —— 据说是Patch时要执行的钩子) // merge component management hooks onto the placeholder node mergeHooks(data) From 63814b4fb380e5cce72f301003027d2a8501da36 Mon Sep 17 00:00:00 2001 From: gogoend Date: Sun, 12 Sep 2021 09:14:19 +0800 Subject: [PATCH 19/32] =?UTF-8?q?observer=E6=B3=A8=E9=87=8A=E8=A1=A5?= =?UTF-8?q?=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/observer/index.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/core/observer/index.js b/src/core/observer/index.js index 2f9ac8f9f21..09f769e851f 100644 --- a/src/core/observer/index.js +++ b/src/core/observer/index.js @@ -120,7 +120,7 @@ function copyAugment (target: Object, src: Object, keys: Array) { * or the existing observer if the value already has one. * 传入数据变为响应式对象 * 算法描述: - * -先看对象是否含有 _ob__ ,并且是Observer 的实例( Vue中响应式对象的标记) + * -先看对象是否含有 __ob__ ,并且是Observer 的实例( Vue中响应式对象的标记) * -有,忽略 * -没有,调用new Observer( value )。进行响应式化 */ @@ -148,6 +148,7 @@ export function observe (value: any, asRootData: ?boolean): Observer | void { /** * Define a reactive property on an Object. + * 本函数用于为对象中每一个属性定义响应式 */ export function defineReactive ( obj: Object, @@ -156,14 +157,16 @@ export function defineReactive ( customSetter?: ?Function, shallow?: boolean ) { + // 该属性对应的依赖管理器 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 @@ -173,7 +176,8 @@ export function defineReactive ( enumerable: true, configurable: true, get: function reactiveGetter () { - const value = getter ? getter.call(obj) : val // 保证了如果已经定义的get方法可以被继承下来,不会丢失 + const value = getter ? getter.call(obj) : val // 保证了如果已经定义的getter可以被保留下来,不会丢失 + // debugger if (Dep.target) { dep.depend() // 关联的当前属性 if (childOb) { @@ -198,7 +202,7 @@ export function defineReactive ( customSetter() } if (setter) { - setter.call(obj, newVal) // 保证了如果已经定义的set方法可以被继承下来,不会丢失 + setter.call(obj, newVal) // 保证了如果已经定义的set方法可以被保留下来,不会丢失 } else { val = newVal } @@ -212,6 +216,7 @@ export function defineReactive ( * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. + * 为对象设置新属性的逻辑 */ export function set (target: Array | Object, key: any, val: any): any { if (Array.isArray(target) && isValidArrayIndex(key)) { @@ -242,6 +247,7 @@ export function set (target: Array | Object, key: any, val: any): any { /** * Delete a property and trigger change if necessary. + * 从对象中删除属性的逻辑 */ export function del (target: Array | Object, key: any) { if (Array.isArray(target) && isValidArrayIndex(key)) { @@ -269,6 +275,7 @@ export function del (target: Array | Object, key: any) { /** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. + * 收集数组子元素中的的依赖 */ function dependArray (value: Array) { for (let e, i = 0, l = value.length; i < l; i++) { From bc2c55eaca64dcafcf00cc57db118538cffcdb44 Mon Sep 17 00:00:00 2001 From: gogoend Date: Sun, 12 Sep 2021 13:22:44 +0800 Subject: [PATCH 20/32] =?UTF-8?q?feat(dep/watcher):=20=E8=A1=A5=E5=85=85de?= =?UTF-8?q?p=20targetStack=E5=8F=98=E9=87=8F=E7=9A=84=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=EF=BC=9B=E8=A1=A5=E5=85=85watcher=E5=B1=9E=E6=80=A7=E7=9A=84?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/observer/dep.js | 5 +++++ src/core/observer/watcher.js | 32 +++++++++++++++++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/core/observer/dep.js b/src/core/observer/dep.js index dee1cab513c..387cfd3e1b7 100644 --- a/src/core/observer/dep.js +++ b/src/core/observer/dep.js @@ -57,14 +57,19 @@ export default class Dep { // the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. +// 当前正在被计算的watcher。 +// 同一时刻内只能存在一个watcher +// 仅在Watcher类下get方法中发生改变 Dep.target = null const targetStack = [] export function pushTarget (_target: Watcher) { + // 如果当前正在被计算的watcher存在,在计算另一个watcher前先把当前被计算的watcher压入到栈中 if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { + // 计算完成后把之前被压入到栈中的watcher弹出来,恢复上一个watcher Dep.target = targetStack.pop() } diff --git a/src/core/observer/watcher.js b/src/core/observer/watcher.js index 1a5493fce99..f0274981822 100644 --- a/src/core/observer/watcher.js +++ b/src/core/observer/watcher.js @@ -35,10 +35,10 @@ export default class Watcher { expression: string; // 关联表达式或渲染方法体 cb: Function; // 在定义Vue 构造函数的时候,传入的watch函数体(值发生变化时的回调函数,function(nVal,oVal){}) id: number; - deep: boolean; + deep: boolean;// watcher来源,是由Vue内部自己生成的(渲染Watcher、computed选项)还是由用户自己定义的(watch选项) user: boolean; lazy: boolean;// 计算属性,和watch 来控制不要让Watcher 立即执行 - sync: boolean; + sync: boolean;// 是否同步执行 - SSR时为true dirty: boolean; active: boolean; @@ -111,14 +111,16 @@ export default class Watcher { /** * Evaluate the getter, and re-collect dependencies. + * 计算getter,重新收集依赖 */ get () { - // 执行前把watcher放到全局作用域 + // 执行前把watcher放到全局作用域 - 赋值给Dep类的静态属性target pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) + debugger } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) @@ -131,9 +133,9 @@ export default class Watcher { if (this.deep) { traverse(value) } - // 执行后把watcher从全局作用域移除 + // 执行后把watcher从全局作用域移除 - Dep类的静态属性target换为先前的watcher或undefined popTarget() - // "清空"关联的dep数据 + // "清空"关联的dep数据 - 清理旧的无关的依赖 this.cleanupDeps() } return value @@ -155,15 +157,20 @@ export default class Watcher { /** * Clean up for dependency collection. + * 清理依赖集合 */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] - if (!this.newDepIds.has(dep.id)) { // 在二次提交中归档就是让旧的deps 和新的 newDeps-致 + // 在二次提交中归档就是让旧的deps 和新的 newDeps-致 + // 如果新的dep集合不存在当前dep,则从dep中移除当前watcher(自己) + // 也就是当重新收集依赖时,若不再依赖当前watcher,应该删掉 + if (!this.newDepIds.has(dep.id)) { dep.removeSub(this) } } + // 交换新旧depId集合 let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp @@ -181,13 +188,18 @@ export default class Watcher { update () { // 本质就是调用run方法 /* istanbul ignore else */ - if (this.lazy) { // 主要针对计算属性,一 般用于求值计算 + if (this.lazy) { + // 主要针对计算属性,一 般用于求值计算 + // state createComputedGetter 中有调用 this.dirty = true - } else if (this.sync) { // 同步,主要用于SSR,同步就表示立即计算 + } else if (this.sync) { + // 同步,主要用于SSR,同步就表示立即计算 this.run() } else { - queueWatcher(this) // 一般浏览器中的异步运行,本质上就是异步执行run //类比: setTimeout( () => this . run(), + // 将当前watcher插入到异步队列 + // 一般浏览器中的异步运行,本质上就是异步执行run //类比: setTimeout( () => this.run(), // 转到相关定义,可发现是在 -- 循环调用run方法 + queueWatcher(this) } } @@ -227,6 +239,7 @@ export default class Watcher { /** * Evaluate the value of the watcher. * This only gets called for lazy watchers. + * 计算值,计算完成后dirty设为true */ evaluate () { this.value = this.get() @@ -245,6 +258,7 @@ export default class Watcher { /** * Remove self from all dependencies' subscriber list. + * 从所有的依赖管理器中把自己移除 */ teardown () { if (this.active) { From 7d6fb1ecca9974677dcc95feb9f2e6a0e677a338 Mon Sep 17 00:00:00 2001 From: gogoend Date: Sun, 12 Sep 2021 15:35:25 +0800 Subject: [PATCH 21/32] =?UTF-8?q?chore=EF=BC=88patch=E3=80=81normalize-chi?= =?UTF-8?q?ldren=EF=BC=89:=20=E5=8E=BB=E6=8E=89=E5=A4=AA=E5=A4=9A=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E7=9A=84debugger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/vdom/helpers/normalize-children.js | 3 --- src/core/vdom/patch.js | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/core/vdom/helpers/normalize-children.js b/src/core/vdom/helpers/normalize-children.js index cf8692e6a57..9e11803a384 100644 --- a/src/core/vdom/helpers/normalize-children.js +++ b/src/core/vdom/helpers/normalize-children.js @@ -30,14 +30,11 @@ export function simpleNormalizeChildren (children: any) { // is needed to cater to all possible types of children values. export function normalizeChildren (children: any): ?Array { if (isPrimitive(children)) { - debugger return [createTextVNode(children)] } if (Array.isArray(children)) { - debugger return normalizeArrayChildren(children) } - debugger return undefined } diff --git a/src/core/vdom/patch.js b/src/core/vdom/patch.js index 9570395085b..cce8a8274ec 100644 --- a/src/core/vdom/patch.js +++ b/src/core/vdom/patch.js @@ -629,7 +629,7 @@ export function createPatchFunction (backend) { // 我们只需要将VNode与DOMNode 建立一个更新的关系 // 递归触发每一个虚拟DOM的update,来更新对应的真的DOM的数据 return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { - debugger + // debugger if (isUndef(vnode)) { if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return From 50eb2a99109135a4f09460b85325c5f3efe82c06 Mon Sep 17 00:00:00 2001 From: gogoend Date: Sun, 12 Sep 2021 15:36:04 +0800 Subject: [PATCH 22/32] =?UTF-8?q?refactor(gogoend-demo):=20demo=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/gogoend/hello.html | 49 +++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/examples/gogoend/hello.html b/examples/gogoend/hello.html index 98ea0b99854..a022db8082e 100644 --- a/examples/gogoend/hello.html +++ b/examples/gogoend/hello.html @@ -23,12 +23,39 @@ From d118b0f939d4fedd44fb47a59843c3e90e6708bd Mon Sep 17 00:00:00 2001 From: gogoend Date: Mon, 13 Sep 2021 00:17:30 +0800 Subject: [PATCH 23/32] =?UTF-8?q?docs(scheduler.js):=20queueWatcher?= =?UTF-8?q?=E5=87=BD=E6=95=B0=20=E6=B7=BB=E5=8A=A0watcher=E5=88=B0?= =?UTF-8?q?=E9=98=9F=E5=88=97=E6=B3=A8=E9=87=8A=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/observer/scheduler.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/observer/scheduler.js b/src/core/observer/scheduler.js index b42a68e767c..6686833ef6d 100644 --- a/src/core/observer/scheduler.js +++ b/src/core/observer/scheduler.js @@ -151,8 +151,11 @@ export function queueWatcher (watcher: Watcher) { if (!flushing) { queue.push(watcher) } else { + // 根据id,把watcher插入到合适的位置 + // 若异步队列已经开始处理,则根据watcher的id把它插入到正确位置 // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. + // 这里是由后往前进行查找的 let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- From eef6461047cf44550bcb0606ede61209f060808b Mon Sep 17 00:00:00 2001 From: gogoend Date: Wed, 15 Sep 2021 09:53:18 +0800 Subject: [PATCH 24/32] =?UTF-8?q?docs(scheduler):=20=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=20-=20=E9=98=B2=E6=AD=A2=E6=97=A0=E9=99=90?= =?UTF-8?q?=E5=BE=AA=E7=8E=AF=E6=9B=B4=E6=96=B0=E7=9A=84=E5=8E=9F=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/observer/scheduler.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/observer/scheduler.js b/src/core/observer/scheduler.js index 6686833ef6d..dd58e958890 100644 --- a/src/core/observer/scheduler.js +++ b/src/core/observer/scheduler.js @@ -78,8 +78,10 @@ function flushSchedulerQueue () { watcher.run() // 循环调用QueueWatcher里的 run方法 // in dev build, check and stop circular updates. if (process.env.NODE_ENV !== 'production' && has[id] != null) { + // 记录循环次数 circular[id] = (circular[id] || 0) + 1 if (circular[id] > MAX_UPDATE_COUNT) { + // 如果循环次数已经超过最大值,则表示更新过程存在循环 - 例如在watch选项中,使用表达式让自己发生了变化,此时又将再次触发更新 warn( 'You may have an infinite update loop ' + ( watcher.user From 122cee93b462eafb06fcc416f6060c6e3c1f552e Mon Sep 17 00:00:00 2001 From: gogoend Date: Wed, 15 Sep 2021 09:55:21 +0800 Subject: [PATCH 25/32] =?UTF-8?q?docs(mountComponent):=20=E8=A1=A5?= =?UTF-8?q?=E5=85=85mountComponent=E6=96=B9=E6=B3=95=E6=B3=A8=E9=87=8A=20-?= =?UTF-8?q?=20=E6=AD=A4=E5=A4=84=E7=BB=84=E4=BB=B6=E6=8C=82=E8=BD=BD?= =?UTF-8?q?=E5=AE=8C=E5=85=A8=E4=BE=9D=E9=9D=A0$options.render=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/instance/lifecycle.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/instance/lifecycle.js b/src/core/instance/lifecycle.js index 82d3dc6011f..6b1bbdd6f62 100644 --- a/src/core/instance/lifecycle.js +++ b/src/core/instance/lifecycle.js @@ -149,6 +149,8 @@ export function mountComponent ( hydrating?: boolean ): Component { vm.$el = el + // 到这一步的时候,template必须被转换为render函数 + // 如果此时$option中还是没有render函数,那就是出问题了 if (!vm.$options.render) { vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== 'production') { From 194fedcb4b458cbc12a617fef95a7151d0f38e9c Mon Sep 17 00:00:00 2001 From: gogoend Date: Sun, 19 Sep 2021 13:04:40 +0800 Subject: [PATCH 26/32] =?UTF-8?q?docs(state.js):=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=20-=20=E9=98=B2=E6=AD=A2data/computed/prop?= =?UTF-8?q?=E6=9C=89=E5=B1=9E=E6=80=A7=E5=90=8C=E5=90=8D=EF=BC=9B=5Fdata?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E5=88=B0=E7=BB=84=E4=BB=B6=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E4=B8=8A=EF=BC=9B=E6=8C=87=E5=87=BA=E5=93=8D=E5=BA=94=E5=BC=8F?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=EF=BC=88observe=E8=A2=AB=E8=B0=83=E7=94=A8?= =?UTF-8?q?=EF=BC=89=E7=9A=84=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/instance/state.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/instance/state.js b/src/core/instance/state.js index d62b774fe54..9f4e06f21a0 100644 --- a/src/core/instance/state.js +++ b/src/core/instance/state.js @@ -130,7 +130,8 @@ function initData (vm: Component) { let i = keys.length while (i--) { const key = keys[i] - /** 这里判断只是为了避免props, data, method 等数据发生冲突,同名的问题*/ + // 这里判断只是为了避免props, data, method 等数据发生冲突(同名)的问题 + // 没有其他用途 if (process.env.NODE_ENV !== 'production') { if (methods && hasOwn(methods, key)) { warn( @@ -146,13 +147,17 @@ function initData (vm: Component) { vm ) } else if (!isReserved(key)) { - proxy(vm, `_data`, key) // 循环data所有属性,映射到Vue实例上, + // 循环data所有属性,映射到Vue实例上, // 就无需使用 app._data.xxx来访问属性 // 而是直接使用app.data访问 + proxy(vm, `_data`, key) } } // observe data - observe(data, true /* asRootData */) // 响应式化 + // 这里是让data里的数据响应式化 + // 相当于在demo的业务代码中直接调用的observer + // https://github.com/gogoend/blog/blob/04e8da87ee81ac38f3d7b0d2682ca47badb679fb/vue2/vue-src-relative-2/reactive-notify-watcher.html#L49 + observe(data, true /* asRootData */) } function getData (data: Function, vm: Component): any { From 74d1fc14f639a2ab3cabcfe44eb72abbbd32d538 Mon Sep 17 00:00:00 2001 From: gogoend Date: Sun, 3 Oct 2021 00:40:35 +0800 Subject: [PATCH 27/32] =?UTF-8?q?refactor(gogoend=20demo):=20=E5=BE=AE?= =?UTF-8?q?=E8=B0=83demo=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F=EF=BC=8C`nana?= =?UTF-8?q?`=E7=BB=84=E4=BB=B6=E5=8F=98=E9=87=8F=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E6=94=B9=E4=B8=BA`Nana`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/gogoend/hello.html | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/examples/gogoend/hello.html b/examples/gogoend/hello.html index a022db8082e..7644ba6dc44 100644 --- a/examples/gogoend/hello.html +++ b/examples/gogoend/hello.html @@ -22,7 +22,7 @@