JSON.stringify 
JSON.stringify() 方法将一个 js 对象或值转换为 json 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。
js
const jsonstringify = (data) => {
  // 确认一个对象是否存在循环引用
  const isCyclic = (obj) => {
    // 使用Set数据类型来存储已经检测过的对象
    let stackSet = new Set();
    let detected = false;
    const detect = (obj) => {
      // 不是对象类型的话,可以直接跳过
      if (obj && typeof obj != "object") {
        return;
      }
      // 当要检查的对象已经存在于stackSet中时,表示存在循环引用
      if (stackSet.has(obj)) {
        return (detected = true);
      }
      // 将当前obj存如stackSet
      stackSet.add(obj);
      for (let key in obj) {
        // 对obj下的属性进行挨个检测
        if (obj.hasOwnProperty(key)) {
          detect(obj[key]);
        }
      }
      // 平级检测完成之后,将当前对象删除,防止误判
      /*
        例如:对象的属性指向同一引用,如果不删除的话,会被认为是循环引用
        let tempObj = {
          name: '前端胖头鱼'
        }
        let obj4 = {
          obj1: tempObj,
          obj2: tempObj
        }
      */
      stackSet.delete(obj);
    };
    detect(obj);
    return detected;
  };
  // 特性七:
  // 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
  if (isCyclic(data)) {
    throw new TypeError("Converting circular structure to JSON");
  }
  // 特性九:
  // 当尝试去转换 BigInt 类型的值会抛出错误
  if (typeof data === "bigint") {
    throw new TypeError("Do not know how to serialize a BigInt");
  }
  const type = typeof data;
  const commonKeys1 = ["undefined", "function", "symbol"];
  const getType = (s) => {
    return Object.prototype.toString
      .call(s)
      .replace(/\[object (.*?)\]/, "$1")
      .toLowerCase();
  };
  // 非对象
  if (type !== "object" || data === null) {
    let result = data;
    // 特性四:
    // NaN 和 Infinity 格式的数值及 null 都会被当做 null。
    if ([NaN, Infinity, null].includes(data)) {
      result = "null";
      // 特性一:
      // `undefined`、`任意的函数`以及`symbol值`被`单独转换`时,会返回 undefined
    } else if (commonKeys1.includes(type)) {
      // 直接得到undefined,并不是一个字符串'undefined'
      return undefined;
    } else if (type === "string") {
      result = '"' + data + '"';
    }
    return String(result);
  } else if (type === "object") {
    // 特性五:
    // 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化
    // 特性六:
    // Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。
    if (typeof data.toJSON === "function") {
      return jsonstringify(data.toJSON());
    } else if (Array.isArray(data)) {
      let result = data.map((it) => {
        // 特性一:
        // `undefined`、`任意的函数`以及`symbol值`出现在`数组`中时会被转换成 `null`
        return commonKeys1.includes(typeof it) ? "null" : jsonstringify(it);
      });
      return `[${result}]`.replace(/'/g, '"');
    } else {
      // 特性二:
      // 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
      if (["boolean", "number"].includes(getType(data))) {
        return String(data);
      } else if (getType(data) === "string") {
        return '"' + data + '"';
      } else {
        let result = [];
        // 特性八
        // 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性
        Object.keys(data).forEach((key) => {
          // 特性三:
          // 所有以symbol为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
          if (typeof key !== "symbol") {
            const value = data[key];
            // 特性一
            // `undefined`、`任意的函数`以及`symbol值`,出现在`非数组对象`的属性值中时在序列化过程中会被忽略
            if (!commonKeys1.includes(typeof value)) {
              result.push(`"${key}":${jsonstringify(value)}`);
            }
          }
        });
        return `{${result}}`.replace(/'/, '"');
      }
    }
  }
};
// 各种测试
// 1. 测试一下基本输出
console.log(jsonstringify(undefined)); // undefined
console.log(jsonstringify(() => {})); // undefined
console.log(jsonstringify(Symbol("前端胖头鱼"))); // undefined
console.log(jsonstringify(NaN)); // null
console.log(jsonstringify(Infinity)); // null
console.log(jsonstringify(null)); // null
console.log(
  jsonstringify({
    name: "前端胖头鱼",
    toJSON() {
      return {
        name: "前端胖头鱼2",
        sex: "boy",
      };
    },
  })
);
// {"name":"前端胖头鱼2","sex":"boy"}
// 2. 和原生的JSON.stringify转换进行比较
console.log(jsonstringify(null) === JSON.stringify(null));
// true
console.log(jsonstringify(undefined) === JSON.stringify(undefined));
// true
console.log(jsonstringify(false) === JSON.stringify(false));
// true
console.log(jsonstringify(NaN) === JSON.stringify(NaN));
// true
console.log(jsonstringify(Infinity) === JSON.stringify(Infinity));
// true
let str = "前端胖头鱼";
console.log(jsonstringify(str) === JSON.stringify(str));
// true
let reg = new RegExp("w");
console.log(jsonstringify(reg) === JSON.stringify(reg));
// true
let date = new Date();
console.log(jsonstringify(date) === JSON.stringify(date));
// true
let sym = Symbol("前端胖头鱼");
console.log(jsonstringify(sym) === JSON.stringify(sym));
// true
let array = [1, 2, 3];
console.log(jsonstringify(array) === JSON.stringify(array));
// true
let obj = {
  name: "前端胖头鱼",
  age: 18,
  attr: ["coding", 123],
  date: new Date(),
  uni: Symbol(2),
  sayHi: function () {
    console.log("hello world");
  },
  info: {
    age: 16,
    intro: {
      money: undefined,
      job: null,
    },
  },
  pakingObj: {
    boolean: new Boolean(false),
    string: new String("前端胖头鱼"),
    number: new Number(1),
  },
};
console.log(jsonstringify(obj) === JSON.stringify(obj));
// true
console.log(jsonstringify(obj));
// {"name":"前端胖头鱼","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"前端胖头鱼","number":1}}
console.log(JSON.stringify(obj));
// {"name":"前端胖头鱼","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"前端胖头鱼","number":1}}
// 3. 测试可遍历对象
let enumerableObj = {};
Object.defineProperties(enumerableObj, {
  name: {
    value: "前端胖头鱼",
    enumerable: true,
  },
  sex: {
    value: "boy",
    enumerable: false,
  },
});
console.log(jsonstringify(enumerableObj));
// {"name":"前端胖头鱼"}
// 4. 测试循环引用和Bigint
let obj1 = { a: "aa" };
let obj2 = { name: "前端胖头鱼", a: obj1, b: obj1 };
obj2.obj = obj2;
console.log(jsonstringify(obj2));
// TypeError: Converting circular structure to JSON
console.log(jsonStringify(BigInt(1)));
// TypeError: Do not know how to serialize a BigInt