Lodash源码阅读-__core-js_shared__

概述

__core-js_shared__ 是 core-js 库中的一个全局共享存储机制,用于在不同模块间共享数据。这个机制特别用于存储一些需要在整个应用程序生命周期内保持一致的值,如符号注册表、内部键值等。

uncurryThis 工具函数

在 core-js 的实现中,uncurryThis 是一个重要的工具函数,它用于确保方法的正确调用,特别是在处理原型方法时。这个函数在 uid 生成过程中被使用,对理解整个共享机制很有帮助。

实现原理

uncurryThis 的核心目的是将一个依赖 this 的方法转换为一个普通函数,使其第一个参数成为原来的 this 上下文。其实现如下:

javascript 复制代码
var NATIVE_BIND = !fails(function () {
  var test = function () {
    /* empty */
  }.bind();
  return typeof test != "function" || test.hasOwnProperty("prototype");
});

var FunctionPrototype = Function.prototype;
var call = FunctionPrototype.call;

// 优化版本(如果原生 bind 可用)
var uncurryThisWithBind =
  NATIVE_BIND && FunctionPrototype.bind.bind(call, call);

// 最终导出的函数
module.exports = NATIVE_BIND
  ? uncurryThisWithBind
  : function (fn) {
      return function () {
        return call.apply(fn, arguments);
      };
    };

通俗示例

为了更好地理解 uncurryThis,我们可以通过一个简单的例子来说明:

假设我们有一个普通的字符串方法 toUpperCase

javascript 复制代码
// 正常使用方式
"hello".toUpperCase(); // 返回 "HELLO"

这个方法依赖于 this 上下文(即字符串对象本身)。如果我们想把它作为一个独立函数使用,通常会这样做:

javascript 复制代码
// 错误的方式 - this 上下文丢失
const toUpper = String.prototype.toUpperCase;
toUpper("hello"); // 错误:this 是 undefined 或 global

// 临时解决方式 - 每次调用都需要 .call 或 .apply
toUpper.call("hello"); // 返回 "HELLO"

而使用 uncurryThis,我们可以创建一个新函数,它自动处理 this 绑定:

javascript 复制代码
// 使用 uncurryThis
const toUpper = uncurryThis(String.prototype.toUpperCase);
toUpper("hello"); // 返回 "HELLO"

这个新函数 toUpper 现在可以像普通函数一样使用,第一个参数会被当作原来的 this 上下文。

在 uid 中的应用

uid.js 中,uncurryThis 被用于获取 toString 方法的一个"解绑"版本:

javascript 复制代码
var toString = uncurryThis((1.0).toString);

这使得 toString 可以作为一个普通函数使用,而不需要担心 this 绑定问题。在生成唯一标识符时,这个函数被用来将数字转换为指定进制的字符串:

javascript 复制代码
toString(++id + postfix, 36);

这种方式比直接调用 (++id + postfix).toString(36) 更加灵活和可靠,特别是在处理可能的原型污染或方法覆盖的情况下。

生成过程

__core-js_shared__ 的生成过程如下:

  1. shared-store.js 中,首先定义了一个常量 SHARED = '__core-js_shared__'
  2. 然后检查全局对象(通过 globalThis)中是否已存在 __core-js_shared__ 属性
  3. 如果存在,则使用现有的;如果不存在,则通过 defineGlobalProperty 创建一个新的空对象
  4. 这个对象被导出并作为共享存储使用
javascript 复制代码
var SHARED = "__core-js_shared__";
var store = globalThis[SHARED] || defineGlobalProperty(SHARED, {});

存储结构

__core-js_shared__ 是一个普通的 JavaScript 对象,其结构大致如下:

javascript 复制代码
{
  versions: [{
    version: '3.41.0',
    mode: 'global', // 或 'pure'
    copyright: '© 2014-2025 Denis Pushkarev (zloirock.ru)',
    license: '...',
    source: '...'
  }],
  keys: {
    // 通过 shared('keys') 创建的共享键值对象
    'IE_PROTO': 'Symbol(IE_PROTO)_1.e61anxgnoy', // 实际示例值
    // 其他键...
  },
  // 其他共享数据...
}

键值生成机制

