Vue源码解析--模板解析(表达式文本节点)

一、模板解析的基本流程:

(1)将el的所有子节点取出,添加到一个新建的文档fragment对象中。

(2)对fragment中所有层次子节点递归进行编译解析处理。

  • 对大括号表达式文本节点进行解析。

  • 对元素节点的指令属性进行解析。

    事件指令解析。
    一般指令解析。

二、大括号表达式文本节点解析流程:

1.HTML中的代码:

<div id="test">
    <p>{
    
    {
    
    name}}</p>
</div>

2.引入的JavaScript文件:

<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="js/compile.js"></script>
<script src="js/observer.js"></script>
<script src="js/mvvm.js"></script>
<script src="js/watcher.js"></script>

3.JavaScript中的代码:

    new Vue({
    
    
        el:"#test",
        data:{
    
    
            name:'小花'
        }
    });

4.编译:
在这里插入图片描述
这里new了一个Compile实例传入的是options.el || document.body,this 这里this指的是vm,其实就是我们引入的compile.js向外暴露了一个Compile函数,此时我们进入compile.js文件里面。

compile.js文件代码分步解析:

function Compile(el, vm) {
    
    
    //保存vm到Compile对象
    this.$vm = vm;
    // console.log(this);
    //判断el是不是元素节点(看nodeType是不是1),将el对应的元素对象保存到Compile对象
    this.$el = this.isElementNode(el) ? el : document.querySelector(el);
    //判断是否存在 如果有el元素则...
    if (this.$el) {
    
    
        //模板解析的三条语句,取出el元素中所有的子节点,保存到$fragment对象中
        this.$fragment = this.node2Fragment(this.$el);
        //用来实现初始化显示(重点语句),编译fragment中所有层次的子节点
        this.init();
        //将编译好的fragment添加到页面el元素中
        this.$el.appendChild(this.$fragment);
    }
}

点来node2Fragment函数,这个函数是写在Compile的原型里面。(这样添加可以优化DOM操作)

    node2Fragment: function(el) {
    
    
        //创建空的fragment对象
        var fragment = document.createDocumentFragment(),
            child;
        //将el中所有子节点转移到fragment中
        while (child = el.firstChild) {
    
    
            fragment.appendChild(child);
        }
        //返回fragment
        return fragment;
    },

点开init函数,此函数也是存在于Compile的原型里面,init函数里面很简短。

    init: function() {
    
    
        //编译fragment里面所有的子节点
        this.compileElement(this.$fragment);
    },

接着点开compileElement函数(显示的是部分代码)。

compileElement: function(el) {
    
    
        var childNodes = el.childNodes,//获得所有的子节点
            me = this;//this指compile的实例
        //遍历每一个子节点(text/element)
        [].slice.call(childNodes).forEach(function(node) {
    
    
            var text = node.textContent;//返回文本内容
            var reg = /\{\{(.*)\}\}/;//用来匹配{
    
    {name}}表达式,多了一个(),用来子匹配

            if (me.isElementNode(node)) {
    
    //是否是元素节点
                //编译它(解析指令)
                me.compile(node);

            } else if (me.isTextNode(node) && reg.test(text)) {
    
    //是文本节点并且文本和正则是否匹配
                //编译大括号表达式文本节点
                me.compileText(node, RegExp.$1.trim());
            }
            //如果当前节点还有子节点,通过递归调用实现所有层次节点的编译。递归是把所有层次都递归调用的最好方法
            if (node.childNodes && node.childNodes.length) {
    
    //问子节点是否有子子节点?并且子子节点长度是否大于0
                me.compileElement(node);
            }
        });
    }

打开isElementNode,isTextNode,compileText函数:

    compileText: function(node, exp) {
    
    
        compileUtil.text(node, this.$vm, exp);
    },

    isDirective: function(attr) {
    
    
        return attr.indexOf('v-') == 0;
    },

    isEventDirective: function(dir) {
    
    
        return dir.indexOf('on') === 0;
    },
    //判断是否是元素节点
    isElementNode: function(node) {
    
    
        return node.nodeType == 1;
    },
    //判断是否是文本节点
    isTextNode: function(node) {
    
    
        return node.nodeType == 3;
    }

