摘要:说明源码万多行,完全解析透太耗时间里面细节处理很多,通读代码,语法都不难个人认为重点在于理解它的思想,掌握面向数据编程的原理。一案例代码及运行流程用说明里面提供的命令行,生成的项目,稍微改动。
说明
vue源码1万多行,完全解析透太耗时间;里面细节处理很多,通读代码,语法都不难;个人认为重点在于理解它的思想,掌握面向数据编程的原理。
通过一个合适的例子,断点调试来查看代码运行流程,可以快速了解编码的思路。
vue@2.5.13
用api说明里面提供的命令行,生成的vue项目,稍微改动。
目录结构:
components/HelloWorld.vue
router/index.js
import Vue from "vue" import Router from "vue-router" import index from "../index.vue" Vue.use(Router) export default new Router({ routes: [ { path: "/", name: "index", component: index } ] })
App.vue
index.vue
main.js
import Vue from "vue" import App from "./App" import router from "./router" import helloworld from "./components/HelloWorld.vue" Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: "#app", router, components: { App, helloworld}, template: "断点运行流程" })
建议用工具打开,里面的缩进代表函数的层级关系
function Vue$3 (options) { //创建新的vue实例 this._init(options); //初始化 vm.$options = mergeOptions( //混合options resolveConstructorOptions(vm.constructor), //获取构造函数Vue$3的options对象;key有beforeCreate、components、destroyed、directives、filters、_base options || {}, vm ); checkComponents(child);//验证options里面components的命名 validateComponentName(key);//校验components的命名合法性 normalizeProps(child, vm);//规范props;无值返回;数组[string]返{string:{ type: null }},对象{key:val}返回 val为string为{key:{type:val}},val为obj为{key:val} normalizeInject(child, vm);//规范inject;无值返回;数组[string]返{string:{ from: string }},对象{key:val}返回 val为string为{key:{from:val}},val为obj为{key:extend({from:key},val)} normalizeDirectives(child);//规范directives;无值不处理;默认为对象{key:def},仅处理def类型为function,返回{key:{bind:def,update:def}} mergeField(key); //parent //components、directives、filters调用mergeAssets; 浅合并child到parent //_base调用defaultStrat; child无值取parent,有值取child //beforeCreate、destroyed调用mergeHook; child无值取parent,child有值 parent有值取parent.concat(child);parent无值 child是数组取child,不是数组取[child] //child父级无此属性执行 //el调用strats.el; return defaultStrat(parent, child) //router、template调用defaultStrat //components父级有,忽略 initProxy(vm); vm._renderProxy = new Proxy(vm, handlers);//handlers = hasHandler initLifecycle(vm); /** 此时vm={ _uid: 0, _isVue: true, $options: { beforeCreate: [1], destroyed: [1], directives: {}, filters: {}, _base: Vue$3(options), el: "#app", router: VueRouter, template: "二、数据双向绑定原理", components:{ App: {}, helloworld: {} } }, _renderProxy: new Proxy(vm, hasHandler), $parent: undefined, $root: vm, $children: [], $refs: {}, _watcher: null, _inactive: null, _directInactive: false, _isMounted: false, _isDestroyed: false, _isBeingDestroyed: false } **/ initEvents(vm); /** vm添加 { _events: {}, _hasHookEvent: false } **/ initRender(vm); /** vm添加 { _vnode: null, _staticTrees: null, $vnode: undefined, $slots: {}, $scopedSlots: {}, _c: function (a, b, c, d) { return createElement(vm, a, b, c, d, false); }, $createElement: function (a, b, c, d) { return createElement(vm, a, b, c, d, true); } } **/ defineReactive(); var dep = new Dep(); /** 此时vm添加 { $attrs: , $listeners: } 监听属性变动 **/ callHook(vm, "beforeCreate"); beforeCreate: function beforeCreate () {}//调用vue-router.js方法 this._router.init(this);//调用vue-router.js this.apps.push(app);//app为此vue实例 var setupHashListener = function () { history.setupListeners(); }; history.transitionTo( history.getCurrentLocation(),//调return getHash();返回href; setupHashListener, setupHashListener ); Vue.util.defineReactive(this, "_route", this._router.history.current); var dep = new Dep(); registerInstance(this, this); Vue.extend = function (extendOptions) { validateComponentName(name); var Sub = function VueComponent (options) { this._init(options); }; Sub.options = mergeOptions( Super.options, extendOptions ); return Sub; initInjections(vm); // resolve injections before data/props var result = resolveInject(vm.$options.inject, vm); initState(vm); /** vm添加 { _watchers: [], _data: {} } **/ observe(vm._data = {}, true /* asRootData */); ob = new Observer(value); var dep = new Dep(); def(value, "__ob__", this); this.walk(value); initProvide(vm); callHook(vm, "created"); vm.$mount(vm.$options.el); el = el && query(el);//获取el元素 var ref = compileToFunctions(template, {}, this) new Function("return 1"); var compiled = compile(template, options); var finalOptions = Object.create(baseOptions); /* baseOptions = { expectHTML: true, modules: [{ staticKeys: ["staticClass"], transformNode: transformNode, genData: genData }, { staticKeys: ["staticStyle"], transformNode: transformNode$1, genData: genData$1 }, { preTransformNode: preTransformNode }], directives: { model: model, text: text, html: html }, isPreTag: isPreTag, isUnaryTag: isUnaryTag, mustUseProp: mustUseProp, canBeLeftOpenTag: canBeLeftOpenTag, isReservedTag: isReservedTag, getTagNamespace: getTagNamespace, staticKeys: genStaticKeys(modules$1) }; */ var compiled = baseCompile(template, finalOptions); var ast = parse(template.trim(), options); transforms = pluckModuleFunction(options.modules, "transformNode"); //例:pluckModuleFunction(modules,key);遍历modules,返回键为key的值的数组 //transforms=[transformNode,transformNode$1] preTransforms = pluckModuleFunction(options.modules, "preTransformNode"); postTransforms = pluckModuleFunction(options.modules, "postTransformNode"); parseHTML(template, {}) var startTagMatch = parseStartTag(); /*例 startTagMatch = { attrs: [], end: 6, start: 0, tagName: "App", unarySlash: "/" } */ handleStartTag(startTagMatch); options.start(tagName, attrs, unary, match.start, match.end); var element = createASTElement(tag, attrs, currentParent); /* element = { type: 1, tag: tag, attrsList: attrs, attrsMap: makeAttrsMap(attrs), parent: parent, children: [] } */ element = preTransforms[i](element, options) || element;//preTransformNode(el, options);仅处理tag为input processPre(element);//v-pre processFor(element);//v-for //getAndRemoveAttr(el, name, removeFromMap)删除el.attrsList数组里的name,removeFromMap为真删除el.attrsMap[name],返回el.attrsMap[name] processIf(element);//v-if v-else-if v-else processOnce(element);//v-once processElement(element, options); processKey(element);//key //getBindingAttr(el, name, getStatic);返回绑定的属性值;有:name或v-bind:name返回parseFilters(getAndRemoveAttr(el,":"+name||"v-bind:"+name)),没有动态属性值查找静态;getStatic不为false,有name,返回JSON.stringify(getAndRemoveAttr(el,name)) processRef(element);//ref processSlot(element);//slot||template||slot-scope processComponent(element);//is||inline-template element = transforms[i](element, options) || element;//transformNode(element, options);transformNode$1(element, options);处理class和style processAttrs(element);//处理attrsList里的属性值 checkRootConstraints(root);//组件根约束,slot、template、v-for closeElement(element); parseEndTag(); return root; /* root={ attrsList: [], attrsMap: {}, children: [], parent: undefined, plain: true, tag: "App", type: 1 } */ optimize(ast, options); isStaticKey = genStaticKeysCached(options.staticKeys || ""); /* isStaticKey=function (val) { return map[val]; } map={ type: true, tag: true, attrsList: true, attrsMap: true, plain: true, parent: true, children: true, attrs: true, staticClass: true, staticStyle: true } */ markStatic$1(root); node.static = isStatic(node);//false markStaticRoots(root, false); node.staticRoot = false; var code = generate(ast, options); var state = new CodegenState(options); var code = ast ? genElement(ast, state) : "_c("div")"; //code="_c("App")" /* code={ render: "with(this){return _c("App")}", staticRenderFns: [] } */ /* compiled={ ast: { attrsList: [], attrsMap: {}, children: [], parent: undefined, plain: true, static: false, staticRoot: false, tag: "App", type: 1 }, render: "with(this){return _c("App")}", staticRenderFns: [] } */ errors.push.apply(errors, detectErrors(compiled.ast)); checkNode(ast, errors); /* compiled加{ errors: [], tips: [] } */ res.render = createFunction(compiled.render, fnGenErrors); return new Function(code) /* res={ render: function anonymous(){with(this){return _c("App")}} staticRenderFns: [] } */ return mount.call(this, el, hydrating) /*注: var mount = Vue$3.prototype.$mount; //1 Vue$3.prototype.$mount = function(){} //2 初次调用,1被2重写,调用2; 此时调用,指定mount,调用1; */ el = el && inBrowser ? query(el) : undefined;//获取el元素 return el; return mountComponent(this, el, hydrating) callHook(vm, "beforeMount"); updateComponent = function () { vm._update(vm._render(), hydrating); }; new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */); /* 此时Watcher Watcher={ active: true, cb: ƒ noop(a, b, c), deep: false, depIds: Set(0) {}, deps: [], dirty: false, expression: "function () {↵ vm._update(vm._render(), hydrating);↵ }", getter: ƒ (), id: 1, lazy: false, newDepIds: Set(0) {}, newDeps: [], sync: false, user: false, vm : vue实例 } */ this.get(); pushTarget(this);//将Watcher实例赋值给Dep._target; value = this.getter.call(vm, vm); vm._update(vm._render(), hydrating); Vue.prototype._render vnode = render.call(vm._renderProxy, vm.$createElement); vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); }; return _createElement(context, tag, data, children, normalizationType)//(vue实例, "App", undefined, undefined, undefined) vnode = createComponent(Ctor, data, context, children, tag);//(组件App, undefined, vue实例, undefined, "App" ) Ctor = baseCtor.extend(Ctor); validateComponentName(name); var Sub = function VueComponent (options) { Sub.options = mergeOptions(Super.options,extendOptions); checkComponents(child); normalizeProps(child, vm); name = camelize(key); normalizeInject(child, vm); normalizeDirectives(child); mergeField(key); //parent //components、directives、filters调用mergeAssets; 浅合并child到parent,child无值取{} //_base调用defaultStrat; child无值取parent,有值取child //beforeCreate、destroyed调用mergeHook; child无值取parent,child有值 parent有值取parent.concat(child);parent无值 child是数组取child,不是数组取[child] //child父级无此属性执行 //beforeDestroy调用mergeHook; //name、render、staticRenderFns、_compiled、_file、_Ctor调用defaultStrat //beforeCreate父级有,忽略 ASSET_TYPES.forEach(function (type) {}) return Sub; //Ctor=Sub; resolveConstructorOptions (Ctor) var superOptions = resolveConstructorOptions(Ctor.super); //递归获取最初的options //返回混合后的options var propsData = extractPropsFromVNodeData(data, Ctor, tag); mergeHooks(data);//data={on:undefined}; var vnode = new VNode()//("vue-component-4-App",data1, undefined, undefined, undefined, vue实例,组件options, undefined,) /* data1={ hook: { destroy: ƒ destroy(vnode), init: ƒ init( vnode, hydrating, parentElm, refElm ), insert: ƒ insert(vnode), prepatch: ƒ prepatch(oldVnode, vnode) }, on: undefined } 组件options={ Ctor: ƒ VueComponent(options) children: undefined listeners: undefined propsData: undefined tag: "App" } */ return vnode; /* vnode={ asyncFactory: undefined asyncMeta: undefined children: undefined componentInstance: undefined componentOptions: {Ctor: ƒ, propsData: undefined, listeners: undefined, tag: "App", children: undefined} context: Vue$3 {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue$3, …} data: {on: undefined, hook: {…}} elm: undefined fnContext: undefined fnOptions: undefined fnScopeId: undefined isAsyncPlaceholder: false isCloned: false isComment: false isOnce: false isRootInsert: true isStatic: false key: undefined ns: undefined parent: undefined raw: false tag: "vue-component-4-App" text: undefined } */ return vnode //vnode //vnode return vnode; //参数vm._render()为vnode = new VNode(); vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false, vm.$options._parentElm, vm.$options._refElm) oldVnode = emptyNodeAt(oldVnode); return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm) var oldElm = oldVnode.elm; var parentElm$1 = nodeOps.parentNode(oldElm);//parentElm$1=body; createElm(vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm$1, nodeOps.nextSibling(oldElm) ); if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {} i(vnode, false /* hydrating */, parentElm, refElm);//i=componentVNodeHooks.init var child = vnode.componentInstance = createComponentInstanceForVnode()//(vnode,vue实例,body,下一节点) return new vnode.componentOptions.Ctor(options) /* 调function VueComponent (options); options={ parent: Vue$3 {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue$3, …} _isComponent: true _parentElm: body _parentVnode: VNode {tag: "vue-component-4-App", data: {…}, children: undefined, text: undefined, elm: undefined, …} _refElm: text } */ this._init(options); /* 重新调用_init方法; var Sub = function VueComponent (options) { this._init(options); }; */ initInternalComponent(vm, options); initProxy(vm); vm._renderProxy = new Proxy(vm, handlers);//handlers为getHandler initLifecycle(vm); initEvents(vm); initRender(vm); defineReactive(vm, "$attrs", parentData && parentData.attrs || emptyObject, function () { !isUpdatingChildComponent && warn("$attrs is readonly.", vm); }, true); defineReactive(vm, "$listeners", options._parentListeners || emptyObject, function () { !isUpdatingChildComponent && warn("$listeners is readonly.", vm); }, true); callHook(vm, "beforeCreate"); initInjections(vm); // resolve injections before data/props initState(vm); initProvide(vm); // resolve provide after data/props callHook(vm, "created"); /* child={ $attrs: (...), $children: [], $createElement: ƒ (a, b, c, d), $listeners: (...), $options: {parent: Vue$3, _parentVnode: VNode, _parentElm: body, _refElm: text, propsData: undefined, …}, $parent: Vue$3 {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue$3, …}, $refs: {}, $root: Vue$3 {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue$3, …}, $scopedSlots: {}, $slots: {}, $vnode: VNode {tag: "vue-component-4-App", data: {…}, children: undefined, text: undefined, elm: undefined, …}, _c: ƒ (a, b, c, d), _data: {__ob__: Observer}, _directInactive: false, _events: {}, _hasHookEvent: false, _inactive: null, _isBeingDestroyed: false, _isDestroyed: false, _isMounted: false, _isVue: true, _renderProxy: Proxy {_uid: 1, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}, _routerRoot: Vue$3 {_uid: 0, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: Vue$3, …}, _self: VueComponent {_uid: 1, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}, _staticTrees: null, _uid: 1, _vnode: null, _watcher: null, _watchers: [] } */ child.$mount(hydrating ? vnode.elm : undefined, hydrating); return mount.call(this, el, hydrating) return mountComponent(this, el, hydrating) callHook(vm, "beforeMount"); updateComponent = function () { vm._update(vm._render(), hydrating); }; new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */); this.get(); pushTarget(this); value = this.getter.call(vm, vm); vm._update(vm._render(), hydrating); vnode = render.call(vm._renderProxy, vm.$createElement); /* App.vue; var render = function() { var _vm = this var _h = _vm.$createElement var _c = _vm._self._c || _h return _c( "div", { attrs: { id: "app" } }, [ _c("img", { attrs: { src: require("./assets/logo.png") } }), _vm._v(" "), _c("router-view") ], 1 ) } */ initComponent(vnode, insertedVnodeQueue); insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert); if (isPatchable(vnode)) {} invokeCreateHooks(vnode, insertedVnodeQueue); cbs.create[i$1](emptyNode, vnode);//updateAttrs、updateClass、updateDOMListeners、updateDOMProps、updateStyle、_enter_、create、updateDirectives //_enter_ enter(vnode); //create registerRef(vnode); setScope(vnode); nodeOps.createElement(tag, vnode); setScope(vnode); createChildren(vnode, children, insertedVnodeQueue); checkDuplicateKeys(children); //3个createElm createElm(children[i], insertedVnodeQueue, vnode.elm, null, true); if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {} nodeOps.createElement(tag, vnode); setScope(vnode); createChildren(vnode, children, insertedVnodeQueue); invokeCreateHooks(vnode, insertedVnodeQueue); cbs.create[i$1](emptyNode, vnode); insert(parentElm, vnode.elm, refElm); nodeOps.appendChild(parent, elm); createElm(children[i], insertedVnodeQueue, vnode.elm, null, true); if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {} vnode.elm = nodeOps.createTextNode(vnode.text); insert(parentElm, vnode.elm, refElm); nodeOps.appendChild(parent, elm); createElm(children[i], insertedVnodeQueue, vnode.elm, null, true); if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {} i(vnode, false /* hydrating */, parentElm, refElm); initComponent(vnode, insertedVnodeQueue); insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert); if (isPatchable(vnode)) {} invokeCreateHooks(vnode, insertedVnodeQueue); cbs.create[i$1](emptyNode, vnode);//updateAttrs、updateClass、updateDOMListeners、updateDOMProps、updateStyle、_enter_、create、updateDirectives //_enter_ enter(vnode); //create registerRef(vnode); setScope(vnode); invokeCreateHooks(vnode, insertedVnodeQueue); insert(parentElm, vnode.elm, refElm); nodeOps.insertBefore(parent, elm, ref$$1);//页面展示出来,created data; invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); return vnode.elm popTarget(); Dep.target = targetStack.pop(); this.cleanupDeps(); dep.removeSub(this$1); remove(this.subs, sub); this.newDepIds.clear(); return value;//value=undefined; return vm;//$el为div#app这个VueComponent;
这边有一篇文章 剖析Vue原理&实现双向绑定MVVM 讲的很细,就不再重复写
以上面文章的内容为基础补充一张 vue数据双向绑定原理图
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/93298.html
摘要:而钻研最好的方式,就是阅读的源代码。整个的源代码,核心内容包括两部分。逃而动手脚的代码,就存在于源代码的中。整个源代码读下来一遍,虽然有些部分不太理解,但是对和一些代码的使用的理解又加深了一步。 笔记中的Vue与Vuex版本为1.0.21和0.6.2,需要阅读者有使用Vue,Vuex,ES6的经验。 起因 俗话说得好,没有无缘无故的爱,也没有无缘无故的恨,更不会无缘无故的去阅读别人的源...
摘要:扎实基础幸好自己之前花了大力气去给自己打基础,让自己现在的基础还算不错。 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧 【Vue原理】Vue源码阅读总结大会 - 序 阅读源码是需...
摘要:没有具体对应源码分析,只是阅读源码的笔记,等之后学好点再写成文章,构造生成的。带指令的所有元素,通过获取,涉及,返回属性选择器对里的进行绑定处理节点提取所有,返回数组,元素是对象,包含。 没有具体对应源码分析,只是阅读源码的笔记,等之后学好点再写成文章 Vue self,构造生成的this。 root根元素。 els带指令的所有元素,通过querySelectorAll获取,涉及ge...
摘要:实际上,我在看代码的过程中顺手提交了这个,作者眼明手快,当天就进行了修复,现在最新的代码里已经不是这个样子了而且状态机标识由字符串换成了数字常量,解析更准确的同时执行效率也会更高。 最近饶有兴致的又把最新版 Vue.js 的源码学习了一下,觉得真心不错,个人觉得 Vue.js 的代码非常之优雅而且精辟,作者本身可能无 (bu) 意 (xie) 提及这些。那么,就让我来吧:) 程序结构梳...
摘要:实际上,我在看代码的过程中顺手提交了这个,作者眼明手快,当天就进行了修复,现在最新的代码里已经不是这个样子了而且状态机标识由字符串换成了数字常量,解析更准确的同时执行效率也会更高。 最近饶有兴致的又把最新版 Vue.js 的源码学习了一下,觉得真心不错,个人觉得 Vue.js 的代码非常之优雅而且精辟,作者本身可能无 (bu) 意 (xie) 提及这些。那么,就让我来吧:) 程序结构梳...
阅读 973·2021-11-15 18:06
阅读 2325·2021-10-08 10:04
阅读 2573·2019-08-28 18:03
阅读 868·2019-08-26 13:42
阅读 1896·2019-08-26 11:31
阅读 2398·2019-08-23 17:13
阅读 894·2019-08-23 16:45
阅读 2031·2019-08-23 14:11