前端开发通过chatgpt获得的能力提升,javascript篇(三)

168 阅读4分钟

JSON.stringify遇到循环引用的解决方案

这是一个自定义的safeStringify函数,可以处理循环引用:

function safeStringify(obj, replacer, space, cycleReplacer) {
  const stack = new WeakMap();

  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (stack.has(value)) {
        // 循环引用处理
        return cycleReplacer ? cycleReplacer(key, value) : '[Circular]';
      }

      stack.set(value, true);
    }

    return replacer ? replacer(key, value) : value;
  }, space);
}

memoize缓存接口或计算

export function memoize(fn) {
  const cache = new Map();

  return function (...args) {
    const key = safeStringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    // 使用箭头函数以保持正确的`this`上下文
    const result = (() => fn.apply(this, args))();
    cache.set(key, result);
    return result;
  };
}

计算两地距离

enum DistanceMode {
  /** 默认模式,返回值数字,单位是km */
  DEFAULT = 'DEFAULT',
  /** 中文模式,返回值为字符串,大于1km使用千米,小于1km使用米 */
  CHINESE = 'CHINESE',
  /** 英文模式,返回值为字符串,大于1km使用km,小于1km使用m */
  ENGLISH = 'ENGLISH',
}

type DistanceReturnType<T> = T extends 'DEFAULT'
  ? number
  : T extends 'CHINESE'
  ? string
  : T extends 'ENGLISH'
  ? string
  : never;

/**
 * 使用vincenty算法
 *
 * 若不传第五个参数为默认模式
 *
 * mode为DEFAULT是默认模式,返回值数字,单位是m,整数。
 *
 * mode为CHINESE是中文模式,返回值为字符串,大于1km使用千米,保留两位小数,小于1km使用米,整数。
 *
 * mode为ENGLISH是英文模式,返回值为字符串,大于1km使用km,保留两位小数,小于1km使用m,整数。
 *   */
export function distance<T extends keyof typeof DistanceMode>(
  lat1: number,
  lon1: number,
  lat2: number,
  lon2: number,
  mode: T = 'DEFAULT' as T,
): DistanceReturnType<T> {
  const a = 6378137; // 地球长半轴长度
  const f = 1 / 298.257223563; // 地球扁率
  const b = (1 - f) * a; // 地球短半轴长度
  const toRadians = (value) => (value * Math.PI) / 180;
  const L = toRadians(lon2 - lon1);

  const U1 = Math.atan((1 - f) * Math.tan(toRadians(lat1)));
  const U2 = Math.atan((1 - f) * Math.tan(toRadians(lat2)));
  const sinU1 = Math.sin(U1);
  const cosU1 = Math.cos(U1);
  const sinU2 = Math.sin(U2);
  const cosU2 = Math.cos(U2);

  let lambda = L;
  let lambdaP;
  let iterLimit = 100;
  let sinLambda;
  let cosLambda;
  let sinSigma;
  let cosSigma;
  let sigma;
  let sinAlpha;
  let cosSqAlpha;
  let cos2SigmaM;
  let C;

  do {
    sinLambda = Math.sin(lambda);
    cosLambda = Math.cos(lambda);
    sinSigma = Math.sqrt(
      cosU2 * sinLambda * (cosU2 * sinLambda) +
        (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) *
          (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda),
    );
    if (sinSigma === 0) {
      return 0 as DistanceReturnType<T>; // 两点重合
    }
    cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
    sigma = Math.atan2(sinSigma, cosSigma);
    sinAlpha = (cosU1 * cosU2 * sinLambda) / sinSigma;
    cosSqAlpha = 1 - sinAlpha * sinAlpha;
    cos2SigmaM = cosSigma - (2 * sinU1 * sinU2) / cosSqAlpha;
    if (isNaN(cos2SigmaM)) {
      cos2SigmaM = 0; // 逆极点
    }
    C = (f / 16) * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
    lambdaP = lambda;
    lambda =
      L +
      (1 - C) *
        f *
        sinAlpha *
        (sigma +
          C *
            sinSigma *
            (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
  } while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0);

  if (iterLimit === 0) {
    return NaN as DistanceReturnType<T>; // 失败,迭代次数达到限制
  }

  const uSq = (cosSqAlpha * (a * a - b * b)) / (b * b);
  const aA =
    1 + (uSq / 16384) * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
  const bB = (uSq / 1024) * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
  const deltaSigma =
    bB *
    sinSigma *
    (cos2SigmaM +
      (bB / 4) *
        (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) -
          (bB / 6) *
            cos2SigmaM *
            (-3 + 4 * sinSigma * sinSigma) *
            (-3 + 4 * cos2SigmaM * cos2SigmaM)));
  const s = b * aA * (sigma - deltaSigma);
  const modeProcessor = {
    [DistanceMode.DEFAULT](dis: number) {
      return Math.round(dis);
    },
    [DistanceMode.ENGLISH](dis: number) {
      if (dis < 1) {
        return `${Math.round(dis)}m`;
      } else {
        return `${(dis / 1000).toFixed(2)}km`;
      }
    },
    [DistanceMode.CHINESE](dis: number) {
      if (dis < 1) {
        return `${Math.round(dis)}米`;
      } else {
        return `${(dis / 1000).toFixed(2)}千米`;
      }
    },
  };
  return modeProcessor[mode](s) as DistanceReturnType<T>;
}

javascript ??运算符作用

在 JavaScript 中,?? 是一个逻辑运算符,被称为“空值合并运算符(Nullish Coalescing Operator)”。

该运算符的作用是,当左边的操作数为 nullundefined 时,返回右边的操作数,否则返回左边的操作数。换句话说,如果左边的操作数存在(非 nullundefined),则返回左边的值,如果不存在,则返回右边的值。

下面是一个例子:

ini
复制代码
javascriptCopy code
let foo = null ?? 'default string'; // 结果是 'default string'
let bar = undefined ?? 'default string'; // 结果是 'default string'
let baz = 'not nullish' ?? 'default string'; // 结果是 'not nullish'

这种运算符在处理可能为 nullundefined 的值时非常有用,例如在设置默认值或者处理可选属性时。

值得注意的是,??||(逻辑 OR 运算符)的行为不完全相同。|| 运算符会在左边的操作数为任何 JavaScript “假值”(如 0, '', NaN, false, null, undefined)时返回右边的操作数。而 ?? 运算符只在左边的操作数为 nullundefined 时才返回右边的操作数。

ini
复制代码
javascriptCopy code
let foo = '' || 'default string'; // 结果是 'default string'
let bar = '' ?? 'default string'; // 结果是 ''

在这个例子中,空字符串是一个“假值”,但不是 nullundefined,因此 ?? 运算符会返回空字符串,而 || 运算符会返回 'default string'。

Array.isArray有什么弊端

Array.isArray(obj)是一个用来判断一个对象是否为数组的方法,返回一个布尔值。它的主要弊端是:

  1. 只能判断一个对象是否为数组,无法判断一个对象是否为类数组对象或可迭代对象。如果想要判断一个类数组对象或可迭代对象是否为数组,需要使用其他方法或者自定义判断逻辑。
  2. 在一些较旧的浏览器中可能不支持Array.isArray方法,需要使用其他方式来判断一个对象是否为数组。
  3. 在某些特殊情况下,Array.isArray方法的返回结果可能会与预期不符。例如,如果在多个窗口之间共享一个数组对象,Array.isArray方法可能会返回false。