ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

vue数据渲染的初始化(vue源码分析1)

2021-07-05 17:32:07  阅读:129  来源: 互联网

标签:function 初始化 vue el vnode 源码 var data 属性


以下代码和分析过程需要结合vue.js源码查看,通过打断点逐一比对。

一. 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="vue.js"></script>
</head>
<body>
    <div id="app">
        <!--this is comment--> {{ message }}
    </div>
    <div id="app1">
        <div>
            <!--count={{count}}-->
            <!--reversedCount={{reversedCount}}-->
        </div>
    </div>

    <script>
        debugger;
        var app = new Vue({
            el: '#app',
            beforeCreate() {},
            created() {},
            beforeMount() {},
            mounted: () => { //挂载元素,获取到DOM节点
                setTimeout(() => {
                    //this.count+=1;
                    //console.log('this.count='+this.count)
                }, 1000)
            },
            beforeUpdate() {},
            updated() {},//挂载元素,获取到DOM节点
            beforeDestroy() {},
            destroyed() {},
            data: function () {
                return {
                    count: 0,
                    message: 'Hello Vue!1111111'
                }
            },
        })
        })
    </script>

</body>

</html>

二.执行步骤

1. initMixin,初始化vue,挂载_init方法

执行initMixin(Vue) Vue是一个构造函数。

在Vue原型上挂载一个_init方法,这一步只是挂载方法,并没有执行。

image.png

2. stateMixin,数据绑定,$watch方法

2-1. 执行stateMixin(Vue) Vue是一个构造函数。

在Vue原型上挂载一个_init方法,这一步只是挂载方法,并没有执行。

定义一个data对象,一个props对象,并分别给这个2个对象添加get和set方法属性.

2-2. 然后执行Object.defineProperty,通过设定对象属性的 set/get 方法来监听数据的变化,

通过get进行依赖收集,而每个set方法就是一个观察者,在数据变更的时候通知订阅者更新视图。

 Object.defineProperty(Vue.prototype, '$data', dataDef); 
 Object.defineProperty(Vue.prototype, '$props', propsDef);

此时Vue.prototype上只有刚挂载的_init方法

image.png

此时dataDef只有get和set方法

image.png

Object.defineProperty执行完以后,这时候再看,这时候已经在Vue上挂载了get,set方法和$data和$props属性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yjiWPdYE-1625476714253)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7f75a7bcac78409d825f623f48687aa3~tplv-k3u1fbpfcp-watermark.image)]

2-3 然后执行

  Vue.prototype.$set = set;
  Vue.prototype.$delete = del;

后面调用的时候我们再看下set和del方法的定义。

2-4 然后挂载$watch

这个方法我们也后面分析,ok,现在看下Vue挂载了哪些方法:

image.png

stateMixin执行完毕

3 eventsMixin,初始化事件绑定方法

3-1. 依次挂载$on,$once,$off,$emit,方法属性

这几个方法只是挂载,我们也后面分析

此时Vue挂载了这些属性:

image.png

4 lifecycleMixin,初始化 更新 销毁 函数

4-1. 依次挂载_update,$forceUpdate,$$destroy,方法属性

这几个方法只是挂载,我们也后面分析

  • _update在【14. 定义指令章节】

此时Vue挂载了这些属性:

image.png

5 renderMixin,初始化vue 需要渲染的函数

5-1. 首先执行 installRenderHelpers

//在Vue的原型上挂载渲染相关的工具函数
installRenderHelpers(Vue.prototype)

函数的入参就是vue的原型,如图:

image.png

installRenderHelpers函数执行完后,这时候Vue的原型上多了渲染工具方法属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PP8go0g1-1625476714260)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/21a8f9af8ddd42cebdb297638bf527b1~tplv-k3u1fbpfcp-watermark.image)]

5-1. 接着挂载$nextTick_render

  • nextTick 延迟回调
  • _render渲染函数

6 声明一些变量

  • patternTypes //类型数组
  • KeepAlive //以组件的格式定义内置组件的属性
  • builtInComponents // 内置保存状态的组件

7 initGlobalAPI(Vue), 初始化全局api 并且暴露 一些静态方法

7-1 挂载基础属性

给配置对象configDef挂载一个get属性,它是一个函数,返回前面的定义的config对象,此时只是挂载,并没执行,因此只有一个get方法。

image.png

接着挂载个set方法,提示一些警告。

然后执行

/**
 * 在这里,为Vue的构造函数添加一个要通过Object.defineProperty监听的属性config,
 * 将configDef中的set和get方法关联到config上。
 * 获取的时候,拿到的是上面描述的那个config对象,如果对这个config对象直接做变更,
 * 就会提示“不要替换vue.config对象,而是设置单个字段”,说明,
 * vue不希望我们直接去替换和变更整个config对象,如果有需要,希望去直接修改我们需要修改的值
*/
Object.defineProperty(Vue, 'config', configDef);
  • 接着在Vue构造函数上挂载util,set,delete,nextTick方法,挂载一个空的options。

  • 然后遍历指令集合,在Vue.options挂载相应指令,然后设置_base属性值为Vue

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EJNcVDeC-1625476714261)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f33c90dd98384ca18f0d96638d0527fd~tplv-k3u1fbpfcp-watermark.image)]

合并KeepAlive组件到components中

extend(Vue.options.components, builtInComponents)

image.png

7-2 initUse

初始化vue 安装插件函数

在Vue上挂载一个use方法。

7-3 initMixin$1

initMixin$1(Vue); //初始化vue mixin 函数

在Vue上挂载一个mixin方法, 将传入的mixin(执行时传入的)覆盖到options项上。

在Vue上挂载一个use方法。

7-4 initExtend

initExtend //初始化vue extend 函数

在Vue上挂载一个extend方法,

7-5 initAssetRegisters

initAssetRegisters //为vue 添加 静态方法component,directive,filter

到此,initGlobalAPI方法执行完毕。

8. Object.defineProperty响应式一些属性

//监听是否是服务器环境
    Object.defineProperty(Vue.prototype, '$isServer', {
        get: isServerRendering //判断是不是node 服务器环境
    });
    // 获取$ssrContext
    Object.defineProperty(Vue.prototype, '$ssrContext', {
        get: function get() {
            return this.$vnode && this.$vnode.ssrContext
        }
    });

    //为ssr运行时帮助程序安装公开FunctionalRenderContext 创建 虚拟dom vonde 渲染 slots插槽
    Object.defineProperty(Vue, 'FunctionalRenderContext', {
        value: FunctionalRenderContext
    });

此时观察Vue.prototype:

image.png

9. 设置版本号

Vue.version = '2.5.16'; //版本号

10. 定义判断属性相关函数

// 以下都是通过makeMap函数返回一个函数,去查找map中是否存在val,存在返回true,否则返回false
isReservedAttr // 判断'style,class'
acceptValue    // 判断'input,textarea,option,select,progress'
mustUseProp    // 判断属性和标签是否匹配
isEnumeratedAttr    // 判断'contenteditable(编辑),draggable(拖动),spellcheck(拼写)'
isBooleanAttr  // 检查是否是html中的布尔值属性(属性值只有 true 和 false)
isXlink        // 判断是否是xmlns 属性
getXlinkProp   // 获取xml link的属性
isFalsyAttrValue   // 判断val 是否是 null 或者 false
var namespaceMap = {
     svg: 'http://www.w3.org/2000/svg', //svg标签命名xmlns属性
      math: 'http://www.w3.org/1998/Math/MathML' //math 中的xmlns属性声明 XHTML 文件
};
isFalsyAttrValue   // 判断val 是否是 html中的原始标签
isSVG              // 判断svg 标签 以及 svg子元素标签
isPreTag           // 判断标签是否是pre
isReservedTag      // 判断是不是 html 原生的标签 或者svg标签
getTagNamespace    // 判断 是否是svg 或者 math 标签
unknownElementCache    // 判断 是否是svg 或者 math 标签
isTextInputType    // 判断 是否是文本输入框 判断属性:text,number,password,search,email,tel,url

