别再说JS标签模板TaggedTemplateLiteral...

前有科技后进阶 2024-07-15 01:39:19

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

什么是 Tagged Template Literals

模板文字(template literals)或模板字符串(template strings)附带的一个好处是能够对它们进行标记。

这意味着开发者可以通过函数运行模板字符串,而不是让浏览器立即将该值分配给变量,从而可以控制实际字符串的生成方式。

标签模板(tag template)的工作方式是开发者只需创建一个函数,然后获取要针对字符串运行的函数的名称,最后只需将该函数名称放在模板前面即可:

function highlight() { // 可以是任意逻辑}const name = 'Snickers';const age = '100';const sentence = highlight`My dog's name is ${name} and he is ${age} years old`;console.log(sentence);

上面示例使用函数 highlight 来标记字符串,浏览器将运行这个函数,向其提供有关字符串的所有信息、用户输入的所有片段以及传入的变量。

在 highlight 函数实际被放入句子之前,开发者可以以任何给定的方式修改。假如有以下代码:

function highlight() { return 'cool';}

如果 highlight 函数只简单输入 return 'cool' ,函数将接受整个字符串作为参数,然后只返回单词“cool”,此时控制台将仅仅输出字符串cool。

const sentence = highlight`My dog's name is ${name} and he is ${age} years old`;console.log(sentence);// 输出 'cool'

显然,这不是我们实际想要的结果,但这个函数可以接受一些参数,这样开发者就可以自己创建字符串。

首先,highlight 函数将得到所有字符串片段的数组,主要包括:

My dog's name isand he isyears old

所有字符串片段数组之后的参数表示正在插值的值,上面例子则表示 name 和 age 参数。当然,好的方式是使用 ES6 的 Rest 运算符,比如下面的示例:

// 以上示例使用 ES6 Rest运算符function highlight(strings, ...values) { return 'cool';}

这里的 strings 是一个由三个元素组成的数组:

"My dog's name is ""and he is ""years old "

上面函数的 values 参数则是另外一个数组,代表实际传递的两个值,这里是字符串 Snickers 和 100。

因此,标记模板的意义在于可以自己创建字符串。 下面的代码将所有的字符串和变量值一起组合成最终的字符串,与浏览器的运行机制完全相同。值得注意的是,strings 数组的长度永远会比 values 多 1,因此最后做了一个判空处理。

function highlight(strings, ...values) { let str = ''; strings.forEach((string, i) => { str += string + (values[i] || ''); // 因为长度问题判空处理 }); return str;}const name = 'Snickers';const age = '100';const sentence = highlight`My dog's name is ${name} and he is ${age} years old`;console.log(sentence);Tagged Template 其他用法

标签函数的第一个参数包含一个字符串数组(字符串常量),其余的参数与表达式相关。

标签不必是普通的标识符,开发者可以使用任何优先级大于 16 的表达式,包括:属性访问、函数调用、new 表达式,甚至其他带标签的模板字面量。

console.log`Hello`;//输出 [ 'Hello' ]console.log.bind(1, 2)`Hello`;// 输出 2 [ 'Hello' ]new Function('console.log(arguments)')`Hello`;//输出 [Arguments] { '0': [ 'Hello' ] }function recursive(strings, ...values) { console.log(strings, values); return recursive;}recursive`Hello``World`;// 输出 [ 'Hello' ] []// 输出 [ 'World' ] []

虽然语法从技术上允许这么做,但不带标签的模板字面量是字符串,并且在链式调用时会抛出 TypeError。

console.log(`Hello``World`); // TypeError: "Hello" is not a function

唯一的例外是可选链,这将抛出语法错误

console.log?.`Hello`;// SyntaxError: Invalid tagged template on optional chainconsole?.log`Hello`;// SyntaxError: Invalid tagged template on optional chain

请注意,这两个表达式仍然是可解析的。这意味着它们将不受自动分号补全的影响,其只会插入分号来修复无法解析的代码。

// 仍是语法错误const a = console?.log`Hello`

标签函数接收到的第一个参数是一个字符串数组,对于任何模板字面量,其长度等于替换次数(${…} 出现次数)加一,因此总是非空的。对于任何特定的带标签的模板字面量表达式,无论对字面量求值多少次,都将始终使用完全相同的字面量数组调用标签函数。

const callHistory = [];function tag(strings, ...values) { callHistory.push(strings); // 返回一个新的对象 return {};}function evaluateLiteral() { return tag`Hello, ${'world'}!`;}console.log(evaluateLiteral() === evaluateLiteral());// 输出 false; 每次`tag`被调用都返回一个全新的对象console.log(callHistory[0] === callHistory[1]);// 输出 true。注意:相同标记文字(tagged literal)的所有计算都将传递到相同的字符串数组中

标签函数以其第一个参数作为标识来缓存结果。为了进一步确保数组值不变,第一个参数及其 raw 属性都会被冻结,因此将无法改变。

Tagged Template 能干什么

目前 Tagged Template 遇到的一个典型用例是字符串的解析,以下面的 html 方法为例,开发者希望将字符中的所有变量替换为真实的值,然后通过 DOM 操作渲染在屏幕中。

function render(state) { return html` <div>此时看看如何设计 html 方法能满足以上需求,这里就离不开借用 Tagged Template 的魔力。重点关注下面的 render 方法,该方法用于动态替换字符串中的变量并返回最终的 HTML 字符串。

const tokensToTemplate = new WeakMap();/** * 创建<template>元素 */function parseTemplate(htmlString) { const template = document.createElement('template'); template.innerHTML = htmlString; return template;}/** * 这里的tokens就是文字字符串数组,expressions是变量集合 * expressions: ['blue', 'Blue!'] */function html(tokens, ...expressions) { const replaceStubs = (string) => string.replaceAll(/__stub-(\d+)__/g, (_, i) => expressions[i]); let template = tokensToTemplate.get(tokens); if (!template) { // WeakMap没有数据才创建 const stubs = expressions.map((_, i) => `__stub-${i}__`); const allTokens = tokens.map((token, i) => (stubs[i - 1] ?? '') + token); const htmlString = allTokens.join(''); template = parseTemplate(htmlString); tokensToTemplate.set(tokens, template); } // 克隆和更新绑定 const cloned = template.content.cloneNode(true); // 克隆元素 const element = cloned.firstElementChild; // 这里是所有的属性,比如name为`class`,而 value 为 '__stub-0__' 字符串 for (const { name, value } of element.attributes) { element.setAttribute(name, replaceStubs(value)); } element.textContent = replaceStubs(element.textContent); return element;}

接下来使用 html 方法将生成的 DOM 插入到浏览器中:

function render(state) { return html` <div>https://wesbos.com/tagged-template-literals

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals

https://www.youtube.com/watch?v=arOXcy7K4XQ

https://www.youtube.com/watch?v=PgQ3wobT1_U

0 阅读:1

前有科技后进阶

简介:感谢大家的关注