瞭解ES6中的模板字串的標籤函式

forceddd發表於2021-11-30

模板字串是可能是我們耳熟能詳的一個ES6新特性,它可以允許我們在字串中插入變數,還能夠換行等等,確實使用起來非常地方便。然而,ES6還新增了一種主要用於和模板字串配合使用的標籤函式。

1.什麼標籤函式?

顧名思義,標籤函式也是一種函式。這是一個很簡單的標籤函式宣告:

function tagFoo() {
    console.log('這是一個標籤函式');
}

仔細觀察一下,看看tagFoo和我們常用的普通函式有什麼區別呢?是不是看不出區別呢?得出這個結論很正常,因為標籤函式其實就是一個函式,所以它的宣告和函式宣告是完全一樣的。

那麼,我們怎麼知道tagFoo是一個標籤函式呢,或者說標籤函式相對於普通函式有什麼特殊之處呢?答案是看tagFoo呼叫方式

tagFoo();//這是一個標籤函式
tagFoo`一個模板字串`;//這是一個標籤函式

標籤函式除了可以作為普通函式,通過()呼叫之外,還可以使用模板字串``來呼叫。換句話說,當一個函式使用模板字串的方式呼叫時, 這個函式就可以被稱為標籤函式,所以我們不妨把標籤函式理解為新增的一種函式呼叫方式。

那麼我們可能會想到,這兩種呼叫方式有什麼不同呢?它們的返回值是什麼呢?

不論哪種呼叫方式,都是呼叫了這個函式,最終的返回值也都是執行這個函式之後的結果。我們給tagFoo加上一個返回值來看看:

function tagFoo() {
    return '我是返回值';
}

let res1 = tagFoo(); 
let res2 = tagFoo`一個模板字串`; 
console.log({ res1, res2 });//{ res1: '我是返回值', res2: '我是返回值' }

2.標籤函式的引數

excuse me?難道這兩種呼叫方式就只有看起來不一樣嗎?當然不是。想來我們都已經注意到了,普通函式的呼叫方式允許我們自行傳入引數,而標籤函式呼叫方式似乎沒有給我們提供傳入引數的機會。一起看看標籤函式存在引數時,它的引數是什麼吧:

function tagFoo(...args) {
    console.log(...args);
}

tagFoo`一個普通的模板字串`; // [ '一個普通的模板字串' ]
tagFoo`一個有插值的模板字串:${'var'}`; //[ '一個有插值的模板字串:', '' ] var
tagFoo`一個有插值的模板字串:${'var1'}-${'var2'}`; //[ '一個有插值的模板字串:', '-', '' ] var1 var2

從上面可以看出,標籤函式呼叫時,接收到的一個引數總是一個陣列,陣列中的元素就是模板字串中的字串部分;從第二個引數開始的剩餘引數接收的是模板字串的插值變數,這些變數的數目是任意的。換種方式宣告的話,可能更直觀一些:

function tagFoo(templateStrings, ...insertVars) {
    console.log({ templateStrings, insertVars });
}
tagFoo`一個普通的模板字串`; //{ templateStrings: [ '一個普通的模板字串' ], insertVars: [] }
tagFoo`一個有插值的模板字串:${'var'}`; //{ templateStrings: [ '一個有插值的模板字串:', '' ], insertVars: [ 'var' ] }
tagFoo`一個有插值的模板字串:${'var1'},${'var2'}`; //{ templateStrings: [ '一個有插值的模板字串:', '-', '' ], insertVars: [ 'var1', 'var2' ] }

也可以用一張圖來表示:

也許可以形容為,templateStrings中的每兩個元素之間,都應該有一個insertVars中插入的變數。兩個陣列中元素的順序是有對應關係的。

3.標籤函式有什麼用?

標籤函式讓我們根據模板字串進行自己的邏輯行為,讓一些操作變得很簡單。

舉一個簡單的例子,將模板字串中的價格n轉成$n

function $(templateStrings, ...insertVars) {
    return templateStrings.reduce((res, temp, i) => {
        return res + temp + (i >= insertVars.length ? '' : '$' + insertVars[i]);
    }, '');
}

console.log($`1號衣服原價${42},打折後的價格是${2}`); //1號衣服原價$42,打折後的價格是$2
console.log($`2號鞋子原價${58},打折後的價格是${88}`); //2號鞋子原價$58,打折後的價格是$88

不使用標籤函式當然也能實現這個效果,也很簡單:

function $(n) {
    return '$' + n;
}
console.log(`1號衣服原價${$(42)},打折後的價格是${$(2)}`); //1號衣服原價$42,打折後的價格是$2
console.log(`2號鞋子原價${$(58)},打折後的價格是${$(88)}`); //2號鞋子原價$58,打折後的價格是$88

使用哪種方式取決於我們自己的喜好。

但是,在處理一些特殊的情景時,標籤函式可能會整一個大驚喜給到我們。

比如styled-components所做的:

const Button = styled.a`
  /* This renders the buttons above... Edit me! */
  display: inline-block;
  border-radius: 3px;
  padding: 0.5rem 0;
  margin: 0.5rem 1rem;
  width: 11rem;
  background: transparent;
  color: white;
  border: 2px solid white;

  /* The GitHub button is a primary button
   * edit this to target it specifically! */
  ${props => props.primary && css`
    background: white;
    color: black;
  `}
`

得到的Button就是一個React元件。通過styled-components,我們可以在JS中寫css樣式了!

我們也可以模仿styled-components,實現一個簡單的styled.a

const styled = {
    a(stringProps, ...getProps) {
        const varProps = getProps.map((f) => f({}) || '');
        const style = stringProps
            .reduce((prop, stringProp, i) => {
                return (
                    prop +
                    stringProp +
                    (i >= varProps.length ? '' : varProps[i])
                );
            }, '')
            //刪除註釋和換行
            .replace(/(\n|(\/\*[\s\S]*?\*\/))/g, '')
            //刪除空格
            .replace(
                /\s*?(?<propName>\w+):(?<propValue>[\s\S]*?);\s*/g,
                (...args) => {
                    const { propName, propValue } = args.pop();
                    return `${propName}:${propValue};`;
                }
            );

        //為了方便展示,返回一個字串
        return `<a style="${style}"></a>`;
    },
};
const Button = styled.a`
    /* This renders the buttons above... Edit me! */
    display: inline-block;
    border-radius: 3px;
    padding: 0.5rem 0;
    margin: 0.5rem 1rem;
    width: 11rem;
    background: transparent;
    color: white;
    border: 2px solid white;

    /* The GitHub button is a primary button
   * edit this to target it specifically! */
    ${(props) => !props.primary && 'background: white;'}
`;

console.log(Button);
//<a style="display: inline-block;border-radius: 3px;padding: 0.5rem 0;margin: 0.5rem 1rem;width: 11rem;background: transparent;color: white;border: 2px solid white;background: white;"></a>

不得不說,標籤函式在處理字串的時候,真的很有吸引力。

當然,你會不會使用標籤函式,還是取決於自己的喜好,蘿蔔白菜,各有所愛嘛。

相關文章