11. nodeOps冻结节点

Object.freeze() 方法可以冻结一个对象。

  • 一个被冻结的对象再也不能被修改;
  • 冻结了一个对象则不能向这个对象添加新的属性
  • 不能删除已有属性
  • 不能修改该对象已有属性的可枚举性、可配置性、可写性
  • 以及不能修改已有属性的值。
  • 此外,冻结一个对象后该对象的原型也不能被修改。
  • freeze() 返回和传入的参数相同的对象。
var nodeOps = Object.freeze({
        createElement: createElement$1, //创建一个真实的dom
        createElementNS: createElementNS, //创建一个真实的dom svg方式
        createTextNode: createTextNode, // 创建文本节点
        createComment: createComment,  // 创建一个注释节点
        insertBefore: insertBefore,  //插入节点 在xxx  dom 前面插入一个节点
        removeChild: removeChild,   //删除子节点
        appendChild: appendChild,  //添加子节点 尾部
        parentNode: parentNode,  //获取父亲子节点dom
        nextSibling: nextSibling,     //获取下一个兄弟节点
        tagName: tagName,   //获取dom标签名称
        setTextContent: setTextContent, //  //设置dom 文本
        setStyleScope: setStyleScope  //设置组建样式的作用域
    });

image.png

下面逐个分析 对象的每一项

11-1 createElement$1

创建一个真实的dom

function createElement$1(tagName, vnode) {
        //创建一个真实的dom
        var elm = document.createElement(tagName);
        //如果不是select标签则返回dom出去, 退出函数
        if (tagName !== 'select') { 
            return elm
        }
        // false or null will remove the attribute but undefined will not
        // false或null将删除属性,但undefined不会
        // 否则,如果是select标签 判断是否设置了multiple属性。如果设置就重置
        if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {
            elm.setAttribute('multiple', 'multiple');
        }
        return elm
    }

11-2 createElementNS

创建一个真实的dom svg方式,document.createElementNS方法可创建带有指定命名空间的元素节点。

    //创建一个真实的dom svg方式
    function createElementNS(namespace, tagName) {
        /**  namespaceMap,前面定义值为 {
             svg: 'http://www.w3.org/2000/svg',
             math: 'http://www.w3.org/1998/Math/MathML'
            };
            例如:document.createElementNS('http://www.w3.org/2000/svg','svg');
        */ 
        return document.createElementNS(namespaceMap[namespace], tagName)
    }

document.createElementNS用法:

var c=document.createElementNS('http://www.w3.org/2000/svg','svg') //创建svg节点
document.body.appendChild(c);

可以看到这dom中插入了一对svg标签

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w7e7SGbM-1625476714264)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4bf1af7ba63f432e97f6c889c79c6490~tplv-k3u1fbpfcp-watermark.image)]

11-3 createTextNode

创建文本节点

function createTextNode(text) {
   return document.createTextNode(text)
}

11-4 createComment

创建注释节点

function createTextNode(text) {
   return document.createTextNode(text)
}

document.createComment用法:

var c=document.createComment("My personal comments"); // 创建注释
document.body.appendChild(c); //插入节点

可以看到这dom中插入了一条注释

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-heXm7PM9-1625476714265)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6a52060cfaf84f078ff37695824617aa~tplv-k3u1fbpfcp-watermark.image)]

11-5 insertBefore

在父节点的某个子节点前插入一个新的节点

function insertBefore(parentNode, newNode, referenceNode) {
        parentNode.insertBefore(newNode, referenceNode);
}

insertBefore用法:

/**
node.insertBefore(newnode,existingnode) 方法可在已有的子节点前插入一个新的子节点。
node: 被插入的父节点。
newnode: 必须。要插入的节点对象
existingnode必须。要添加新的节点前的子节点。
例如:
*/
<ul id="List">
    <li>上海</li>
    <li>深圳</li>
</ul>
var newCity = document.createElement("li")  //创建元素
var textnode = document.createTextNode("北京") // 创建元素
newCity.appendChild(textnode)  // 添加文本
var list = document.getElementById("List")
// 在父节点ul的第一个节点li前添加一个newCity节点
list.insertBefore(newCity,list.childNodes[0]);

执行完后:
<ul id="List">
    <li>北京</li>
    <li>上海</li>
    <li>深圳</li>
</ul>

11-6 其它节点操作

剩下的比较简单,就放一起讲了

    //删除子节点
    function removeChild(node, child) {
        node.removeChild(child);
    }

    //添加子节点 尾部
    function appendChild(node, child) {
        node.appendChild(child);
    }

    //获取父亲子节点dom
    function parentNode(node) {
        return node.parentNode
    }

    //获取下一个兄弟节点
    function nextSibling(node) {
        return node.nextSibling
    }

    //获取dom标签名称
    function tagName(node) {
        return node.tagName
    }

    //设置dom 文本
    function setTextContent(node, text) {
        node.textContent = text;
    }


    //设置组建样式的作用域,设置后只在当前组件生效
    function setStyleScope(node, scopeId) {
        node.setAttribute(scopeId, '');
    }

到这里,nodeOps冻结节点就讲完了

12. 定义ref 创建 更新 和 销毁 事件

var ref = {
        create: function create(_, vnode) {
            //创建注册一个ref
            registerRef(vnode);
        },
        update: function update(oldVnode, vnode) {
            //更新ref
            if (oldVnode.data.ref !== vnode.data.ref) {

                registerRef(oldVnode, true); //先删除
                registerRef(vnode);  //在添加
            }
        },
        destroy: function destroy(vnode) {
            registerRef(vnode, true); //删除销毁ref
        }
    }

12-1. create

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件$refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。

/**
     * 注册ref或者删除ref。比如标签上面设置了ref='abc' 
     * 那么该函数就是为this.$refs.abc 注册ref 把真实的dom存进去
     */
    function registerRef(
        vnode, // 虚拟dom对象
        isRemoval // 是否销毁ref
    ) {
        debugger
        var key = vnode.data.ref;  //获取vnode ref的字符串
        if (!isDef(key)) {  //没有定义则退出函数,可以看下面的解析
            return
        }
        var vm = vnode.context; //context 上下文
        //优先获取vonde的组件实例(对于组件来说),或者el(该Vnode对应的DOM节点,非组件来说)
        var ref = vnode.componentInstance || vnode.elm; 
        var refs = vm.$refs;
        // 如果需要销毁ref
        if (isRemoval) {
            if (Array.isArray(refs[key])) {
                remove(refs[key], ref); 
            } else if (refs[key] === ref) { 
                refs[key] = undefined;  
            }
        } else {
            if (vnode.data.refInFor) {  //当在v-for之内时,则保存为数组形式
                if (!Array.isArray(refs[key])) { //refs[key] 不是数组 则变成一个数组
                    refs[key] = [ref];
                } else if (refs[key].indexOf(ref) < 0) { //如果ref 不存在 refs的时候则添加进去
                    // $flow-disable-line
                    refs[key].push(ref);
                }
            } else {
                refs[key] = ref; //如果是单个直接赋值
            }
        }
    }

isDef:

//判断数据是否定义(注意!值为0或者false也是定义过了的)
function isDef(v) {
    return v !== undefined && v !== null
}

12-2. update

update: function update(oldVnode, vnode) {
            //如果新旧ref不一致,执行更新ref的操作
            if (oldVnode.data.ref !== vnode.data.ref) {
                registerRef(oldVnode, true); //先删除
                registerRef(vnode);  //在添加
            }
        },

12-3. destroy

destroy: function destroy(vnode) {
            //删除销毁ref
            registerRef(vnode, true); 
        }

