【面试题】模板字符串的缓存机制

160 阅读1分钟

【面试题】模板字符串的缓存机制

image-20230814014900145

这里可以看到一个有意思的现象. render()函数返回的实际上是html函数的结果为true, 但是直接调用html函数却是false.

首先我们需要知道一个pre知识:

例如,在模板字符串Hello ${name}!中,“Hello”和“!”是字面量部分,而${name}是插值部分,表示一个表达式。

  • html``这个语法是标签模板字符串. 也就是使用模板字符串来调用函数. (可以这么理解, 但是这个说法是不对的, 没有文档会说使用模板字符串来调用函数)
  • 通常写作这样的, html`hello${name}` 传递两个参数一个是strings数组values数组.
  • 也就是说, 如果不传值的话 html`` 的第一个参数会是""空数组.
  • html``=== html`` 的结果为false 就是因为 返回的两个数组是不同的空数组, 引用地址不同 所以3等为false

接下来我们看render

  • render函数实际上返回html``的返回值.
  • 那么根据上面的结果, 这里应该依旧是false. 但是为true.
  • 也就说明了, 两次返回的空数组 实际上是一个, 也就是引用地址相同.

从结果推过程得出的结论, 我们现在去找证明

  • 我从mdn中没有找到类似的用法, 我就去找了ECMAscript标准.看到材料如下
13.2.8.4 GetTemplateObject ( templateLiteral )
The abstract operation GetTemplateObject takes argument templateLiteral (a Parse Node) and returns an Array. It performs the following steps when called:

...

NOTE 2
Each TemplateLiteral in the program code of a realm is associated with a unique template object that is used in the evaluation of tagged Templates (13.2.8.6). The template objects are frozen and the same template object is used each time a specific tagged Template is evaluated. Whether template objects are created lazily upon first evaluation of the TemplateLiteral or eagerly prior to first evaluation is an implementation choice that is not observable to ECMAScript code.

  • 意思是:
这一部分说明了在特定的执行环境(realm)中,每个模板字面量都与一个唯一的模板对象关联。当特定的标记模板被评估时,将使用相同的模板对象。模板对象是冻结的,因此它们是不可变的。
  • 可以得出结论, 在render里调用html``的时候, 实际上使用的是缓存里的html标签, 这样就不需要重新进行拼接了.

我们在写几个demo验证一下

const tag = (strings) => strings;

function createTemplateA() {
  return tag``; // 返回空的标记模板字面量
}

function createTemplateB() {
  return tag``; // 返回空的标记模板字面量
}

const templateA = createTemplateA();
const templateB = createTemplateB();

console.log(templateA === templateB); // 输出:false

这里是因为不同的函数作用域里, 每个和当前作用域绑定了不同的tag.

块级作用域无法触发

const tag = (strings) => strings;

let templateA, templateB;

{
  // 一个块级作用域
  templateA = tag``; // 返回空的标记模板字面量
  templateB = tag``; // 返回空的标记模板字面量
}

console.log(templateA === templateB); // 输出:false

闭包访问同一作用域

const tag = () => tag
function outer() {
  const templateA = tag``;
  return function inner() {
    const templateB = tag``;
    return templateA === templateB; // true
  };
}

console.log(outer()()) //true

总结

模板字面量的缓存机制是一种性能优化,只在同一函数作用域内多次评估相同的标记模板字面量时生效。不同的函数作用域、块级作用域和全局作用域不会触发这种缓存机制。

函数的4种调用的方式

普通函数调用:最常见的调用方式,直接通过函数名调用。

function myFunction() {
  console.log('Called!');
}
myFunction();

方法调用:当函数作为对象的属性时,可以作为方法调用。

const obj = {
  myMethod: function() {
    console.log('Called!');
  }
};
obj.myMethod();

构造函数调用:使用new关键字

function MyConstructor() {
  this.value = 42;
}
const instance = new MyConstructor();

call apply 调用

function myFunction(a, b) {
    return a * b;
}
myArray = [10, 2];
myObject = myFunction.apply(myObject, myArray);  // 返回 20

其实还有立即调用函数, 生成器等.

补充

模板字符串通常用于国际化和安全html渲染

国际化

浏览器内置可能这样写这段脚本

const translations = {
  en: {
    greeting: "Hello ${name}!",
  },
  es: {
    greeting: "¡Hola ${name}!",
  }
};

然后这样去选择

function i18n(strings, ...values) {
  const lang = getUserLanguage(); // 假设这个函数返回用户的语言设置
  const template = translations[lang][strings[0]];
  return String.raw(template, ...values);
}

const name = "John";
const greeting = i18n`greeting`; // 如果用户的语言设置为"es",则返回"¡Hola John!"

安全的html渲染

渲染用户时候, 要防止XSS攻击, 标签模板字符串可以用来确保插入的内容被适当地转义。

function html(strings, ...values) {
  let result = strings[0];
  for (let i = 0; i < values.length; i++) {
    result += escapeHTML(values[i]) + strings[i + 1]; // 假设 escapeHTML 函数会转义 HTML 特殊字符
  }
  return result;
}

const userContent = "<script>alert('XSS!');</script>";
const safeHTML = html`<div>${userContent}</div>`; // 用户的输入被安全地转义

这样会转义html特殊字符.