web前端

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

2020-09-03  本文已影响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
    }
}

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

上一篇 下一篇

猜你喜欢

热点阅读