13. 定义空vnode(emptyNode)和hooks

先看下vnode有哪些属性:

image.png

// 定义一些声明周期相关的hooks
var hooks = ['create', 'activate', 'update', 'remove', 'destroy'];

14. 定义指令

var directives = {
        create: updateDirectives, //创建指令
        update: updateDirectives,  //更新指令
        destroy: function unbindDirectives(vnode) {  //销毁指令
            updateDirectives(vnode, emptyNode);
        }
    }

这三个属性其实用到的是同一个函数,updateDirectives

//更新指令
    function updateDirectives(
        oldVnode, //oldVnode 老数据
        vnode     //vnode 新数据 
        ) {
        // 只要新旧指令存在,就更新
        if (oldVnode.data.directives || vnode.data.directives) {
            _update(oldVnode, vnode);
        }
    }

接着分析_update函数,这个函数比较长,我们细细分析

  /**
     * 更新指令 比较oldVnode和vnode,根据oldVnode和vnode的情况 
     * 触发指令钩子函数bind,update,inserted,insert,componentUpdated,unbind钩子函数
     */
    function _update(
        oldVnode, //oldVnode 老数据
        vnode     //vnode 新数据 
    ) {
        // 如果旧节点是空节点,就表示当前操作为创建,首次创建
        var isCreate = oldVnode === emptyNode;
        //如果新节点是空节点,就表示当前操作为销毁
        var isDestroy = vnode === emptyNode;
        //规范化的指令,为指令属性修正变成规范的指令数据。返回指令数据集合
        // 这个方法请看下面的解析
        var oldDirs = normalizeDirectives$1(
            oldVnode.data.directives, //vonde指令对象集合
            oldVnode.context //vm vne实例化对象,或者是组件实例化的对象
        );
        //规范化的指令,为指令属性修正变成规范的指令数据。返回指令数据集合
        var newDirs = normalizeDirectives$1(
            vnode.data.directives, //vonde指令对象集合
            vnode.context //vm vne实例化对象,或者是组件实例化的对象
        );

        var dirsWithInsert = []; // 触发inserted指令钩子函数的指令列表。
        var dirsWithPostpatch = []; // 触发componentUpdated钩子函数的指令列表。

        var key, oldDir, dir;
        for (key in newDirs) {      //循环新的指令集合
            oldDir = oldDirs[key];  //获取旧的单个指令值
            dir = newDirs[key];     //获取新的单个指令值

            if (!oldDir) { //新增指令,触发bind
                // new directive, bind 新指令,绑定
                callHook$1(  //例:v-focus指令的bind函数
                    dir, //新的指令值
                    'bind', //触发bind钩子函数
                    vnode,//新的vonde
                    oldVnode //旧的vonde
                );
                if (dir.def && dir.def.inserted) {
                    // 如果有插入指令,加入列表
                    dirsWithInsert.push(dir); 
                }
            } else {
                // 指令已存在触发update
                // existing directive, update 现有的指令,更新
                // 如有指令 <div v-hello='123'></div> value=123. 如果更新了123 就是更新值
                dir.oldValue = oldDir.value; 
                callHook$1(
                    dir,
                    'update',  //触发更新钩子函数
                    vnode,
                    oldVnode
                );
                 // 如果有更新指令,加入列表
                if (dir.def && dir.def.componentUpdated) { 
                    dirsWithPostpatch.push(dir);
                }
            }
        }

        // 此时完成bind和update钩子函数,列表更新完成

        if (dirsWithInsert.length) {
            // 定义一个函数,该函数会执行dirsWithInsert里的每个函数
            var callInsert = function () {
                for (var i = 0; i < dirsWithInsert.length; i++) {
                    callHook$1(
                        dirsWithInsert[i], //新的指令值 也就是上面的dir(新的单个指令值)
                        'inserted', //触发inserted钩子函数
                        vnode, //新的vonde
                        oldVnode //旧的vonde
                    );
                }
            };
            if (isCreate) {
                //如果是初始化  
                mergeVNodeHook(
                    vnode,
                    'insert',//合并钩子函数
                    callInsert
                );
            } else {
                callInsert();
            }
        }

        if (dirsWithPostpatch.length) {
            mergeVNodeHook(vnode,
                'postpatch',
                function () {
                    for (var i = 0; i < dirsWithPostpatch.length; i++) {
                        callHook$1(
                            dirsWithPostpatch[i],
                            'componentUpdated',
                            vnode, oldVnode);
                    }
                });
        }

        if (!isCreate) {
            for (key in oldDirs) {
                if (!newDirs[key]) { //新的vonde 中没有了指令
                    // no longer present, unbind 不再存在,解除束缚
                    callHook$1(
                        oldDirs[key],
                        'unbind', //触发unbind 钩子
                        oldVnode,
                        oldVnode,
                    );
                }
            }
        }
    }

接着分析normalizeDirectives$1,它修正指令属性变成规范的指令数据,返回指令数据集合

function normalizeDirectives$1(
        dirs, //vonde 指令集合
        vm //vm vne实例化对象,或者是组件实例化的对象
    ) {
        //创建一个空的对象
        var res = Object.create(null);
        //如果 指令 名称dirs 不存在 则返回一个空的对象
        if (!dirs) {
            // $flow-disable-line
            return res
        }

        var i, dir;
        for (i = 0; i < dirs.length; i++) { //循环遍历指令集合
            dir = dirs[i];
            if (!dir.modifiers) { //判断是否有修饰符
                // $flow-disable-line
                dir.modifiers = emptyModifiers; //空对象
            }
            //返回指令名称 或者属性name名称+修饰符
            res[getRawDirName(dir)] = dir;
            /**
            *给当前指令挂载自定义指令属性,该属性由用户自定义如 
            *bind,inserted,update,componentUpdated,unbind这些
            */
            dir.def = resolveAsset(vm.$options, 'directives', dir.name, true);
        }
        // $flow-disable-line
        return res
    }

接着看getRawDirName函数,返回指令名称 或者属性name名称+修饰符

    function getRawDirName(dir) {
        //rawName 视图中的 指令如 <div v-hello></div>  就是v-hello
        //name 视图中的 指令如 <div v-hello></div>  就是hello
        //name 视图中的 指令如有修饰符 <div v-hello.native></div>  就是hello.native
        //modifiers 修饰符
        return dir.rawName || ((dir.name) + "." + (Object.keys(dir.modifiers || {}).join('.')))
    }

此时res[getRawDirName(dir)] = dir,已经将指令名作为res的属性了,并且将指令作为属性值。

接着检测指令是否在 组件对象上面 ,返回注册指令或者组建的对象

    function resolveAsset(
        options, //参数 例:vm.$options
        type, // 类型 例:'directives' , 'filters' ,'components'
        id,   // 指令,组件的key 属性  例:dir.name
        warnMissing //开启警告的信息 例:true
    ) {
        /* istanbul ignore if  如果id不是字符串,退出函数 */
        // 返回逻辑【1】
        if (typeof id !== 'string') {
            return
        }
        var assets = options[type]; // 例: vm.$options['components']
        // check local registration variations first
        /**
         * 首先检查本地注册的变化 判断id(组件等的name)是否是assets自有属性
         * 否则判断将id驼峰后的key,是否是assets自有属性
         * 否则判断将id驼峰后,再首字母变大写的key,是否是assets自有属性
         */
        // 例:判断v-modal 指令,在不在options['directives']中
        // 例:判断my-header 组件,在不在options['components']中
        /**
         * 所以,我们在Vue引入某个组件时候,我们可以在template写组件标签用驼峰的方式,
         * 也可以是首字母大写,或是直接用组件名来当作组件的标签,就是因为这里做了这样的扩展处理
         */

        // 执行返回逻辑【2】
        if (hasOwn(assets, id)) {
            return assets[id]
        }

        //  可以让这样的的属性 v-model 变成 vModel  变成驼峰
        var camelizedId = camelize(id);
        // 执行返回逻辑【3】
        if (hasOwn(assets, camelizedId)) {
            return assets[camelizedId]
        }

        // 将首字母变成大写 即 vModel 变成 VModel
        var PascalCaseId = capitalize(camelizedId);
        // 执行返回逻辑【4】
        if (hasOwn(assets, PascalCaseId)) {
            return assets[PascalCaseId]
        }

        // fallback to prototype chain  回到原型链
        var res = assets[id] || assets[camelizedId] || assets[PascalCaseId];
        // 如果以上都不成立且是开发环境则警告
        if ("development" !== 'production' && warnMissing && !res) {
            warn(
                'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
                options
            );
        }
        //返回注册指令或者组建的对象(原型上的)
        return res
    }

