深入浅出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对应的函数:


m-my.jpg

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

总结

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容