美文网首页web前端
vue源码分析(五):一般指令

vue源码分析(五):一般指令

作者: 姜治宇 | 来源:发表于2020-09-03 17:12 被阅读0次

    vue中一般性的指令比较多,我们就举个比较典型的来说一下,其他的思路都一样的。
    比如动态class指令:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .bg{
                background: #ccc;
                color:#000;
            }
            .active{
                background: cornflowerblue;
                color:#fff;
            }
            .old {
                border:0;
                width:200px;
                height:50px;
                line-height:50px;
                text-align: center;
                font-size:20px;
    
            }
            .old2 {
                border-radius: 20px;
            }
    
        </style>
    </head>
    <body>
        <div id="app">
            <button class="old old2" v-bind:class="{'bg':false,'active':true}">点击一下</button>
        </div>
    </body>
    </html>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.min.js"></script>
    
    <script>
        const vm = new Vue({
            el:'#app'
        })
    </script>
    

    动态class是我们常用的指令,这个是如何实现的呢?
    其实很简单,直接上代码:

    function Vue(options){
        this.$options = options
        this._data = this.$options.data
        this.$compile = new Compile(options.el,this) //模板解析
    }
    
    function Compile(el,vm){
        this.$vm = vm
        this.$el = document.querySelector(el)
        console.log(this.$el)
        if(this.$el){
            this.$fragment = this.node2Fragment(this.$el) //将节点转到fragment处理
    
            this.init() //开始处理
    
            this.$el.appendChild(this.$fragment) //塞回原位
        }
    }
    Compile.prototype = {
        //将#app里面的节点都转到文档碎片中
        node2Fragment(el){
            var fragment = document.createDocumentFragment()
            var child = null
            while(child = el.firstChild) {
                fragment.appendChild(child)
            }
            return fragment
        },
        //处理碎片中的信息
        init(){
            this.compileElement(this.$fragment)
        },
        //正则匹配
        compileElement(el){
            var childNodes = el.childNodes;
            [].slice.call(childNodes).forEach(node=>{
                var text = node.textContent // 获取文本信息
                var reg = /\{\{(.*)\}\}/
                //这里增加指令的处理
                if(node.nodeType === 1){
                    //如果是元素节点
                    this.compile(node)
                } else if(node.nodeType === 3 && reg.test(text)) {
    
                    this.compileText(node,RegExp.$1)
                } else if(node.childNodes && node.childNodes.length) {
                    //递归
                    this.compileElement(node)
                }
            })
        },
        //处理元素节点
        compile(node){
            var nodeAttrs = node.attributes; // 获取元素节点的所有属性,伪数组
            [].slice.call(nodeAttrs).forEach(attr=>{
                var attrName = attr.name;//获取属性名
                if(attrName.indexOf('v-') === 0){//判断是否是指令
                    var exp = attr.value; //获取属性值,也就是触发的方法名
                    var dir = attrName.substring(2) // 截取字符串,得到on:click
                    if(dir.indexOf('on') === 0){ //判断事件指令
                        var eventType = dir.split(':')[1]; //获取事件类型
                        var fn = this.$vm.$options.methods && this.$vm.$options.methods[exp];// 获取函数
                        if(eventType && fn) {
                            node.addEventListener(eventType,fn.bind(this.$vm),false) // 注意fn里面的this指向
                        }
    
    
    
                    } else if(dir.indexOf('bind') === 0) { // 一般指令
                        var dirType = dir.split(':')[1] // 获取指令类型class
                        if(dirType === 'class') {
                            var oldClassName = node.className; //原来的class类
                            var newClassName = '' //动态class类
                            var classObj = eval('(' + exp + ')'); //解析为object对象
    
                            for(var key in classObj) { //遍历对象,如果value为true追加class名,false不追加
                                if(classObj[key]) {
                                    newClassName += ' ' + key
                                }
                            }
                            node.className = oldClassName + newClassName // 设置className
                        }
    
                    }
                    node.removeAttribute(attrName) // 从chrome控制台的Elements看文档结构,发现没有v-这样的属性(指令),所以需要处理完去掉
                }
            })
    
        },
    //用vm.data信息,替换大括号的name
        compileText(node,exp){
    
            node.textContent = this.getVMVal(exp)
        },
    //处理层级问题
        getVMVal(exp){ // a.b.c
            var val = this.$vm._data
            var arr = exp.split('.') //["a", "b", "c"]
            console.log(exp)
            arr.forEach(k=>{
                //debugger
                val = val[k] // 层级递进
            })
            return val
        }
    }
    

    当然这只是一个很粗略的版本,把原理搞明白即可。

    相关文章

      网友评论

        本文标题:vue源码分析(五):一般指令

        本文链接:https://www.haomeiwen.com/subject/tjkosktx.html