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

前言

Moon目前實(shí)現(xiàn)的內(nèi)置指令有八個(gè):if/show/for/on/model/html/literal/mask掘鄙,也可以讓用戶自定義指令鲫趁。

beforeGenerate ->duringPropGenerate->afterGenerate

所有指令都是在generateProps或generateNode里調(diào)用,分別按beforeGenerate ->duringPropGenerate->afterGenerate 的狀態(tài)執(zhí)行指令邏輯戈二,我們節(jié)選相關(guān)的代碼段:

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);
    }
}

顧名思義,這個(gè)beforeGenerate 值得就是compile過程中g(shù)enerate執(zhí)行之前祟偷,這時(shí)ast還沒有轉(zhuǎn)換成code讥耗。我們可以看到,傳入了四個(gè)參數(shù):prop, node, parent, state望伦,從state參數(shù)我們可以聯(lián)想到ast的state變量林说,所以更加確認(rèn)這是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這兩個(gè)時(shí)期屯伞,code已經(jīng)生成了或者正在生成中腿箩,操作的就是code了。這時(shí)我們發(fā)現(xiàn)duringPropGenerate比beforeGenerate少傳了個(gè)parent參數(shù)劣摇,因?yàn)闃浣Y(jié)構(gòu)中才有父元素嘛~
而afterGenerate又比duringPropGenerate多傳了個(gè)call參數(shù)(實(shí)際上它是一段code)珠移,這更加說明了afterGenerate是在generate完code后再往上面加code的過程。

內(nèi)置指令

m-if

m-if在afterGenerate里生成一個(gè)三元表達(dá)式末融,若條件為真就生成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也是生成一個(gè)三元表達(dá)式,但它是直接修改el.style.display勾习,和那三個(gè)過程無關(guān):

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

m-for

m-for在beforeGenerate里設(shè)置flatten數(shù)組的標(biāo)志位,在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 + "; })");
    }
}

我們可以發(fā)現(xiàn)它會把形似"m-if=(item,key) in iteratable"中的item/key當(dāng)做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;
}

很明顯巧婶,它對應(yīng)著我們api中使用for指令時(shí)的三種可迭代情況:數(shù)組乾颁、對象、數(shù)字艺栈。就像lodash封裝數(shù)組和對象為集合方法一樣英岭,針對數(shù)組和數(shù)字,就使用for循環(huán)迭代湿右,針對對象就使用for...in迭代巴席。
另外我們也明顯地感受到了這位作者不是前端出身的,因?yàn)樗鼡?dān)心i$1會覆蓋i變量所以才這樣命名的诅需,實(shí)際上js的var可以重復(fù)聲明的漾唉。

m-on

m-on在beforeGenerate里獲取事件名和回調(diào)以及事件修飾符(如prevent)荧库,然后生成調(diào)用事件的代碼并添加監(jiān)聽回調(diào):

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里獲取依賴、根據(jù)html類型綁定事件赵刑,然后動態(tài)檢測依賴的getter分衫,生成代碼(靠set改變值),添加事件監(jiān)聽:

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

因?yàn)閷?shí)際上model指令是bind指令和on指令的結(jié)合語法糖般此,所以只能適用于特殊的表單元素:input蚪战、checkbox、radio等铐懊,而Moon并沒有實(shí)現(xiàn)bind指令邀桑,所以在這里實(shí)現(xiàn)地頗為復(fù)雜,不過我們可以大致地知道bind value或者checked值它用getter和setter的方式實(shí)現(xiàn)了科乎,on的部分監(jiān)聽change事件壁畸。

m-html

m-html對應(yīng)v-html,它會在beforeGenerate里設(shè)置先通過compileTemplateExpression收集依賴,再設(shè)置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對應(yīng)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

它負(fù)責(zé)生成樣式中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是字符串碴倾、數(shù)組和對象的情況逗噩。如果是字符串直接返回class名,如果是數(shù)組就遞歸拼接跌榔,如果是對象(形似{'active':isTrue})就根據(jù)值的真假拼接是鍵的class名异雁。

m-mask

m-mask目前是空的,按字面意思是面具指令矫户,猜測是類似v-mask的輸入自動格式化指令片迅。

自定義指令

用戶可以自定義一個(gè)指令,它存儲在directives里皆辽。
比如我們定義一個(gè)m-my指令,讓一個(gè)input元素在頁面加載的時(shí)候自動聚焦:

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

它會先把m-my存儲到directives里

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

在diffProps的里面會執(zhí)行m-my對應(yīng)的函數(shù):


m-my.jpg

從這里可以看出柑蛇,它傳參數(shù)的方式和三種過程都不一樣。

總結(jié)

指令在angular中出現(xiàn)的比vue略早一些驱闷,在ng里指令還分結(jié)構(gòu)性指令和非結(jié)構(gòu)性指令耻台。但在moon中,指令也分為操作ast或code和不操作的指令空另。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盆耽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌摄杂,老刑警劉巖坝咐,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異析恢,居然都是意外死亡墨坚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門映挂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泽篮,“玉大人,你說我怎么就攤上這事柑船∶背牛” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵鞍时,是天一觀的道長亏拉。 經(jīng)常有香客問我,道長寸癌,這世上最難降的妖魔是什么专筷? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任弱贼,我火速辦了婚禮蒸苇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吮旅。我一直安慰自己溪烤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布庇勃。 她就那樣靜靜地躺著檬嘀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪责嚷。 梳的紋絲不亂的頭發(fā)上鸳兽,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機(jī)與錄音罕拂,去河邊找鬼揍异。 笑死,一個(gè)胖子當(dāng)著我的面吹牛爆班,可吹牛的內(nèi)容都是我干的衷掷。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼柿菩,長吁一口氣:“原來是場噩夢啊……” “哼戚嗅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤懦胞,失蹤者是張志新(化名)和其女友劉穎替久,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躏尉,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡侣肄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了醇份。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稼锅。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖僚纷,靈堂內(nèi)的尸體忽然破棺而出矩距,到底是詐尸還是另有隱情,我是刑警寧澤怖竭,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布锥债,位于F島的核電站,受9級特大地震影響痊臭,放射性物質(zhì)發(fā)生泄漏哮肚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一广匙、第九天 我趴在偏房一處隱蔽的房頂上張望允趟。 院中可真熱鬧,春花似錦鸦致、人聲如沸潮剪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抗碰。三九已至,卻和暖如春绽乔,著一層夾襖步出監(jiān)牢的瞬間弧蝇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工折砸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留看疗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓鞍爱,卻偏偏與公主長得像鹃觉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子睹逃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容