当通过 sharedKey 函数请求一个键(如 'IE_PROTO')时:

  1. 首先从共享存储中获取 'keys' 对象
  2. 检查请求的键是否已存在
  3. 如果存在,直接返回;如果不存在,则通过 uid 函数生成一个唯一标识符

uid 函数实现

uid 函数的核心实现如下:

javascript 复制代码
var id = 0;
var postfix = Math.random();
var toString = uncurryThis((1.0).toString);

module.exports = function (key) {
  return (
    "Symbol(" +
    (key === undefined ? "" : key) +
    ")_" +
    toString(++id + postfix, 36)
  );
};

这个函数生成的唯一标识符由两部分组成:

  • 前缀部分:Symbol(key)_,其中 key 是传入的键名(如 'IE_PROTO')
  • 后缀部分:一个由递增计数器(id)与随机数(postfix)组合而成的值,转换为 36 进制字符串

sharedKey 函数实现

sharedKey 函数则负责管理键的缓存:

javascript 复制代码
var shared = require("../internals/shared");
var uid = require("../internals/uid");

var keys = shared("keys");

module.exports = function (key) {
  return keys[key] || (keys[key] = uid(key));
};

这个函数确保相同的键只会生成一次唯一标识符,之后会从缓存中获取,保证了跨模块的一致性。

IE_PROTO 值示例

以下是通过实际运行生成的 IE_PROTO 值示例:

javascript 复制代码
// 第一次生成的 IE_PROTO 值(会被缓存)
Symbol(IE_PROTO)_1.e61anxgnoy

// 第二次获取的 IE_PROTO 值(从缓存中获取,与第一次相同)
Symbol(IE_PROTO)_1.e61anxgnoy

// 另一个不同键的值
Symbol(OTHER_KEY)_2.e61anxgnoy

// 不使用缓存直接生成的新 IE_PROTO 值
Symbol(IE_PROTO)_3.e61anxgnoy

// 更多 IE_PROTO 示例值(每次都不同)
Symbol(IE_PROTO)_4.e61anxgnp
Symbol(IE_PROTO)_5.e61anxgnp
Symbol(IE_PROTO)_6.e61anxgnp
Symbol(IE_PROTO)_7.e61anxgnp
Symbol(IE_PROTO)_8.e61anxgnp

从这些示例中可以看出:

  1. 每个值都遵循 Symbol(IE_PROTO)_数字 的格式
  2. 通过 sharedKey 函数获取的相同键值会被缓存,确保一致性
  3. 直接通过 uid 函数生成的值每次都是唯一的,即使键名相同

这种机制确保了在不同模块中使用相同的 IE_PROTO 键时,它们引用的是同一个唯一标识符,从而保证了跨模块的一致性。

总结

__core-js_shared__ 提供了一个优雅的解决方案,用于在 JavaScript 库的不同部分之间共享数据,特别是在处理需要全局唯一性的标识符时。这种机制既保证了数据的一致性,又避免了全局命名空间的污染。通过 uidsharedKey 函数的配合,core-js 能够生成格式如 Symbol(IE_PROTO)_1.e61anxgnoy 的唯一标识符,并在需要时可靠地复用它们。

相关推荐
Fantasywt3 小时前
THREEJS 片元着色器实现更自然的呼吸灯效果
前端·javascript·着色器
IT、木易3 小时前
大白话JavaScript实现一个函数,将字符串中的每个单词首字母大写。
开发语言·前端·javascript·ecmascript
Mr.NickJJ4 小时前
JavaScript系列06-深入理解 JavaScript 事件系统:从原生事件到 React 合成事件
开发语言·javascript·react.js
张拭心6 小时前
2024 总结,我的停滞与觉醒
android·前端
念九_ysl6 小时前
深入解析Vue3单文件组件:原理、场景与实战
前端·javascript·vue.js
Jenna的海糖6 小时前
vue3如何配置环境和打包
前端·javascript·vue.js
Mr.NickJJ6 小时前
React Native v0.78 更新
javascript·react native·react.js
星之卡比*6 小时前
前端知识点---库和包的概念
前端·harmonyos·鸿蒙
灵感__idea6 小时前
Vuejs技术内幕:数据响应式之3.x版
前端·vue.js·源码阅读
烛阴6 小时前
JavaScript 构造器进阶:掌握 “new” 的底层原理,写出更优雅的代码!
前端·javascript