var randomBytes = require('randombytes');
// 生成一个内部唯一 ID 以使 regexp 模式更难猜测。
var PLACE_HOLDER_REGEXP = new RegExp('(\\\\)?"@__(F|R|D|M|S|A|U|I|B)-' + UID + '-(\\d+)__@"', 'g');
var IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g;
var IS_PURE_FUNCTION = /function.*?\(/;
var IS_ARROW_FUNCTION = /.*?=>.*?/;
var UNSAFE_CHARS_REGEXP = /[<>\/\u2028\u2029]/g;
var RESERVED_SYMBOLS = ['*', 'async'];
// 将不安全的HTML和无效的JavaScript行结束符字符映射到可以在JavaScript字符串中
function escapeUnsafeChars(unsafeChar) {
return ESCAPED_CHARS[unsafeChar];
var bytes = randomBytes(UID_LENGTH);
for (var i = 0; i < UID_LENGTH; ++i) {
result += bytes[i].toString(16);
function deleteFunctions(obj) {
if (typeof obj[key] === "function") {
for (var i = 0; i < functionKeys.length; i++) {
delete obj[functionKeys[i]];
module.exports = function serialize(obj, options) {
options || (options = {});
// 如果传入第二哥参数是 数字或者字符,直接兼容为 空格
if (typeof options === 'number' || typeof options === 'string') {
options = {space: options};
* 返回函数和regexp的占位符(由索引标识)它们后来被它们的字符串表示所取代。
function replacer(key, value) {
if (options.ignoreFunction) {
if (!value && value !== undefined) {
// 如果该值是一个带有toJSON方法的对象,则在replacer运行之前调用toJSON,因此我们使用这个[key]来获取非toJSON值
var origValue = this[key];
var type = typeof origValue;
if (origValue instanceof RegExp) {
return '@__R-' + UID + '-' + (regexps.push(origValue) - 1) + '__@';
if (origValue instanceof Date) {
return '@__D-' + UID + '-' + (dates.push(origValue) - 1) + '__@';
if (origValue instanceof Map) {
return '@__M-' + UID + '-' + (maps.push(origValue) - 1) + '__@';
if (origValue instanceof Set) {
return '@__S-' + UID + '-' + (sets.push(origValue) - 1) + '__@';
if (origValue instanceof Array) {
var isSparse = origValue.filter(function () {
}).length !== origValue.length;
return '@__A-' + UID + '-' + (arrays.push(origValue) - 1) + '__@';
if (type === 'function') {
return '@__F-' + UID + '-' + (functions.push(origValue) - 1) + '__@';
if (type === 'undefined') {
return '@__U-' + UID + '-' + (undefs.push(origValue) - 1) + '__@';
if (type === 'number' && !isNaN(origValue) && !isFinite(origValue)) {
return '@__I-' + UID + '-' + (infinities.push(origValue) - 1) + '__@';
return '@__B-' + UID + '-' + (bigInts.push(origValue) - 1) + '__@';
function serializeFunc(fn) {
var serializedFn = fn.toString();
if (IS_NATIVE_CODE_REGEXP.test(serializedFn)) {
throw new TypeError('Serializing native function: ' + fn.name);
// pure functions, example: {key: function() {}}
if (IS_PURE_FUNCTION.test(serializedFn)) {
// arrow functions, example: arg1 => arg1+5
if (IS_ARROW_FUNCTION.test(serializedFn)) {
var argsStartsAt = serializedFn.indexOf('(');
var def = serializedFn.substr(0, argsStartsAt)
var nonReservedSymbols = def.filter(function (val) {
return RESERVED_SYMBOLS.indexOf(val) === -1
// enhanced literal objects, example: {key() {}}
if (nonReservedSymbols.length > 0) {
return (def.indexOf('async') > -1 ? 'async ' : '') + 'function'
+ (def.join('').indexOf('*') > -1 ? '*' : '')
+ serializedFn.substr(argsStartsAt);
if (options.ignoreFunction && typeof obj === "function") {
// 防止`JSON.stringify()`返回'undefined',方法是序列化为文本字符串:“undefined”。
if (options.isJSON && !options.space) {
str = JSON.stringify(obj);
str = JSON.stringify(obj, options.isJSON ? null : replacer, options.space);
// 防止`JSON.stringify()`返回'undefined',方法是序列化为文本字符串:“undefined”。
if (typeof str !== 'string') {
if (options.unsafe !== true) {
str = str.replace(UNSAFE_CHARS_REGEXP, escapeUnsafeChars);
if (functions.length === 0 && regexps.length === 0 && dates.length === 0 && maps.length === 0 && sets.length === 0 && arrays.length === 0 && undefs.length === 0 && infinities.length === 0 && bigInts.length === 0) {
// Replaces all occurrences of function, regexp, date, map and set placeholders in the
// JSON string with their string representations. If the original value can
// not be found, then `undefined` is used.
return str.replace(PLACE_HOLDER_REGEXP, function (match, backSlash, type, valueIndex) {
// The placeholder may not be preceded by a backslash. This is to prevent
// replacing things like `"a\"@__R-<UID>-0__@"` and thus outputting
return "new Date(\"" + dates[valueIndex].toISOString() + "\")";
return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + regexps[valueIndex].flags + "\")";
return "new Map(" + serialize(Array.from(maps[valueIndex].entries()), options) + ")";
return "new Set(" + serialize(Array.from(sets[valueIndex].values()), options) + ")";
return "Array.prototype.slice.call(" + serialize(Object.assign({length: arrays[valueIndex].length}, arrays[valueIndex]), options) + ")";
return infinities[valueIndex];
return "BigInt(\"" + bigInts[valueIndex] + "\")";
var fn = functions[valueIndex];
return serializeFunc(fn);