打开compile函数,此函数位于compileElement函数中:

    //用来编译元素节点里面所有的指令(指令以标签形式写上去的)
    compile: function(node) {
    
    
        var nodeAttrs = node.attributes,
            me = this;

        [].slice.call(nodeAttrs).forEach(function(attr) {
    
    
            var attrName = attr.name;
            if (me.isDirective(attrName)) {
    
    
                var exp = attr.value;
                var dir = attrName.substring(2);
                // 事件指令
                if (me.isEventDirective(dir)) {
    
    
                    compileUtil.eventHandler(node, me.$vm, exp, dir);
                    // 普通指令
                } else {
    
    
                    compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
                }

                node.removeAttribute(attrName);
            }
        });
    },

compile.js文件夹里面有包含解析指令方法的工具:

var compileUtil = {
    
    
    //用来编译v-text,v-text和编译{}用的同一个函数
    text: function(node, vm, exp) {
    
    
        this.bind(node, vm, exp, 'text');
    },
    //解析v-html
    html: function(node, vm, exp) {
    
    
        this.bind(node, vm, exp, 'html');
    },
    //解析v-model
    model: function(node, vm, exp) {
    
    
        this.bind(node, vm, exp, 'model');

        var me = this,
            val = this._getVMVal(vm, exp);
        node.addEventListener('input', function(e) {
    
    
            var newValue = e.target.value;
            if (val === newValue) {
    
    
                return;
            }

            me._setVMVal(vm, exp, newValue);
            val = newValue;
        });
    },
    //解析v-class
    class: function(node, vm, exp) {
    
    
        this.bind(node, vm, exp, 'class');
    },

    bind: function(node, vm, exp, dir) {
    
    
        //得到更新节点的函数
        var updaterFn = updater[dir + 'Updater'];//函数
        //调用函数更新节点
        updaterFn && updaterFn(node, this._getVMVal(vm, exp));

        new Watcher(vm, exp, function(value, oldValue) {
    
    
            updaterFn && updaterFn(node, value, oldValue);
        });
    },

    // 事件处理
    eventHandler: function(node, vm, exp, dir) {
    
    
        var eventType = dir.split(':')[1],
            fn = vm.$options.methods && vm.$options.methods[exp];

        if (eventType && fn) {
    
    
            node.addEventListener(eventType, fn.bind(vm), false);
        }
    },
    //从vm得到表达式所对应的值(表达式可能是多层)
    _getVMVal: function(vm, exp) {
    
    
        var val = vm;
        exp = exp.split('.');
        exp.forEach(function(k) {
    
    
            val = val[k];
        });
        return val;
    },

    _setVMVal: function(vm, exp, value) {
    
    
        var val = vm;
        exp = exp.split('.');
        exp.forEach(function(k, i) {
    
    
            // 非最后一个key,更新val的值
            if (i < exp.length - 1) {
    
    
                val = val[k];
            } else {
    
    
                val[k] = value;
            }
        });
    }
};

compile.js文件夹里面有包含更新节点方法的工具:

//包含多个更新节点的方法的工具对象
var updater = {
    
    
    //更新节点的textContent属性值
    textUpdater: function(node, value) {
    
    
        node.textContent = typeof value == 'undefined' ? '' : value;
    },
    //更新节点的innerHTML属性值
    htmlUpdater: function(node, value) {
    
    
        node.innerHTML = typeof value == 'undefined' ? '' : value;
    },
    //更新节点的className属性值
    classUpdater: function(node, value, oldValue) {
    
    
        var className = node.className;
        className = className.replace(oldValue, '').replace(/\s$/, '');

        var space = className && String(value) ? ' ' : '';

        node.className = className + space + value;
    },
    //更新节点的value属性值
    modelUpdater: function(node, value, oldValue) {
    
    
        node.value = typeof value == 'undefined' ? '' : value;
    }
};

三、大括号表达式文本节点解析核心:

  1. 第一步:取出name。
  2. 第二步:根据name得到value。
  3. 第三步:把得到的值放到节点的textContent属性里面。

取出name的代码:

//模板解析的三条语句,取出el元素中所有的子节点,保存到$fragment对象中
this.$fragment = this.node2Fragment(this.$el);
 //用来实现初始化显示(重点语句),编译fragment中所有层次的子节点
this.init();
    //创建DocumentFragment减少DOM的回流
    node2Fragment: function(el) {
    
    
        //创建空的fragment对象
        var fragment = document.createDocumentFragment(),
            child;
        //将el中所有子节点转移到fragment中
        while (child = el.firstChild) {
    
    
            fragment.appendChild(child);
        }
        //返回fragment
        return fragment;
    },