这里有到工具函数hasOwn:

  /**
     * Check whether the object has the property.
     *检查对象属性是在自身上还是原型上,在自身上返回true
     */
    var hasOwnProperty = Object.prototype.hasOwnProperty;

    function hasOwn(obj, key) {
        return hasOwnProperty.call(obj, key)
    }

接着将我们的指令name变成驼峰的写法,camelize:

/**
     把横线-的转换成驼峰写法
     这个正则可以让这样的的属性 v-model 变成 vModel
     把名称格式为“xx-xx”的变为“xxXx”,这里接收的是当前的props属性值,一个字符串
     */
    var camelizeRE = /-(\w)/g;
    var camelize = cached(function (str) {
        return str.replace(camelizeRE, function (_, c) {
            /**
             * var str="Hello World!"
               str.toUpperCase() //HELLO WORLD! 将小写转为大写
             */
            return c ? c.toUpperCase() : '';
        })
    });

接着看下cached函数:

// 它将我们需要调用的一些函数给封装到一个对象里面,需要的时候就去对象取
function cached(fn) {
        var cache = Object.create(null); //这样创建的没有原型的空对象 
        return (function cachedFn(str) {
            var hit = cache[str];
            return hit || (cache[str] = fn(str))
        })
    }

为了更直接观的查看camelize函数,我们就将cached引入过来,再回到我们前面的camelizeRE函数,是不是清晰了很多:

var camelizeRE = /-(\w)/g;
    var camelize = function () {
        var cache = Object.create(null); //这样创建的没有原型的空对象 
        // 这个str就是某些属性的key,如id,v-modal
        // 也就是说,会先去缓存对象cache中判断有没有存在,id属性对应的函数,有就返回,没有的就设置
        return (function cachedFn(str) {
            var hit = cache[str];
            return hit
            || (cache[str] =  str.replace(camelizeRE, function (_, c) { //后面这一块就是cached的入参fn
                return c ? c.toUpperCase() : '';
            })(str))
        })
        
    };

ok,此时我们已经分析完camelize函数了,现在resolveAsset已经分析完了,拿到了指令集合。normalizeDirectives$1也执行完了,指令属性修正变成规范的指令数据.

接着回到我们的_update函数,新旧的指令集合我们都拿到了:

这时,我们遍历新旧指令集合,这里面又涉及到几个工具函数,我们依次分析

_update->callHook$1

    //触发指令钩子函数
    function callHook$1(
        dir,  //新的指令值
        hook, //钩子函数 例:bind
        vnode, //新的vnode
        oldVnode, //旧的vnode
        isDestroy  // 是否销毁  例:true
    ) {
        var fn = dir.def && dir.def[hook]; //获取属性上面的钩子函数
        if (fn) {
            try {
                fn(
                    vnode.elm, //真实dom
                    dir, //新的指令值
                    vnode, //新的vond
                    oldVnode, //旧的vonde
                    isDestroy //是否要销毁标记
                );
            } catch (e) {
                handleError(e, vnode.context, ("directive " + (dir.name) + " " + hook + " hook"));
            }
        }
    }

_update->mergeVNodeHook,合并vue vnode 钩子函数

 /*
     *  钩子函数的作用是把insert作为一个hooks属性保存到对应的Vnode的data上面,
        当该Vnode插入到父节点后会调用该hooks
     *  def[hookKey] = invoker; //把钩子函数用对象存起来
     * */

function mergeVNodeHook(
        def,  // vnode
        hookKey,  // 函数指令 例: 'insert'
        hook // 回调函数 例:callInsert()
        ) {
        // 则将它重置为VNode.data.hook,如果VNode.data.hook不存在
        // 则初始化为一个空对象 注:普通节点VNode.data.hook是不存在的。
        if (def instanceof VNode) {
            def = def.data.hook || (def.data.hook = {});
        }

        var invoker;
        //获取旧的oldHook 钩子
        var oldHook = def[hookKey];

        function wrappedHook() {
            //回调,执行钩子函数
            hook.apply(this, arguments);
            // important: remove merged hook to ensure it's called only once
            // and prevent memory leak
            // 重要:删除合并钩子以确保只调用一次
            // 和防止内存泄漏
            // 这个函数不解析了,太简单,删除invoker.fns数组中的wrappedHook项
            remove(invoker.fns, wrappedHook);
        }

        //如果旧的钩子函数没有 为空的时候,则创建一个钩子函数
        if (isUndef(oldHook)) { 
            // no existing hook
            invoker = createFnInvoker([wrappedHook]);
        } else {
            // istanbul ignore if  
            // 如果有老的钩子函数,并且fns钩子函数存在 并且已经合并过
            if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {
                // already a merged invoker 已合并的调用程序
                invoker = oldHook; //直接老的钩子函数直接覆盖新的钩子函数
                //为钩子函数的fns 添加一个函数
                invoker.fns.push(wrappedHook);
            } else {
                // existing plain hook
                invoker = createFnInvoker([oldHook, wrappedHook]);
            }
        }

        invoker.merged = true;
        //把钩子函数用对象存起来
        def[hookKey] = invoker;
    }

_update->mergeVNodeHook->createFnInvoker,创建一个钩子函数

    // 如果事件只是个函数就为为事件添加多一个静态类, invoker.fns = fns; 把真正的事件放在fns。
    // 而 invoker 则是转义fns然后再运行fns
    function createFnInvoker(
        fns // 传入参数可能是数组/函数,数组项是函数 例:invoker = createFnInvoker([wrappedHook]);
        ) {
        // 先看后面的赋值,再看这个函数定义
        function invoker() {
            var arguments$1 = arguments;

            //静态方法传进来的函数 赋值给fns
            var fns = invoker.fns;

            //判断fns 是否是一个数组
            if (Array.isArray(fns)) {
                //如果是数组 浅拷贝
                var cloned = fns.slice();
                //执行fns 数组中的函数 并且把 invoker  arguments$1参数一个个传给fns 函数中
                for (var i = 0; i < cloned.length; i++) {

                    cloned[i].apply(null, arguments$1);
                }
            } else {
                // return handler return value for single handlers
                //如果fns只是一个函数 则执行arguments$1参数一个个传给fns 函数中
                return fns.apply(null, arguments)
            }
        }
        // 挂载传入的函数数组到invoker的fns属性上
        invoker.fns = fns;
        return invoker  //静态类
    }

再回到_update函数,我们已经分析完了这个函数了,它实现了更新新旧节点指令和执行钩子函数。
回推回去,directives也分析完了,指令章节也分析完了。

15. 定义一些指令,属性的全局对象

//定义一个空的指令修饰对象
var emptyModifiers = Object.create(null);
var baseModules = [
        ref,  //创建,更新 ,销毁 函数 详细查看章节:【12. 定义ref】
        directives //自定义指令 创建 ,更新,销毁函数 详细查看章节:【14. 定义指令】
]
// 属性相关
var attrs = {
    create: updateAttrs, //创建属性
    update: updateAttrs  //更新属性
}
// 类相关
var klass = {
        create: updateClass,
        update: updateClass
}

