core-js 中的 Array.isTemplateObject:识别真正的模板字符串数组
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
导读
Array.isTemplateObject(value)是 TC39 提案 Array.isTemplateObject为主体,结合模块实现与单元测试,讲解该提案的语义、core-js 的判定算法、引入方式与典型应用场景,读完你可以直接在项目中使用core-js/proposals/array-is-template-object这一入口获得该能力。
提案背景:模板对象为什么值得被识别
ES2015 引入的模板字符串除了求值之外,还有一个容易被忽略的细节:标签模板调用会把“模板字面量的原始内容”以冻结数组的形式传给标签函数。例如(it => it)\qwe${ 123 }asd`中,标签函数收到的第一个参数是一个冻结数组['qwe', 'asd'],并且它还带有一个同样被冻结的raw属性,保存未经转义处理的原始片段['qwe', 'asd']`。
问题在于:语言规范只定义了“模板调用产生冻结数组”这一行为,却没有提供“检测一个值是不是这种模板数组”的内建 API。库作者往往只能通过判断Object.isFrozen(value) && value.raw !== undefined之类的启发式手段猜测,既不准确也不安全。
Array.isTemplateObject提案正是要填补这个空缺:它让开发者可以用一个标准、可靠的方法判断某个值是否真的是模板调用产物,从而在编译期/运行时工具链(例如安全地消费模板字符串、识别 CSS-in-JS 的模板调用、实现高性能模板缓存等场景)中做出正确分支。
core-js 中的模块与入口
在 core-js 中,该功能由 proposal 级模块承载,源码位于 esnext.array.is-template-object.js。它被编入以下入口:
- 提案入口:proposals/array-is-template-object.js,对应
core-js/proposals/array-is-template-object; - full 目录的单方法入口:full/array/is-template-object.js,对应
core-js(-pure)/full/array/is-template-object; - 聚合入口:full/array/index.js 第 9 行也会引入该模块;
- stage 入口:作为 Stage 2 提案,它被列入 stage/2.js 第 4 行,因此引入
core-js/stage/2(或更早的 stage 包)也会自动带上该能力。
对应的 TypeScript 签名与原文档一致:
class Array { static isTemplateObject(value: any): boolean }推荐的最小化引入方式:
// 仅引入本提案 import 'core-js/proposals/array-is-template-object'; // 或只引入单个方法(core-js-pure 同样适用) import 'core-js-pure/full/array/is-template-object'; Array.isTemplateObject((it => it)`qwe${ 123 }asd`); // => true判定算法:源码级拆解
原文档只给出了方法签名与一行示例,真正的判定细节在模块实现中。它内部先定义了一个辅助函数isFrozenStringArray(array, allowUndefined),随后isTemplateObject由两次该检查组合而成:
var isFrozenStringArray = function (array, allowUndefined) { if (!isFrozen || !isArray(array) || !isFrozen(array)) return false; var index = 0; var length = array.length; var element; while (index < length) { element = array[index++]; if (!(typeof element == 'string' || (allowUndefined && element === undefined))) { return false; } } return length !== 0; }; $({ target: 'Array', stat: true, sham: true, forced: true }, { isTemplateObject: function isTemplateObject(value) { if (!isFrozenStringArray(value, true)) return false; var raw = value.raw; return isFrozenStringArray(raw, false) && raw.length === value.length; } });判定条件可以逐条拆解为:
- 必须是数组且已冻结:
isFrozenStringArray内部先经isArray(来自 internals/is-array.js,按规范IsArray抽象操作实现)确认是数组,再要求Object.isFrozen为真。真正的模板数组在创建时就是冻结的,这一条能直接排除绝大多数普通对象与可变数组。 - 数组不能为空:返回
length !== 0,说明空数组一律不算模板对象。 - 元素必须全部是字符串:逐个遍历元素,非 string 类型直接返回
false。注意第一次调用isFrozenStringArray(value, true)传入allowUndefined = true——根据提案语义,模板数组的插值槽位对应的元素可以是undefined(对应undefined插值或尾随空槽场景),因此此处允许undefined元素通过。 raw属性必须是冻结字符串数组:value.raw需再次通过isFrozenStringArray(raw, false)。这次allowUndefined = false,因为规范中raw数组的所有元素一定是字符串;同时要求raw.length === value.length,确保主数组与原始片段数组一一对应。
只有四条同时满足,Array.isTemplateObject才返回true。一个没有raw属性的冻结字符串数组(例如Object.freeze(['hello']))会在第 4 步被拒绝,这正是它区别于“启发式猜测”的地方。
从源码结构看,$来自 internals/export.js,它负责把方法挂到全局Array上。其中stat: true表示挂为静态方法,forced: true表示即使宿主环境原生实现了同名方法也强制覆盖(保证行为一致),而sham: true会通过 export.js 给方法打上非标准的sham标记——因为 JavaScript 引擎目前无法真正“创建”一个模板对象,该方法只能识别真模板数组、无法凭空构造,因此它本质上是一个“sham”(不完全的 polyfill)而非完整 polyfill。
测试验证:行为边界一览
单元测试 对判定边界做了完整的覆盖,可以直接作为行为契约阅读:
assert.isFunction(isTemplateObject); assert.arity(isTemplateObject, 1); assert.name(isTemplateObject, 'isTemplateObject'); assert.looksNative(isTemplateObject); assert.nonEnumerable(Array, 'isTemplateObject'); assert.false(isTemplateObject(undefined)); assert.false(isTemplateObject(null)); assert.false(isTemplateObject({})); assert.false(isTemplateObject(function () { return arguments; }())); assert.false(isTemplateObject([])); assert.false(isTemplateObject(freeze([])), 'frozen string array without .raw should return false #1'); assert.false(isTemplateObject(freeze(['hello'])), 'frozen string array without .raw should return false #2'); const template = Function('return (it => it)`qwe${ 123 }asd`')(); if (template) assert.true(isTemplateObject(template));测试揭示的边界语义:
- 基础类型一律为
false:undefined、null、普通对象、arguments对象都不满足“冻结数组”前提; - 空数组(即使冻结)为
false:freeze([])返回false; - 冻结字符串数组但缺少
raw为false:freeze(['hello'])会通过前三步检查,却在raw检查处失败,这验证了“光靠冻结+字符串元素不足以冒充模板数组”的设计; - 真模板调用返回
true:测试用Function('return (it => it)\qwe${ 123 }asd`')()动态构造一次真实标签模板调用(外层用Function是为了在无法解析模板的环境下安全降级),再断言isTemplateObject(template) === true`。
使用注意事项
- 只识别、不构造:由于
sham属性,该方法无法与原生实现进行“能力对等”的替换——它只能判断某个值是不是语言层面真实产生的模板数组,不能把任意冻结数组“升级”为模板数组。 - 对插值槽位为
undefined的情况宽容:主数组允许undefined元素(allowUndefined = true),而raw数组不允许,二者判定策略不同,阅读代码或撰写测试时需要注意这一不对称。 - 引入时机:该能力属于 proposal 级(Stage 2),API 与语义仍可能随 TC39 讨论演进;在生产环境应通过
core-js/proposals/array-is-template-object或core-js(-pure)/full/array/is-template-object显式引入,避免整套 stage 包带来额外体积。 - 环境兼容:实现依赖
Object.isFrozen与Array.isArray(后者在 internals/is-array.js 中有classof兜底),不支持这些能力的极老环境需要更早的 core-js 版本先行填充基础能力。
小结
Array.isTemplateObject把“模板字符串调用产物的识别”从启发式猜测提升为标准化 API。core-js 以 proposal 模块的形式提供了完整实现:判定算法严格校验“冻结数组 + 全字符串元素 + 非空 + 冻结的raw+ 长度一致”,并配以覆盖全部边界条件的单元测试。需要识别标签模板调用的库或工具,可以直接通过core-js/proposals/array-is-template-object引入并在运行时安全地依赖这一判定结果。
【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考