深入浅出MV*框架源码(五):Moon中的指令

前言

Moon目前实现的内置指令有八个:if/show/for/on/model/html/literal/mask,也可以让用户自定义指令。

beforeGenerate ->duringPropGenerate->afterGenerate

所有指令都是在generateProps或generateNode里调用,分别按beforeGenerate ->duringPropGenerate->afterGenerate 的状态执行指令逻辑,我们节选相关的代码段:

let beforeGenerate = null;
for(propKey in props) {
    const prop = props[propKey];
    const name = prop.name;
    if((specialDirective = specialDirectives[name]) !== undefined 
        && (beforeGenerate = specialDirective.beforeGenerate) !== undefined) {
      beforeGenerate(prop, node, parent, state);
    }
}

顾名思义,这个beforeGenerate 值得就是compile过程中generate执行之前,这时ast还没有转换成code。我们可以看到,传入了四个参数:prop, node, parent, state,从state参数我们可以联想到ast的state变量,所以更加确认这是ast层面的操作无疑了。

let afterGenerate = null;
let duringPropGenerate = null;
for(propKey in props) {
  const prop = props[propKey];
  const name = prop.name;

  if((specialDirective = specialDirectives[name]) !== undefined) {
    if((afterGenerate = specialDirective.afterGenerate) !== undefined) {
      specialDirectivesAfter[name] = {
        prop: prop,
        afterGenerate: afterGenerate
      };

      hasSpecialDirectivesAfter = true;
    }

    if((duringPropGenerate = specialDirective.duringPropGenerate) !== undefined) {
      propsCode += duringPropGenerate(prop, node, state);
    }

    node.meta.shouldRender = true;
  } 
  ......

    if (specialDirectivesAfter !== null) {
        var specialDirectiveAfter;
        for (var specialDirectiveKey in specialDirectivesAfter) {
            specialDirectiveAfter = specialDirectivesAfter[specialDirectiveKey];
            call = specialDirectiveAfter.afterGenerate(specialDirectiveAfter.prop, call, node, state);
        }
    }
}

而在afterGenerate和 duringPropGenerate这两个时期,code已经生成了或者正在生成中,操作的就是code了。这时我们发现duringPropGenerate比beforeGenerate少传了个parent参数,因为树结构中才有父元素嘛~
而afterGenerate又比duringPropGenerate多传了个call参数(实际上它是一段code),这更加说明了afterGenerate是在generate完code后再往上面加code的过程。

内置指令

m-if

m-if在afterGenerate里生成一个三元表达式,若条件为真就生成vnode的code,否则是空的VNode:

specialDirectives["m-if"] = {
    afterGenerate: function(prop, code, vnode, state) {
        var value = prop.value;
        compileTemplateExpression(value, state.dependencies);
        return (value + " ? " + code + " : " + emptyVNode);
    }
}

m-show

m-show也是生成一个三元表达式,但它是直接修改el.style.display,和那三个过程无关:

directives["m-show"] = function(el, val, vnode) {
    el.style.display = (val ? '' : 'none');
}

m-for

m-for在beforeGenerate里设置flatten数组的标志位,在afterGenerate里迭代生成code:

specialDirectives["m-for"] = {
    beforeGenerate: function(prop, vnode, parentVNode, state) {
        // Setup Deep Flag to Flatten Array
        parentVNode.deep = true;
    },
    afterGenerate: function(prop, code, vnode, state) {

        // Get dependencies
        var dependencies = state.dependencies;

        // Get Parts
        var parts = prop.value.split(" in ");

        // Aliases
        var aliases = parts[0].split(",");

        // The Iteratable
        var iteratable = parts[1];
        compileTemplateExpression(iteratable, dependencies);

        // Get any parameters
        var params = aliases.join(",");

        // Add aliases to scope
        for (var i = 0; i < aliases.length; i++) {
            var aliasIndex = dependencies.indexOf(aliases[i]);
            if (aliasIndex !== -1) {
                dependencies.splice(aliasIndex, 1);
            }
        }

        // Use the renderLoop runtime helper
        // 等同于return `Moon.renderLoop(${iteratable}, function(${params}) {return${code};})`
        return ("Moon.renderLoop(" + iteratable + ", function(" + params + ") { return " + code + "; })");
    }
}

我们可以发现它会把形似"m-if=(item,key) in iteratable"中的item/key当做params解析出来,然后返回一份由renderLoop和code生成的新code。

renderLoop

这里用到了renderLoop来迭代生成code:

Moon.renderLoop = function(iteratable, item) {
    var items = null;

    if (Array.isArray(iteratable)) {
        items = new Array(iteratable.length);

        // Iterate through the array
        for (var i = 0; i < iteratable.length; i++) {
            items[i] = item(iteratable[i], i);
        }
    } else if (typeof iteratable === "object") {
        items = [];

        // Iterate through the object
        for (var key in iteratable) {
            items.push(item(iteratable[key], key));
        }
    } else if (typeof iteratable === "number") {
        items = new Array(iteratable);

        // Repeat a certain amount of times
        for (var i$1 = 0; i$1 < iteratable; i$1++) {
            items[i$1] = item(i$1 + 1, i$1);
        }
    }

    return items;
}

很明显,它对应着我们api中使用for指令时的三种可迭代情况:数组、对象、数字。就像lodash封装数组和对象为集合方法一样,针对数组和数字,就使用for循环迭代,针对对象就使用for...in迭代。
另外我们也明显地感受到了这位作者不是前端出身的,因为它担心i$1会覆盖i变量所以才这样命名的,实际上js的var可以重复声明的。