15-1:现在分析下:attrs=》updateAttrs,更新属性,比较新的vnode和旧的oldVnode中的属性值

  • 如果不相等则设置属性;
  • 如果新的vnode 属性中没有了则删除该属性
    function updateAttrs(oldVnode, vnode) {
        debugger
        var opts = vnode.componentOptions;  //获取组件的拓展参数
        // 退出函数
        if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) { 
            return
        }
        // 退出函数
        if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {
            return
        }
        var key, cur, old;
        var elm = vnode.elm;
        var oldAttrs = oldVnode.data.attrs || {};  // 旧属性
        var attrs = vnode.data.attrs || {}; // 新属性
        // clone observed objects, as the user probably wants to mutate it
        // 克隆观察到的对象,因为用户可能希望对其进行变种

        if (isDef(attrs.__ob__)) {  //重新克隆一个
            attrs = vnode.data.attrs = extend({}, attrs);
        }

        // 如果不相等则设置属性
        for (key in attrs) { 
            cur = attrs[key];  // 新属性值
            old = oldAttrs[key]; // 旧属性值
            if (old !== cur) { 
                //设置属性
                setAttr(elm, key, cur);
            }
        }
        // #4391: in IE9, setting type can reset value for input[type=radio] 
        // #6666: IE/Edge forces progress value down to 1 before setting a max 
        /* istanbul ignore if */
        // 在IE9中,设置类型可以重置输入值[type=radio]
        // 在设置最大值之前,IE/Edge会将进度值降低到1

        // 如果是ie浏览器,或者是edge浏览器 新的值和旧的值不相等的时候
        if ((isIE || isEdge) && attrs.value !== oldAttrs.value) { 
            setAttr(elm, 'value', attrs.value); //设置新的value值
        }

        // 如果旧的属性不在新的属性中,说明要删除
        for (key in oldAttrs) { 
            if (isUndef(attrs[key])) {
                if (isXlink(key)) { //判断是否是xml
                    elm.removeAttributeNS(xlinkNS, getXlinkProp(key)); //设置属性
                }//如果不是 'contenteditable,draggable,spellcheck' 属性
                else if (!isEnumeratedAttr(key)) { 
                    elm.removeAttribute(key); //设置属性
                }
            }
        }
    }

现在分析下:attrs=》updateAttrs=>setAttr, 设置属性

    function setAttr(
        el, // vnode.elm
        key, // 属性key 例: 'value'
        value // 新属性值
        ) {
        //如果dom 标签名 含有'-' 则是自定义标签
        if (el.tagName.indexOf('-') > -1) {
            //设置属性
            baseSetAttr(el, key, value);
        } 
        // 检查是否是html中的布尔值属性  就是该属性只有 true 和 false
        // 详细查看:【10. 定义判断属性相关函数】
        else if (isBooleanAttr(key)) { 
            // set attribute for blank value 为空值设置属性
            // e.g. <option disabled>Select one</option>
            if (isFalsyAttrValue(value)) { 
                el.removeAttribute(key);
            } else {
                // technically allowfullscreen is a boolean attribute for <iframe>
                // but Flash expects a value of "true" when used on <embed> tag 
                // 从技术上讲,allowfullscreen是一个布尔属性
                // 但是Flash希望在<embed>标签上使用时,其值为"true"
                value = key === 'allowfullscreen' && el.tagName === 'EMBED'
                    ? 'true'
                    : key;
                el.setAttribute(key, value);
            }
        } else if 
        // 判断是否是contenteditable,draggable,spellcheck 这三个属性的其中一个
        // 详细查看:【10. 定义判断属性相关函数】
        (isEnumeratedAttr(key)) { 
            el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true');
        } else if 
        // 判断是否是xmlns 属性 例: <bookstore xmlns:xlink="http://www.w3.org/1999/xlink">
        (isXlink(key)) {   
            if (isFalsyAttrValue(value)) { //value 没有值
                //xml 则用个方法删除属性
                el.removeAttributeNS(xlinkNS, getXlinkProp(key));
            } else {
                //设置xml 属性
                el.setAttributeNS(xlinkNS, key, value);
            }
        } else {
            //设置基本属性
            baseSetAttr(el, key, value);
        }
    }

现在分析下:attrs=》updateAttrs=>setAttr=>baseSetAttr, 设置属性,做一些边界处理

    function baseSetAttr(
        el,   // dom节点
        key,  // 属性的 key
        value // 属性的值
    ) {
        // 判断val 是否是 【null,undefined,false】中的一个
        if (isFalsyAttrValue(value)) {
            el.removeAttribute(key);  //从dom中删除属性
        } else {
            // #7138: IE10 & 11 fires input event when setting placeholder on IE10和11在设置占位符时触发输入事件
            // <textarea>... block the first input event and remove the blocker 阻塞第一个输入事件并删除该阻塞程序
            // immediately.
            /* istanbul ignore if */
            if (
                isIE &&  //如果是is
                !isIE9 &&  //如果不是ie9  不支持ie9
                el.tagName === 'TEXTAREA' &&  //如果标签是TEXTAREA(textarea)
                key === 'placeholder' && 
                !el.__ieph
            ) {
                var blocker = function (e) {
                    /**
                     * 如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,
                     * 它们会按照被添加的顺序执行。如果其中某个监听函数执行了 event.stopImmediatePropagation() 方法,
                     * 则当前元素剩下的监听函数将不会被执行。
                     */
                    // stopImmediatePropagation 则是阻止事件冒泡
                    e.stopImmediatePropagation();
                    //删除input 事件
                    el.removeEventListener('input', blocker);
                };
                //添加新的input 事件
                el.addEventListener('input', blocker);
                // $flow-disable-line
                //标志已经添加过 或者更新过input事件
                el.__ieph = true;
                /* IE placeholder patched  占位符打补丁 */
            }
            //设置属性
            el.setAttribute(key, value);
        }
    }

设置属性更新updateAttrs分析完成,

15-2:现在分析下:klass=》updateClass,更新属性,更新 真实dom的 calss

    function updateClass(oldVnode, vnode) {
        var el = vnode.elm;  //获取【新】的 dom节点
        var data = vnode.data; //获取【新】的 vnode数据
        var oldData = oldVnode.data; //获取【旧的】oldVnode 数据
        // 边界处理,【可不看】
        if (
            isUndef(data.staticClass) && //如果没有定义静态的 staticClass
            isUndef(data.class) && //没有定义calss
            (
                isUndef(oldData) ||
                (
                    isUndef(oldData.staticClass) && isUndef(oldData.class)
                )
            )
        ) {
            // 退出函数
            return
        }
        //class 转码获取vonde 中的staticClass 静态class  和class动态class转义成真实dom需要的class格式。然后返回class字符串

        var cls = genClassForVnode(vnode);

        // handle transition classes
        // 处理转换类
        var transitionClass = el._transitionClasses;
        if (isDef(transitionClass)) {
            cls = concat(cls, stringifyClass(transitionClass));
        }

        // set the class _prevClass 上一个css表示是否已经更新过
        if (cls !== el._prevClass) {
            el.setAttribute('class', cls);
            el._prevClass = cls;
        }
    }

klass=》updateClass=》genClassForVnode,将class 转码,获取vonde 中的staticClass(静态class) 和class(动态class),转义成真实dom需要的class格式,然后返回class字符串:

    function genClassForVnode(vnode) {
        var data = vnode.data;  //获取vnode.data 数据 标签属性数据
        var parentNode = vnode; //获取 父节点
        var childNode = vnode; //获取子节点

        while (isDef(childNode.componentInstance)) { 
            // 如果定义了componentInstance(组件实例),递归合并子组件的class
            childNode = childNode.componentInstance._vnode; //上一个vnode
            if (childNode && childNode.data) {
                data = mergeClassData(childNode.data, data);
            }
        }
        while (isDef(parentNode = parentNode.parent)) { //递归父组件parent 合并父组件class
            if (parentNode && parentNode.data) {
                //合并calss数据
                data = mergeClassData(data, parentNode.data);
            }
        }
        return renderClass(data.staticClass, data.class) //渲染calss
    }