init: function() {
    
    
        //编译fragment里面所有的子节点
        this.compileElement(this.$fragment);
    },

    compileElement: function(el) {
    
    
        var childNodes = el.childNodes,//获得所有的子节点

            me = this;//this指compile的实例
        //遍历每一个子节点(text/element)
        [].slice.call(childNodes).forEach(function(node) {
    
    
            var text = node.textContent;//返回文本内容
            var reg = /\{\{(.*)\}\}/;//用来匹配{
    
    {name}}表达式,多了一个(),用来子匹配

            if (me.isElementNode(node)) {
    
    //是否是元素节点
                //编译它(解析指令)
                me.compile(node);

            } else if (me.isTextNode(node) && reg.test(text)) {
    
    //是文本节点并且文本和正则是否匹配
                //编译大括号表达式文本节点
                me.compileText(node, RegExp.$1.trim());
            }
            //如果当前节点还有子节点,通过递归调用实现所有层次节点的编译。递归是把所有层次都递归调用的最好方法
            if (node.childNodes && node.childNodes.length) {
    
    //问子节点是否有子子节点?并且子子节点长度是否大于0
                me.compileElement(node);
            }
        });
    },

根据name得到value。

else if (me.isTextNode(node) && reg.test(text)) {
    
    //是文本节点并且文本和正则是否匹配
    //编译大括号表达式文本节点
       me.compileText(node, RegExp.$1.trim());
}
compileText: function(node, exp) {
    
    
        compileUtil.text(node, this.$vm, exp);
    },
	text: function(node, vm, exp) {
    
    
        this.bind(node, vm, exp, 'text');
    },
	bind: function(node, vm, exp, dir) {
    
    
        //得到更新节点的函数
        var updaterFn = updater[dir + 'Updater'];//函数
        //调用函数更新节点
        updaterFn && updaterFn(node, this._getVMVal(vm, exp));

        new Watcher(vm, exp, function(value, oldValue) {
    
    
            updaterFn && updaterFn(node, value, oldValue);
        });
    },
//从vm得到表达式所对应的值(表达式可能是多层)
    _getVMVal: function(vm, exp) {
    
    
        var val = vm;
        exp = exp.split('.');
        exp.forEach(function(k) {
    
    
            val = val[k];
        });
        return val;
    },

把得到的值放到节点的textContent属性里面:

//将编译好的fragment添加到页面el元素中
   this.$el.appendChild(this.$fragment);

四:compile.js文件全部代码:

function Compile(el, vm) {
    
    
    //保存vm到Compile对象
    this.$vm = vm;
    // console.log(this);
    //判断el是不是元素节点(看nodeType是不是1),将el对应的元素对象保存到Compile对象
    this.$el = this.isElementNode(el) ? el : document.querySelector(el);
    //判断是否存在 如果有el元素则...
    if (this.$el) {
    
    
        //模板解析的三条语句,取出el元素中所有的子节点,保存到$fragment对象中
        this.$fragment = this.node2Fragment(this.$el);
        //用来实现初始化显示(重点语句),编译fragment中所有层次的子节点
        this.init();
        //将编译好的fragment添加到页面el元素中
        this.$el.appendChild(this.$fragment);
    }
}

Compile.prototype = {
    
    
    constructor: Compile,
    //创建DocumentFragment减少DOM的回流
    node2Fragment: function(el) {
    
    
        //创建空的fragment对象
        var fragment = document.createDocumentFragment(),
            child;
        //将el中所有子节点转移到fragment中
        while (child = el.firstChild) {
    
    
            fragment.appendChild(child);
        }
        //返回fragment
        return fragment;
    },

    init: function() {
    
    
        //编译fragment里面所有的子节点
        this.compileElement(this.$fragment);
    },

    compileElement: function(el) {
    
    
        var childNodes = el.childNodes,//获得所有的子节点

            me = this;//this指compile的实例
        //遍历每一个子节点(text/element)
        [].slice.call(childNodes).forEach(function(node) {
    
    
            var text = node.textContent;//返回文本内容
            var reg = /\{\{(.*)\}\}/;//用来匹配{
    
    {name}}表达式,多了一个(),用来子匹配

            if (me.isElementNode(node)) {
    
    //是否是元素节点
                //编译它(解析指令)
                me.compile(node);

            } else if (me.isTextNode(node) && reg.test(text)) {
    
    //是文本节点并且文本和正则是否匹配
                //编译大括号表达式文本节点
                me.compileText(node, RegExp.$1.trim());
            }
            //如果当前节点还有子节点,通过递归调用实现所有层次节点的编译。递归是把所有层次都递归调用的最好方法
            if (node.childNodes && node.childNodes.length) {
    
    //问子节点是否有子子节点?并且子子节点长度是否大于0
                me.compileElement(node);
            }
        });
    },
    //用来编译元素节点里面所有的指令(指令以标签形式写上去的)
    compile: function(node) {
    
    
        var nodeAttrs = node.attributes,
            me = this;

        [].slice.call(nodeAttrs).forEach(function(attr) {
    
    
            var attrName = attr.name;
            if (me.isDirective(attrName)) {
    
    
                var exp = attr.value;
                var dir = attrName.substring(2);
                // 事件指令
                if (me.isEventDirective(dir)) {
    
    
                    compileUtil.eventHandler(node, me.$vm, exp, dir);
                    // 普通指令
                } else {
    
    
                    compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
                }

                node.removeAttribute(attrName);
            }
        });
    },
