vue源码阅读笔记

说明
vue源码1万多行,完全解析透太耗时间;里面细节处理很多,通读代码,语法都不难;个人认为重点在于理解它的思想,掌握面向数据编程的原理。
通过一个合适的例子,断点调试来查看代码运行流程,可以快速了解编码的思路。
[email protected]

一、案例代码及运行流程

用api说明里面提供的命令行,生成的vue项目,稍微改动。
目录结构:
vue源码阅读笔记_第1张图片

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数据双向绑定原理图
vue源码阅读笔记_第2张图片

你可能感兴趣的:(vue.js,javascript)