klass=》updateClass=》genClassForVnode=》mergeClassData,合并calss数据

    function mergeClassData(child, parent) {
        return {
            staticClass: concat(child.staticClass, parent.staticClass), //静态calss
            class: isDef(child.class)  //data中动态calss
                ? [child.class, parent.class]
                : parent.class
        }
    }

klass=》updateClass=》genClassForVnode=》renderClass,渲染calss 这里获取到已经转码的calss

    function mergeClassData(child, parent) {
        return {
            staticClass: concat(child.staticClass, parent.staticClass), //静态calss
            class: isDef(child.class)  //data中动态calss
                ? [child.class, parent.class]
                : parent.class
        }
    }

klass=》updateClass=》genClassForVnode=》renderClass=》stringifyClass,转码 class,把数组格式,对象格式的calss 全部转化成 字符串格式

    function stringifyClass(value) {
        if (Array.isArray(value)) { //如果是数组
            // 数组变成字符串,然后用空格 隔开 拼接 起来变成字符串
            // 本质是递归调用,数组的每一项调用stringifyClass
            return stringifyArray(value)
        }
        if (isObject(value)) {
            return stringifyObject(value)
        }
        //直到全部转成 字符串才结束递归
        if (typeof value === 'string') {
            return value
        }
        /* istanbul ignore next */
        return ''
    }

klass=》updateClass=》genClassForVnode=》renderClass=》stringifyClass=>stringifyArray, 数组变成字符串,然后用空格 隔开 拼接 起来变成字符串

    function stringifyArray(value) {
        var res = '';
        var stringified;
        for (var i = 0, l = value.length; i < l; i++) {
            // 如果value[i]的类型都没有命中,则stringifyClass返回'',即stringified为'',则不进行拼接
            if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') {
                if (res) {
                    res += ' ';
                }
                res += stringified;
            }
        }
        return res
    }

klass=》updateClass=》genClassForVnode=》renderClass=》stringifyClass=>stringifyObject,对象字符串变成字符串,然后用空格 隔开 拼接 起来变成字符串

    function stringifyObject(value) {
        var res = '';
        for (var key in value) {
            if (value[key]) {
                if (res) {
                    res += ' ';
                }
                res += key;
            }
        }
        return res
    }

stringifyClass分析完成,renderClass执行完毕(拿到了转码后的class),genClassForVnode(拿到转义成真实dom需要的class格式)也执行完毕,updateClass(拿到了真实dom的 calss)

16. 定义更新dom事件

var events = {
    create: updateDOMListeners,
    update: updateDOMListeners
}

16-1. updateDOMListeners,更新dom事件

    function updateDOMListeners(oldVnode, vnode) {
        // 边界处理
        if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
            return
        }
        var on = vnode.data.on || {};
        var oldOn = oldVnode.data.on || {};
        target$1 = vnode.elm; //真实的dom
        normalizeEvents(on);    //为事件 多添加 change 或者input 事件加进去
        // 更新数据源 并且为新的值 添加函数 旧的值删除函数等功能
        updateListeners(
            on, //新的事件对象
            oldOn, //旧的事件对象
            add$1, //添加真实dom的事件函数
            remove$2, //删除真实dom的事件函数
            vnode.context //vue 实例化的对象 new Vue 或者组件 构造函数实例化的对象
        );
        target$1 = undefined;
    }

16-1. updateDOMListeners=>normalizeEvents,为事件 多添加 change 或者input 事件加进去,可不关注

    function normalizeEvents(on) {
        /* istanbul ignore if */
        if (isDef(on[RANGE_TOKEN])) {
            // IE input[type=range] only supports `change` event
            // 判断是否是ie 浏览器,如果是则选择 change 事件,如果不是则选择input事件
            var event = isIE ? 'change' : 'input';  
            // 连接事件 把change或者input 事件添加进去
            on[event] = [].concat(on[RANGE_TOKEN], on[event] || []); 
            delete on[RANGE_TOKEN]; //删除旧的事件
        }
        // This was originally intended to fix #4521 but no longer necessary
        // after 2.5. Keeping it for backwards compat with generated code from < 2.4
        /* istanbul ignore if */
        //最初的目的是修复#4521,但现在已经没有必要了
        // 2.5之后。保留它以便与< 2.4生成的代码进行反向比较
        //添加change事件
        if (isDef(on[CHECKBOX_RADIO_TOKEN])) {

            on.change = [].concat(on[CHECKBOX_RADIO_TOKEN], on.change || []);
            delete on[CHECKBOX_RADIO_TOKEN];
        }
    }

16-1. updateDOMListeners=>updateListeners,更新数据源 并且为新的值 添加函数 旧的值删除函数等功能

    function updateListeners(
        on,  //新的事件
        oldOn, //旧的事件
        add,  //添加事件函数
        remove$$1, //删除事件函数
        vm//vue 实例化对象
    ) {
        var name, def, cur, old, event;

        for (name in on) {
            def = cur = on[name];  //on 新的事件值
            old = oldOn[name];  // 旧的值
            event = normalizeEvent(name);   //normalizeEvent 如果是事件,则过滤 事件修饰符

            /* istanbul ignore if */
            // isUndef 值是空的 undefined || null
            if (isUndef(cur)) {
                //如果不是生产环境
                "development" !== 'production' && warn(
                    "Invalid handler for event \"" + (event.name) + "\": got " + String(cur),
                    vm
                );
            } else if (isUndef(old)) {

                if (isUndef(cur.fns)) { //如果函数不存在 则绑定函数
                    //函数 获取钩子函数
                    // 创建函数调用器并重新复制给cur和on[name]
                    cur = on[name] = createFnInvoker(cur); //这个时候cur.fns就存在了
                }



                name = '&' + name; // mark the event as passive 将事件标记为被动的
                //添加事件
                add(
                    event.name, //事件名称
                    cur, // 转义过的事件 执行静态类
                    event.once, //是否只触发一次的状态
                    event.capture, //  事件俘获或是冒泡行为
                    event.passive, // 检测事件修饰符 是否是   '&'
                    event.params //事件参数
                );

            } else if (cur !== old) {
                //如果新的值不等于旧的值
                //则更新新旧值
                old.fns = cur;
                on[name] = old;
            }
        }
        for (name in oldOn) {
            //循环旧的值 为空的时候
            if (isUndef(on[name])) {
                //获取事件
                event = normalizeEvent(name);
                //删除旧的值的事件
                remove$$1(event.name, oldOn[name], event.capture);
            }
        }
    }

updateDOMListeners分析完成。

17. updateDOMProps更新真实dom的props属性

var domProps = {
        create: updateDOMProps, //更新真实dom的props 属性值
        update: updateDOMProps
}

17-1. updateDOMProps,更新真实dom的props属性