//递归是一种特别的嵌套调用
    compileText: function(node, exp) {
    
    
        compileUtil.text(node, this.$vm, exp);
    },

    isDirective: function(attr) {
    
    
        return attr.indexOf('v-') == 0;
    },

    isEventDirective: function(dir) {
    
    
        return dir.indexOf('on') === 0;
    },
    //判断是否是元素节点
    isElementNode: function(node) {
    
    
        return node.nodeType == 1;
    },
    //判断是否是文本节点
    isTextNode: function(node) {
    
    
        return node.nodeType == 3;
    }
};

// 包含多个解析指令方法的工具对象
var compileUtil = {
    
    
    //用来编译v-text,v-text和编译{}用的同一个函数
    text: function(node, vm, exp) {
    
    
        this.bind(node, vm, exp, 'text');
    },
    //解析v-html
    html: function(node, vm, exp) {
    
    
        this.bind(node, vm, exp, 'html');
    },
    //解析v-model
    model: function(node, vm, exp) {
    
    
        this.bind(node, vm, exp, 'model');

        var me = this, val = this._getVMVal(vm, exp);
        node.addEventListener('input', function(e) {
    
    
            var newValue = e.target.value;
            if (val === newValue) {
    
    
                return;
            }

            me._setVMVal(vm, exp, newValue);
            val = newValue;
        });
    },
    //解析v-class
    class: function(node, vm, exp) {
    
    
        this.bind(node, vm, exp, 'class');
    },

    bind: function(node, vm, exp, dir) {
    
    
        //得到更新节点的函数
        var updaterFn = updater[dir + 'Updater'];//函数
        //调用函数更新节点
        updaterFn && updaterFn(node, this._getVMVal(vm, exp));

        new Watcher(vm, exp, function(value, oldValue) {
    
    
            updaterFn && updaterFn(node, value, oldValue);
        });
    },

    // 事件处理
    eventHandler: function(node, vm, exp, dir) {
    
    
        var eventType = dir.split(':')[1],
            fn = vm.$options.methods && vm.$options.methods[exp];

        if (eventType && fn) {
    
    
            node.addEventListener(eventType, fn.bind(vm), false);
        }
    },
    //从vm得到表达式所对应的值(表达式可能是多层)
    _getVMVal: function(vm, exp) {
    
    
        var val = vm;
        exp = exp.split('.');
        exp.forEach(function(k) {
    
    
            val = val[k];
        });
        return val;
    },

    _setVMVal: function(vm, exp, value) {
    
    
        var val = vm;
        exp = exp.split('.');
        exp.forEach(function(k, i) {
    
    
            // 非最后一个key,更新val的值
            if (i < exp.length - 1) {
    
    
                val = val[k];
            } else {
    
    
                val[k] = value;
            }
        });
    }
};

//包含多个更新节点的方法的工具对象
var updater = {
    
    
    //更新节点的textContent属性值
    textUpdater: function(node, value) {
    
    
        node.textContent = typeof value == 'undefined' ? '' : value;
    },
    //更新节点的innerHTML属性值
    htmlUpdater: function(node, value) {
    
    
        node.innerHTML = typeof value == 'undefined' ? '' : value;
    },
    //更新节点的className属性值
    classUpdater: function(node, value, oldValue) {
    
    
        var className = node.className;
        className = className.replace(oldValue, '').replace(/\s$/, '');

        var space = className && String(value) ? ' ' : '';

        node.className = className + space + value;
    },
    //更新节点的value属性值
    modelUpdater: function(node, value, oldValue) {
    
    
        node.value = typeof value == 'undefined' ? '' : value;
    }
};

猜你喜欢

转载自blog.csdn.net/weixin_43690348/article/details/109700639
今日推荐