概述
__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__
的生成过程如下:
- 在
shared-store.js
中,首先定义了一个常量SHARED = '__core-js_shared__'
- 然后检查全局对象(通过
globalThis
)中是否已存在__core-js_shared__
属性 - 如果存在,则使用现有的;如果不存在,则通过
defineGlobalProperty
创建一个新的空对象 - 这个对象被导出并作为共享存储使用
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')时:
- 首先从共享存储中获取 'keys' 对象
- 检查请求的键是否已存在
- 如果存在,直接返回;如果不存在,则通过
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
从这些示例中可以看出:
- 每个值都遵循
Symbol(IE_PROTO)_数字
的格式 - 通过
sharedKey
函数获取的相同键值会被缓存,确保一致性 - 直接通过
uid
函数生成的值每次都是唯一的,即使键名相同
这种机制确保了在不同模块中使用相同的 IE_PROTO
键时,它们引用的是同一个唯一标识符,从而保证了跨模块的一致性。
总结
__core-js_shared__
提供了一个优雅的解决方案,用于在 JavaScript 库的不同部分之间共享数据,特别是在处理需要全局唯一性的标识符时。这种机制既保证了数据的一致性,又避免了全局命名空间的污染。通过 uid
和 sharedKey
函数的配合,core-js 能够生成格式如 Symbol(IE_PROTO)_1.e61anxgnoy
的唯一标识符,并在需要时可靠地复用它们。