Lodash源码阅读-isMasked

功能概述

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 实现有几个重要原因:

  1. 性能考虑:原生函数通常由浏览器引擎实现,性能更好。
  2. 功能稳定性:原生实现经过更严格的测试,更可靠。
  3. 调试需求:在排查问题时,知道一个函数是原生的还是 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 : "";
})();

这段代码通过自执行函数生成一个用于检测的键值。这里使用了三层条件判断来确保安全访问:

  1. coreJsData 判断

    • 作用:检查是否在 core-js 环境中

    • 场景:

      js 复制代码
      // 非 core-js 环境
      console.log(coreJsData); // => undefined
      // core-js 环境
      console.log(coreJsData); // => { version: "3.x.x", ... }
  2. coreJsData.keys 判断

    • 作用:确保存在键值映射对象

    • 场景:

      js 复制代码
      // 老版本 core-js 可能不存在 keys
      console.log(coreJsData.keys); // => undefined
      // 现代版本 core-js
      console.log(coreJsData.keys); // => { /* 键值映射对象 */ }
  3. 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;
}

函数通过两步检查来判断函数是否被修改:

  1. 首先确保 maskSrcKey 存在(说明环境中使用了 core-js)
  2. 然后检查函数对象上是否存在 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 函数的场景下。

相关推荐
excel15 分钟前
Three.js 材质(Material)详解 —— 区别、原理、场景与示例
前端
掘金安东尼35 分钟前
抛弃自定义模态框:原生Dialog的实力
前端·javascript·github
hj5914_前端新手4 小时前
javascript基础- 函数中 this 指向、call、apply、bind
前端·javascript
薛定谔的算法4 小时前
低代码编辑器项目设计与实现:以JSON为核心的数据驱动架构
前端·react.js·前端框架
Hilaku5 小时前
都2025年了,我们还有必要为了兼容性,去写那么多polyfill吗?
前端·javascript·css
yangcode5 小时前
iOS 苹果内购 Storekit 2
前端
LuckySusu5 小时前
【js篇】JavaScript 原型修改 vs 重写:深入理解 constructor的指向问题
前端·javascript
LuckySusu5 小时前
【js篇】如何准确获取对象自身的属性?hasOwnProperty深度解析
前端·javascript
LuckySusu5 小时前
【js篇】深入理解 JavaScript 作用域与作用域链
前端·javascript
LuckySusu5 小时前
【js篇】call() 与 apply()深度对比
前端·javascript