function updateDOMProps(oldVnode, vnode) {

        if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {
            return
        }
        var key, cur;
        var elm = vnode.elm;
        var oldProps = oldVnode.data.domProps || {}; //获取旧的props属性
        var props = vnode.data.domProps || {}; //获取新的props
        // clone observed objects, as the user probably wants to mutate it
        // 克隆观察到的对象,因为用户可能希望对其进行修改
        // 如果是props添加了观察者,重新克隆他,这样就可以修改了
        if (isDef(props.__ob__)) { 
            props = vnode.data.domProps = extend({}, props);
        }

        for (key in oldProps) {
            if (isUndef(props[key])) {
                elm[key] = '';
            }
        }
        for (key in props) {
            cur = props[key]; 
            // ignore children if the node has textContent or innerHTML,
            // as these will throw away existing DOM nodes and cause removal errors
            // on subsequent patches (#3360)
            //忽略子节点,如果节点有textContent或innerHTML,
            //因为这将丢弃现有的DOM节点并导致删除错误
            //其后的修补程式(#3360)
            if (
                key === 'textContent' ||
                key === 'innerHTML'
            ) {
                if (vnode.children) {
                    vnode.children.length = 0;
                }
                if (cur === oldProps[key]) {
                    continue
                }
                // #6601 work around Chrome version <= 55 bug where single textNode
                // replaced by innerHTML/textContent retains its parentNode property
                // #6601解决Chrome版本<= 55的bug,其中只有一个textNode
                //被innerHTML/textContent替换后,保留了它的parentNode属性
                if (elm.childNodes.length === 1) { //文本节点
                    elm.removeChild(elm.childNodes[0]);
                }
            }

            if (key === 'value') {
                // store value as _value as well since
                // non-string values will be stringified
                //将value存储为_value以及since
                //非字符串值将被字符串化
                elm._value = cur;
                // avoid resetting cursor position when value is the same
                // 当值相同时,避免重置光标位置
                var strCur = isUndef(cur) ? '' : String(cur); //转义成字符串
                if (shouldUpdateValue(
                    elm,   //真实的dom
                    strCur //value
                )) {
                    elm.value = strCur; //赋值
                }
            } else {
                elm[key] = cur; //直接赋值
            }
        }
    }

18. style 字符串 格式化为对象

style 字符串 转换成对象 比如'width:100px;height:200px;' 转化成 {width:100px,height:200px}

    var parseStyleText = cached(function (cssText) {
        var res = {};
        //匹配字符串中的 ;符号。但是不属于 (;)的 符号 如果是括号中的;不能匹配出来
        var listDelimiter = /;(?![^(]*\))/g; 
        var propertyDelimiter = /:(.+)/;  //:+任何字符串
        cssText.split(listDelimiter).forEach(function (item) {
            if (item) {
                var tmp = item.split(propertyDelimiter);
                tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim());
            }
        });
        return res
    });

19. 设置属性和样式

    var cssVarRE = /^--/; //开始以 --开始
    var importantRE = /\s*!important$/; //以!important 结束

    var setProp = function (el, name, val) {
        //object.setProperty(propertyname, value, priority)
        // propertyname	必需。一个字符串,表示创建或修改的属性。
        // value	可选,新的属性值。
        // priority	可选。字符串,规定是否需要设置属性的优先级 important。
        // 可以是下面三个值:"important",undefined,""
        /* istanbul ignore if */
        if (cssVarRE.test(name)) { //开始以 --开始
            el.style.setProperty(name, val); //设置真实dom样式
        } else if (importantRE.test(val)) { //以!important 结束
            el.style.setProperty(
                name,
                val.replace(importantRE, ''),
                'important'
            );
        } else {
            //给css加前缀
            var normalizedName = normalize(name);
            if (Array.isArray(val)) {
                // Support values array created by autoprefixer, e.g.
                // {display: ["-webkit-box", "-ms-flexbox", "flex"]}
                // Set them one by one, and the browser will only set those it can recognize
                //支持自动修复程序创建的值数组。
                //{显示:[“-webkit-box”、“-ms-flexbox”,“柔化”)}
                //一个一个设置,浏览器只会设置它能识别的
                for (var i = 0, len = val.length; i < len; i++) {
                    el.style[normalizedName] = val[i]; //循环一个个设置样式
                }
            } else {
                el.style[normalizedName] = val;
            }
        }
    };

    var vendorNames = ['Webkit', 'Moz', 'ms'];
    var emptyStyle;
    //给css加前缀。解决浏览器兼用性问题,加前缀
    var normalize = cached(function (prop) {
        emptyStyle = emptyStyle || document.createElement('div').style; //获取浏览器中的style样式
        prop = camelize(prop);
        if (prop !== 'filter' && (prop in emptyStyle)) { //如果该属性已经在样式中
            return prop
        }
        var capName = prop.charAt(0).toUpperCase() + prop.slice(1); //首字母变成大写
        for (var i = 0; i < vendorNames.length; i++) {
            var name = vendorNames[i] + capName; //加前缀
            if (name in emptyStyle) {
                return name
            }
        }
    });

19. 更新样式

    var style = {
        create: updateStyle,
        update: updateStyle
    }

19-1. updateStyle,将vonde虚拟dom的css 转义成并且渲染到真实dom的css

    function updateStyle(oldVnode, vnode) {
        var data = vnode.data; //获取新虚拟dom的标签属性
        var oldData = oldVnode.data; //获取旧虚拟dom的标签属性

        if (isUndef(data.staticStyle) && isUndef(data.style) &&
            isUndef(oldData.staticStyle) && isUndef(oldData.style)
        ) {
            return
        }

        var cur, name;
        var el = vnode.elm; //获取真实的dom
        var oldStaticStyle = oldData.staticStyle; //获取旧的静态 staticStyle
        var oldStyleBinding = oldData.normalizedStyle || oldData.style || {}; //获取旧的动态style

        // if static style exists, stylebinding already merged into it when doing normalizeStyleData
        //  如果存在静态样式,则在执行normalizeStyleData时,stylebinding已经合并到其中
        var oldStyle = oldStaticStyle || oldStyleBinding; //旧的style样式


        //将可能的数组/字符串值规范化为对象 //把style 字符串 转换成对象 比如'width:100px;height:200px;' 转化成 {width:100px,height:200px}
        var style = normalizeStyleBinding(vnode.data.style) || {};

        // store normalized style under a different key for next diff
        // make sure to clone it if it's reactive, since the user likely wants
        // to mutate it.
        //为下一个diff在不同的键下存储规范化样式
        //如果它是反应性的,请确保克隆它,因为用户可能希望这样做
        //使之变异
        vnode.data.normalizedStyle = isDef(style.__ob__) ? //如果style 加入了观察者之后
            extend({}, style) :  //重新克隆,可以修改
            style; //直接赋值
        //getStyle循环子组件和组件的样式,把它全部合并到一个样式对象中返回 样式对象 如{width:100px,height:200px} 返回该字符串。
        var newStyle = getStyle(
            vnode,
            true
        );

        for (name in oldStyle) { //获取旧虚拟dom的样式
            if (isUndef(newStyle[name])) { // 如果新的虚拟dom vonde没有了
                setProp(el, name, ''); //则设置样式为空
            }
        }
        for (name in newStyle) { //循环新的虚拟dom vonde 样式
            cur = newStyle[name];
            if (cur !== oldStyle[name]) { //如果旧的和新的不同了 就设置新的样式
                // ie9 setting to null has no effect, must use empty string
                setProp(el, name, cur == null ? '' : cur);
            }
        }
    }

updateStyle=》normalizeStyleBinding,将可能的数组/字符串值规范化为对象

    function normalizeStyleBinding(bindingStyle) {
        if (Array.isArray(bindingStyle)) {
            return toObject(bindingStyle)
        }
        if (typeof bindingStyle === 'string') {
            // 把style 字符串 转换成对象 比如'width:100px;height:200px;' 
            // 转化成 {width:100px,height:200px}
            // 详细查看章节:18. style 字符串 格式化为对象
            return parseStyleText(bindingStyle)
        }
        return bindingStyle
    }

