模板字串是可能是我們耳熟能詳的一個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>
不得不說,標籤函式在處理字串的時候,真的很有吸引力。
當然,你會不會使用標籤函式,還是取決於自己的喜好,蘿蔔白菜,各有所愛嘛。