功能概述
isMasked 函数用于检测一个函数是否被 core-js 修改过。在 Lodash 的类型检测体系中,它主要配合 isNative 函数使用,用于识别被 polyfill 改写的原生函数。
core-js 简介
core-js 是一个现代化的 JavaScript 标准库,它提供了大量的 polyfill 实现。当我们使用 Babel 等工具转译代码时,通常会引入 core-js 来确保新的 JavaScript 特性在旧版本浏览器中也能正常工作。
例如,当我们在项目中使用 Array.prototype.includes 方法时:
js
// 在不支持 includes 的旧版本浏览器中
const arr = [1, 2, 3];
console.log(arr.includes(2)); // 通过 core-js 的 polyfill 使其正常工作
在这种情况下,core-js 会修改原生的 Array.prototype,添加 includes 方法的实现。这就是为什么我们需要一个机制来识别哪些函数是原生的,哪些是被 polyfill 修改过的。
源码实现
js
var coreJsData = context["__core-js_shared__"];
var maskSrcKey = (function () {
var uid = /[^.]+$/.exec(
(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO) || ""
);
return uid ? "Symbol(src)_1." + uid : "";
})();
function isMasked(func) {
return !!maskSrcKey && maskSrcKey in func;
}
实现原理解析
原理概述
isMasked 函数通过检查函数对象上是否存在特定的 Symbol 属性来判断该函数是否被 core-js 修改过。这个特定的 Symbol 属性是 core-js 在修改原生函数时添加的标记,通过这个标记我们可以识别出哪些函数被 polyfill 改写过。
为什么需要区分原生函数和 polyfill?
区分原生函数和 polyfill 实现有几个重要原因:
- 性能考虑:原生函数通常由浏览器引擎实现,性能更好。
- 功能稳定性:原生实现经过更严格的测试,更可靠。
- 调试需求:在排查问题时,知道一个函数是原生的还是 polyfill 的可以帮助我们更快定位问题。
例如,在性能敏感的应用中,我们可能会这样使用:
js
if (isNative(Array.prototype.sort)) {
// 使用原生排序
arr.sort();
} else {
// 使用自定义的排序实现
customSort(arr);
}
代码解析
1. core-js 环境检测
js
var coreJsData = context["__core-js_shared__"];
通过检查全局对象上是否存在 core-js_shared__ 属性来判断是否使用了 core-js:
- 如果使用了 core-js,这个属性会存在
- 如果没有使用 core-js,这个属性为 undefined
示例:
js
// 在使用了 core-js 的环境中
console.log(!!coreJsData); // => true
console.log(coreJsData);
// => {
// version: "3.30.2",
// keys: {
// IE_PROTO: "Symbol(IE_PROTO)_dj82n5",
// // 其他内部使用的键值
// },
// store: {
// // 存储 polyfill 相关的数据
// "Symbol(src)_1.e6YlpA": true,
// // 其他数据
// }
// }
// 在没有使用 core-js 的环境中
console.log(!!coreJsData); // => false
console.log(coreJsData); // => undefined
2. maskSrcKey 的生成
js
var maskSrcKey = (function () {
var uid = /[^.]+$/.exec(
(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO) || ""
);
return uid ? "Symbol(src)_1." + uid : "";
})();
这段代码通过自执行函数生成一个用于检测的键值。这里使用了三层条件判断来确保安全访问:
-
coreJsData
判断-
作用:检查是否在 core-js 环境中
-
场景:
js// 非 core-js 环境 console.log(coreJsData); // => undefined // core-js 环境 console.log(coreJsData); // => { version: "3.x.x", ... }
-
-
coreJsData.keys
判断-
作用:确保存在键值映射对象
-
场景:
js// 老版本 core-js 可能不存在 keys console.log(coreJsData.keys); // => undefined // 现代版本 core-js console.log(coreJsData.keys); // => { /* 键值映射对象 */ }
-
-
coreJsData.keys.IE_PROTO
判断-
作用:获取 IE 浏览器特定的标识符
-
场景:
js// 非 IE 环境 console.log(coreJsData.keys.IE_PROTO); // => undefined // IE 环境 console.log(coreJsData.keys.IE_PROTO); // => "Symbol(IE_PROTO)_1.e61anxgnoy"
-
这种链式条件判断确保了代码在各种环境下的安全执行。让我们看一下正则表达式 /[^.]+$/
是如何从 IE_PROTO 值中提取标识符的:
js
// IE_PROTO 值示例
var ieProtoValue = "Symbol(IE_PROTO)_1.e61anxgnoy";
// 正则表达式 /[^.]+$/ 匹配过程
// 1. [^.]+ 匹配除了点号之外的任意字符(一个或多个)
// 2. $ 确保匹配从字符串末尾开始
var uid = /[^.]+$/.exec(ieProtoValue); // => ["e61anxgnoy"]
// 最终生成的 maskSrcKey
var result = "Symbol(src)_1." + uid[0]; // => "Symbol(src)_1.e61anxgnoy"
这种机制确保了:
- 如果不是 core-js 环境,直接返回空字符串
- 如果是老版本 core-js,没有 keys 属性,返回空字符串
- 如果是 IE 环境,使用特定的 IE_PROTO 值生成标识符
示例:
js
// 在使用了 core-js 的环境中
console.log(maskSrcKey); // => "Symbol(src)_1.e61anxgnoy"
// 在没有使用 core-js 的环境中
console.log(maskSrcKey); // => ""
3. 函数标记检测
js
function isMasked(func) {
return !!maskSrcKey && maskSrcKey in func;
}
函数通过两步检查来判断函数是否被修改:
- 首先确保 maskSrcKey 存在(说明环境中使用了 core-js)
- 然后检查函数对象上是否存在 maskSrcKey 属性
示例:
js
// 在使用了 babel-polyfill(依赖 core-js)的环境下
console.log(isMasked(Array.prototype.includes)); // => true
console.log(isMasked(function () {})); // => false
// 在没有使用 core-js 的环境下
console.log(isMasked(Array.prototype.includes)); // => false
console.log(isMasked(function () {})); // => false
总结
isMasked 函数通过巧妙的标记检测机制,帮助我们识别出被 core-js 修改过的函数。这个功能在 Lodash 的类型检测系统中发挥着重要作用,特别是在需要区分原生函数和 polyfill 函数的场景下。