m-on

m-on在beforeGenerate里获取事件名和回调以及事件修饰符(如prevent),然后生成调用事件的代码并添加监听回调:

specialDirectives["m-on"] = {
    beforeGenerate: function(prop, vnode, parentVNode, state) {
        // Extract Event, Modifiers, and Parameters
        var value = prop.value;
        var meta = prop.meta;

        var methodToCall = value;

        var rawModifiers = meta.arg.split(".");
        var eventType = rawModifiers.shift();

        var params = "event";
        var rawParams = methodToCall.split("(");

        if (rawParams.length > 1) {
            // Custom parameters detected, update method to call, and generated parameter code
            methodToCall = rawParams.shift();
            params = rawParams.join("(").slice(0, -1);
            compileTemplateExpression(params, state.dependencies);
        }

        // Generate any modifiers
        var modifiers = "";
        for (var i = 0; i < rawModifiers.length; i++) {
            var eventModifierCode = eventModifiersCode[rawModifiers[i]];
            if (eventModifierCode === undefined) {
                modifiers += "if(Moon.renderEventModifier(event.keyCode, \"" + (rawModifiers[i]) + "\") === false) {return null;};"
            } else {
                modifiers += eventModifierCode;
            }
        }

        // Final event listener code
        var code = "function(event) {" + modifiers + "instance.callMethod(\"" + methodToCall + "\", [" + params + "])}";
        addEventListenerCodeToVNode(eventType, code, vnode);
    }
}

m-model

m-model在beforeGenerate里获取依赖、根据html类型绑定事件,然后动态检测依赖的getter,生成代码(靠set改变值),添加事件监听:

specialDirectives["m-model"] = {
    beforeGenerate: function(prop, vnode, parentVNode, state) {
    ......  
    }

因为实际上model指令是bind指令和on指令的结合语法糖,所以只能适用于特殊的表单元素:input、checkbox、radio等,而Moon并没有实现bind指令,所以在这里实现地颇为复杂,不过我们可以大致地知道bind value或者checked值它用getter和setter的方式实现了,on的部分监听change事件。

m-html

m-html对应v-html,它会在beforeGenerate里设置先通过compileTemplateExpression收集依赖,再设置innerHTML:

specialDirectives["m-html"] = {
    beforeGenerate: function(prop, vnode, parentVNode, state) {
        var value = prop.value;
        var dom = vnode.props.dom;
        if (dom === undefined) {
            vnode.props.dom = dom = {};
        }
        compileTemplateExpression(value, state.dependencies);
        dom.innerHTML = "(\"\" + " + value + ")";
    }
}

它没有返回值,而是直接操作的dom。

m-literal

m-literal对应v-text,在duringPropGenerate里先收集依赖,再通过renderClass生成带样式的文本:

specialDirectives["m-literal"] = {
    duringPropGenerate: function(prop, vnode, state) {
        var propName = prop.meta.arg;
        var propValue = prop.value;
        compileTemplateExpression(propValue, state.dependencies);

        if (state.hasAttrs === false) {
            state.hasAttrs = true;
        }

        if (propName === "class") {
            // Detected class, use runtime class render helper
            return ("\"class\": Moon.renderClass(" + propValue + "), ");
        } else {
            // Default literal attribute
            return ("\"" + propName + "\": " + propValue + ", ");
        }
    }
};

如果带样式,就通过renderClass生成样式代码,否则直接一段文本代码。

renderClass

它负责生成样式中class的code:

Moon.renderClass = function(classNames) {
    if (typeof classNames === "string") {
        // If they are a string, no need for any more processing
        return classNames;
    }

    var renderedClassNames = "";
    if (Array.isArray(classNames)) {
        // It's an array, so go through them all and generate a string
        for (var i = 0; i < classNames.length; i++) {
            renderedClassNames += (Moon.renderClass(classNames[i])) + " ";
        }
    } else if (typeof classNames === "object") {
        // It's an object, so to through and render them to a string if the corresponding condition is truthy
        for (var className in classNames) {
            if (classNames[className]) {
                renderedClassNames += className + " ";
            }
        }
    }

    // Remove trailing space and return
    renderedClassNames = renderedClassNames.slice(0, -1);
    return renderedClassNames;
}

可以看出来它同样是分了三种情况:class是字符串、数组和对象的情况。如果是字符串直接返回class名,如果是数组就递归拼接,如果是对象(形似{'active':isTrue})就根据值的真假拼接是键的class名。

m-mask

m-mask目前是空的,按字面意思是面具指令,猜测是类似v-mask的输入自动格式化指令。

自定义指令

用户可以自定义一个指令,它存储在directives里。
比如我们定义一个m-my指令,让一个input元素在页面加载的时候自动聚焦:

Moon.directive('my',
     function(node, vnode, parentVNode) {
         node.focus()
     }
)

它会先把m-my存储到directives里

Moon.directive = function(name, action) {
    directives["m-" + name] = action;
}

在diffProps的里面会执行m-my对应的函数:


深入浅出MV*框架源码(五):Moon中的指令_第1张图片
m-my.jpg

从这里可以看出,它传参数的方式和三种过程都不一样。

总结

指令在angular中出现的比vue略早一些,在ng里指令还分结构性指令和非结构性指令。但在moon中,指令也分为操作ast或code和不操作的指令。

你可能感兴趣的:(深入浅出MV*框架源码(五):Moon中的指令)