前言
在看 Next.js 新特性时,看到处理表单方式介绍 (opens in a new tab)的时候,看到了这种使用 sql 的方法。
后面就去了解了一下这是什么。
什么是标签函数?
在 JS 中有一类特殊的函数 —— (字符串)标签函数,标签函数是一种特殊的函数调用语法,它赋予开发人员处理模板字面量的能力,从而以更加灵活和控制的方式生成和操作字符串,也就是用于自定义模版字符串的处理逻辑。标签函数是 ES6 引入的,因此不用担心兼容性问题! 标签函数除了可以作为普通函数,通过 fn()调用之外,标签函数还可以直接跟在模版字符串前面使用模板字符串 fn`` 来调用。换句话说,当一个函数使用模板字符串的方式调用时,这个函数就可以被称为标签函数,所以我们不妨把标签函数理解为新增的一种函数调用方式。比如:
function tagFn() {
return "我是返回值";
}
let res1 = tagFn();
let res2 = tagFn`一个模板字符串`;
console.log({ res1, res2 }); //{ res1: '我是返回值', res2: '我是返回值' }
内置的标签函数
JS 中只有一个内置标签函数 —— String.raw ,用于获取模字符串的原始字符串形式,即:
- 处理替换(例如替换${name}为变量实际的值)
- 不处理转义序列(例如 \n)
const name = "xh";
const s1 = `Hello \n ${name}`;
const s2 = String.raw`Hello \n ${name}`;
console.log(s1);
console.log(s2);
标签函数的参数
当标签函数存在参数时,它的参数是什么?
function tagFn(...args) {
console.log(...args);
}
tagFn`一个普通的模板字符串`;
// [ '一个普通的模板字符串' ]
tagFn`一个有插值的模板字符串:${"var"}`;
//[ '一个有插值的模板字符串:', '' ] var
tagFn`一个有插值的模板字符串:${"var1"}-${"var2"}`;
//[ '一个有插值的模板字符串:', '-', '' ] var1 var2
从上面可以看出,标签函数调用时,接收到的一个参数总是一个数组,数组中的元素就是模板字符串中的字符串部分;从第二个参数开始的剩余参数接收的是模板字符串的插值变量,这些变量的数目是任意的。换种方式声明的话,可能更直观一些:
function tagFn(templateStrings, ...insertVars) {
console.log({ templateStrings, insertVars });
}
tagFn`一个普通的模板字符串`;
//{ templateStrings: [ '一个普通的模板字符串' ], insertVars: [] }
tagFn`一个有插值的模板字符串:${"var"}`;
//{ templateStrings: [ '一个有插值的模板字符串:', '' ], insertVars: [ 'var' ] }
tagFn`一个有插值的模板字符串:${"var1"},${"var2"}`;
//{ templateStrings: [ '一个有插值的模板字符串:', ',', '' ], insertVars: [ 'var1', 'var2' ] }
tagFn`${"var"}一个有插值的模板字符串:${"var1"}!`;
//{ templateStrings: [ '', '一个有插值的模板字符串:', '!' ], insertVars: [ 'var', 'var1' ] }
可以看出来,templateStrings 中的每两个元素之间,都应该有一个 insertVars 中插入的变量。两个数组中元素的顺序是有对应关系的。当 templateStrings 开头和结尾有 insertVars 时会有个空字符串。
自定义标签函数
实现简易 String.raw
function myRaw(strings, ...values) {
let result = "";
for (let i = 0; i < strings.length; i++) {
result += strings.raw[i] || strings[i];
if (i < values.length) {
result += values[i];
}
}
return result;
}
console.log(`Hello \n world!`);
console.log(String.raw`Hello \n world!`);
console.log(myRaw`Hello \n world!`);
标签函数的应用场景
1. 语法校验
比如,对于 HTML 字符串,可以使用标签函数来自动转义模板字符串中的特殊字符,以防止 XSS (跨站脚本攻击)。下面是一个代码示例:
function safeHtml(strings, ...values) {
let result = strings[0];
for (let i = 1; i < strings.length; i++) {
let val = String(values[i - 1]);
result += val.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
result += strings[i];
}
return result;
}
let userSuppliedInput = "<img src=x onerror=alert('XSS')>";
const result = safeHtml`<div>${userSuppliedInput}</div>`;
// <div><img src=x onerror=alert('XSS')></div>
2. i18n(国际化和本地化)
标签函数可以用于处理模板字符串中的文本,使其根据用户的语言和地区进行适当的转换。
function translate(strings, ...values) {
const lang = "en"; // 假设当前语言为英语
const translations = {
Hello: "你好",
world: "世界",
};
let result = "";
strings.forEach((string, index) => {
result += string;
if (values[index] !== undefined) {
result += translations[values[index]] || values[index];
}
});
return result;
}
const greeting = translate`${"Hello"}, ${"world"}!`;
console.log(greeting);
// 输出: "你好, 世界!"
3. 特殊使用 (styled-components (opens in a new tab))
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 样式了!
总结
标签函数属于 ES6 特性,所以整体兼容性很不错,当我们需要处理模版字符串时,可以考虑自定义一个标签函数。