updateStyle=》getStyle, 父组件样式应该在子组件样式之后,这样父组件的样式就可以覆盖它,循环子组件和组件的样式,把它全部合并到一个样式对象中,返回 样式对象 如{width:100px,height:200px} 返回该字符串。

    function getStyle(
        vnode, //虚拟dom
        checkChild //标志点 布尔值
    ) {
        var res = {};
        var styleData; //style data
        if (checkChild) { // 标志点 布尔值
            var childNode = vnode; //获取子节点
            while (childNode.componentInstance) { //已经实例化过的 就是子节点有vonde
                childNode = childNode.componentInstance._vnode;
                if (
                    childNode &&
                    childNode.data &&
                    (styleData = normalizeStyleData(childNode.data))
                ) {
                    extend(res, styleData);
                }
            }
        }

        if ((styleData = normalizeStyleData(vnode.data))) {
            extend(res, styleData);
        }

        var parentNode = vnode;
        while ((parentNode = parentNode.parent)) {
            if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) {
                extend(res, styleData);
            }
        }
        return res
    }

updateStyle=》getStyle=> normalizeStyleData,在同一个vnode上合并静态和动态样式数据

function normalizeStyleData(data) {
        // //将可能的数组/字符串值规范化为对象  把style 字符串 转换成对象 比如'width:100px;height:200px;' 转化成 {width:100px,height:200px} 返回该字符串。
        var style = normalizeStyleBinding(data.style); //获取到vonde中的style属性值
        // static style is pre-processed into an object during compilation
        // and is always a fresh object, so it's safe to merge into it
        //静态样式在编译期间被预处理为对象
        //始终是一个新鲜的对象,所以可以安全地融入其中
        return data.staticStyle ?
            extend(data.staticStyle, style) : //合并静态
            style
    }

updateStyle分析完成。

20. 封装工具模板

    var platformModules = [
        // attrs包含两个方法create和update都是更新设置真实dom属性值 
        // {create: updateAttrs, /*创建属性*/ update: updateAttrs  /*更新属性 */}
        attrs,  
        // klass包含类包含两个方法create和update都是更新calss。
        // 其实就是updateClass方法。 设置真实dom的class
        klass, 
        events, //更新真实dom的事件
        domProps, //更新真实dom的props 属性值
        // 更新真实dom的style属性。有两个方法create 和update 不过函数都是updateStyle更新真实dom的style属性值.
        // 将vonde虚拟dom的css 转义成并且渲染到真实dom的css中
        style, 
        transition // 过度动画
    ]
    var modules = platformModules.concat(baseModules);
    //path 把vonde 渲染成真实的dom
    var patch = createPatchFunction(
        {
            nodeOps: nodeOps,
            modules: modules
        }
    );

21 .定义插入更新指令函数

    var directive = {
        inserted: function inserted(el, binding, vnode, oldVnode) {

            if (vnode.tag === 'select') {
                // #6903
                if (oldVnode.elm && !oldVnode.elm._vOptions) {
                    mergeVNodeHook(vnode, 'postpatch', function () {
                        directive.componentUpdated(el, binding, vnode);
                    });
                } else {
                    setSelected(el, binding, vnode.context);
                }
                el._vOptions = [].map.call(el.options, getValue);
            } else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
                el._vModifiers = binding.modifiers;
                if (!binding.modifiers.lazy) {
                    el.addEventListener('compositionstart', onCompositionStart);
                    el.addEventListener('compositionend', onCompositionEnd);
                    // Safari < 10.2 & UIWebView doesn't fire compositionend when
                    // switching focus before confirming composition choice
                    // this also fixes the issue where some browsers e.g. iOS Chrome
                    // fires "change" instead of "input" on autocomplete.
                    el.addEventListener('change', onCompositionEnd);
                    /* istanbul ignore if */
                    if (isIE9) {
                        el.vmodel = true;
                    }
                }
            }
        },

        componentUpdated: function componentUpdated(el, binding, vnode) {
            if (vnode.tag === 'select') {
                setSelected(el, binding, vnode.context);
                // in case the options rendered by v-for have changed,
                // it's possible that the value is out-of-sync with the rendered options.
                // detect such cases and filter out values that no longer has a matching
                // option in the DOM.
                var prevOptions = el._vOptions;
                var curOptions = el._vOptions = [].map.call(el.options, getValue);
                if (curOptions.some(function (o, i) {
                    return !looseEqual(o, prevOptions[i]);
                })) {
                    // trigger change event if
                    // no matching option found for at least one value
                    var needReset = el.multiple
                        ? binding.value.some(function (v) {
                            return hasNoMatchingOption(v, curOptions);
                        })
                        : binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions);
                    if (needReset) {
                        trigger(el, 'change');
                    }
                }
            }
        }
    };

22. 定义更新绑定指令函数

   var show = {
        bind: function bind(el, ref, vnode) {
            var value = ref.value;

            vnode = locateNode(vnode);
            var transition$$1 = vnode.data && vnode.data.transition;
            var originalDisplay = el.__vOriginalDisplay =
                el.style.display === 'none' ? '' : el.style.display;
            if (value && transition$$1) {
                vnode.data.show = true;
                enter(vnode, function () {
                    el.style.display = originalDisplay;
                });
            } else {
                el.style.display = value ? originalDisplay : 'none';
            }
        },

        update: function update(el, ref, vnode) {
            var value = ref.value;
            var oldValue = ref.oldValue;

            /* istanbul ignore if */
            if (!value === !oldValue) {
                return
            }
            vnode = locateNode(vnode);
            var transition$$1 = vnode.data && vnode.data.transition;
            if (transition$$1) {
                vnode.data.show = true;
                if (value) {
                    enter(vnode, function () {
                        el.style.display = el.__vOriginalDisplay;
                    });
                } else {
                    leave(vnode, function () {
                        el.style.display = 'none';
                    });
                }
            } else {
                el.style.display = value ? el.__vOriginalDisplay : 'none';
            }
        },

        unbind: function unbind(el,
            binding,
            vnode,
            oldVnode,
            isDestroy) {
            if (!isDestroy) {
                el.style.display = el.__vOriginalDisplay;
            }
        }
    }

23 . 封装指令

    var platformDirectives = {
        model: directive,
        show: show
    }

24. 检验属性

    Vue.config.mustUseProp = mustUseProp;    //校验属性


    Vue.config.isReservedTag = isReservedTag;
    Vue.config.isReservedAttr = isReservedAttr;
    Vue.config.getTagNamespace = getTagNamespace;
    Vue.config.isUnknownElement = isUnknownElement;

    // install platform runtime directives & components
    extend(Vue.options.directives, platformDirectives);
    extend(Vue.options.components, platformComponents);

    // install platform patch function 安装平台补丁功能
    Vue.prototype.__patch__ = inBrowser ? patch : noop;

25. 挂载$mount

    Vue.prototype.$mount = function (el, hydrating) {
        debugger
        // query(el) 获取dom,已经是dom就返回,不是dom并且获取不到,警告提示,创建一个新的dev
        el = el && inBrowser ? query(el) : undefined;
        // 安装组件
        return mountComponent(
            this, // Vue实例
            el,  // 真实dom
            hydrating
        )
    };

26 .vue 开发工具配置

    if (inBrowser) {
        setTimeout(function () {
            if (config.devtools) {
                if (devtools) {
                    devtools.emit('init', Vue);
                } else if (
                    "development" !== 'production' &&
                    "development" !== 'test' &&
                    isChrome
                ) {
                    console[console.info ? 'info' : 'log'](
                        'Download the Vue Devtools extension for a better development experience:\n' +
                        'https://github.com/vuejs/vue-devtools'
                    );
                }
            }
            //如果不是生产环境
            if ("development" !== 'production' &&
                "development" !== 'test' &&
                config.productionTip !== false &&
                typeof console !== 'undefined'
            ) {
                console[console.info ? 'info' : 'log'](
                    "You are running Vue in development mode.\n" +
                    "Make sure to turn on production mode when deploying for production.\n" +
                    "See more tips at https://vuejs.org/guide/deployment.html"
                );
            }
        }, 0);
    }

标签:function,初始化,vue,el,vnode,源码,var,data,属性
来源: https://blog.csdn.net/weixin_39818813/article/details/118494537

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有