前端面试常见笔试算法题(一)

377 阅读2分钟

对象深拷贝

解法1:   先通过JSON.stringify序列化为字符串,再JSON.parse解析为对象即可

function deepCopy(obj) {
  return JSON.parse(JSON.stringify(obj));
}

解法2:   在遍历一个对象时,如果其属性是一个引用类型则递归的处理这个属性,并且为了解决循环引用,使用一个map来保存已经遍历过的引用属性。

function isObject(val) {
  return typeof val === 'object' && val !== null;
}

function deepClone(obj, hash = new WeakMap()) {
  // 如果是简单类型,直接返回
  if (!isObject(obj)) return obj;
  // 如果在map中已经存在,则直接返回,避免循环处理
  if (hash.has(obj)) return hash.get(obj);

  const newObj = Array.isArray(obj) ? [] : {};
  // 需要把已经处理过的引用类型保存到map
  hash.set(obj, newObj);
  for(let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 直接统一递归处理属性值,如果是简单类型会直接返回,引用类型会继续处理
      newObj[key] = deepClone(obj[key], hash)
    }
  }

  return newObj;
}

扁平js对象

题目描述:

// 实现一个 flatten 函数,实现如下的转换功能
const obj = {
  a: 1,
  b: [1, 2, { c: true }],
  c: { e: 2, f: 3 },
};

// 转化为扁平化之后的对象
let objRes = {
  a: 1,
  "b[0]": 1,
  "b[1]": 2,
  "b[2].c": true,
  "c.e": 2,
  "c.f": 3,
};

解题:

function isObject(val) {
  return typeof val === 'object' && val !== null;
}

function flattenObj(obj) {
  // 声明一个对象保存结果
  const res = {};

  function flatten(curr, path = "") {
    // 当是引用类型时,遍历属性,拼接path,再递归处理值
    if (isObj(curr)) {
      for (let key in curr) {
        if (curr.hasOwnProperty(key)) {
          // 拼接path,如果存在path则和key拼接,初始没有path时使用key。数组类型key用[],普通对象用.
          const newPath = path
            ? `${path}${Array.isArray(curr) ? `[${key}]` : `.${key}`}`
            : key;
           // 递归处理,直到最后值是简单类型
          flatten(curr[key], newPath);
        }
      }
    } else if (path) {
      // 简单类型直接复值
      res[path] = curr;
    }
  }
  recurse(obj);

  return res;
}

实现get函数根据路径获取对象值

题目描述:

// 根据路径获取对象值
// var object = { 'a': [{ 'b': { 'c': 3 } }] };

//get(object, 'a[0].b.c');
// => 3

////get(object, ['a', '0', 'b', 'c']);
// => 3

//get(object, 'a.b.c', 'default');
// 没值返回默认值 => 'default'

解题:通过查询路径上的所有属性,当不存在属性时直接return

function get(obj, path, defaultValue) {
  // 参数校验
  if (!obj || !path) return;
  
  if (typeof path === "string") {
    // 由于要支持sting和array的路径,所以这里路径要统一处理为数组
    // 'a[0].b.c' => ['a', '0', 'b', 'c']
    path = path.replace(/\[(\d+)\]/g, ".$1").split(".");
  }

  // 遍历获取对象值
  for (let i = 0; i < path.length; i++) {
    // 在遍历过程中当属性对象不存在,返回默认值。比如遍历[a,d,f]:a属性对应的[{ 'b': { 'c': 3 } }],不存在d,返回默认值
    if (obj === null || typeof obj !== "object") {
      return defaultValue;
    }
    obj = obj[path[i]];
  }
  // 判断最后一个属性是否存在值,不存在返回默认值
  return obj === undefined ? defaultValue : obj;
}

数组扁平

解题:判断数组每一项是否还是数组,再递归的处理

// deep 控制打平的层数
function flat(arr, deep = 1) {
  if (deep === 0) return arr;
  return arr.reduce((pre,cur) => {
    return pre.concat(Array.isArray(cur) ? flat(cur, deep - 1) : cur)
  },[])
}

手写call函数

解题:这种方式将函数先作为一个属性挂载到要绑定的ctx上,再通过 {}.fn()调用达到目的

// ctx为绑定的this指向
Function.prototype.Call = function(ctx, ...args) {
   // 不传值默认挂载到windown
  if (ctx === null || ctx === undefined) {
    ctx = window
  }

  let fn = Symbol(); // 通过Symbol()生成一个唯一的值,避免冲突
  ctx[fn] = this;
  const result = ctx[fn](...args); //通过 `{}.fn()`调用
  delete ctx[fn]; // 删除属性
  return result;
}

手写apply

// 同上call
Function.prototype.Apply = function(ctx, args) {
  if (ctx === null || ctx === undefined) {
    ctx = window
  }

  let fn = Symbol();
  ctx[fn] = this;

  const result = ctx[fn](...args);
  delete ctx[fn];
  return result;
}

手写bind

Function.prototype.Bind = function(ctx, ...args) {
  if (ctx === null || ctx === undefined) {
    ctx = window
  }

  // 套路一样,就是返回一个函数,在函数内部处理
  return (...args2) => {
    const fn = Symbol();
    ctx[fn] = this;
    const res = ctx[fn](...args,...args2);
    delete ctx[fn];
    return res;
  }
}

函数柯里化

定义:柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数,或者说函数接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数。

function curry(fn) {
  return function _curry(...args) {
    // fn.length指原函数的参数个数,当获取的参数 >= fn.length时,执行原函数
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
       // 否则return 一个函数接受剩下的参数,直到获取的参数 >= fn.length
      return function (...args2) {
        return _curry.apply(this, [...args, ...args2]);
      };
    }
  };
}

构造千分位分割

题目: 1234567 => 1,234,567

// 第一种方式
function thousandSplit(num) {
  // 通过Intl.NumberFormat内置函数处理
  return Intl.NumberFormat("en-US").format(num);
}

// 第二种方式
function thousandSplit(num) {
   // 将金额转换为字符串
  let numString = num.toString();

  // 使用正则表达式添加千分位分隔符, \B是匹配单词边界,(?=(\d{3})+$)是匹配三个数字
  numString = numString.replace(/\B(?=(\d{3})+$)/g, ",");

  return numString;
}