手写一些面试常见JS和自定义hooks

786 阅读3分钟

手写一些面试常见JS和自定hooks

  • 大数相加
  • 数组降维
  • 快速排序
  • n以内素数
  • 两数相加
  • 字符串中出现最大的字符
  • 手写Promise.all
  • useDebounce
  • useThrottle
  • useScroll

大数相加

javascript的Number精度有限,最大整数2^53 - 1,超过就不准确,这里可以使用BigInt来计算

// Number
9007199254740991+3617264626230116     //12624463880971108
// BigInt
9007199254740991n+3617264626230116n   //12624463880971107n

也可以自己封装大数相加,核心思路就是转换为String,在通过0补位让两个数字位数一样,在进行计算

let num1 = 9007199254740991;
let num2 = 3617264626230116;

function add(num1, num2) {
    let maxLength = null, sum = '', carry = 0, remainder = 0;
    num1 = num1.toString()  // 数字转换string
    num2 = num2.toString()
    maxLength = Math.max(num1.length, num2.length); //找出两个数最大的长度
    num1 = num1.padStart(maxLength, '0') //把两个字符串补位到相同
    num2 = num2.padStart(maxLength, '0')

    // 满10进1
    for (let index = maxLength - 1; index >= 0; index--) {
        remainder = parseInt(num1[index]) + parseInt(num2[index]) + carry
        carry = Math.floor(remainder / 10)
        sum = remainder % 10 + sum
    }

    if (carry === 1) {
        sum = 1 + sum
    }

    return sum
}
add(num1, num2)

数组降维

使用reduce累加,非数组直接concat添加,数组就递归调用

let array = [1,2,3,[4,[5,{name:'张三'}]]]

const isArray = (obj) => Object.prototype.toString.call(obj) === '[object Array]'

function flat(array) {
    return array.reduce((sum, next) => {
        return isArray(next) ? sum.concat(flat(next)) : sum.concat(next)
    }, [])
}

flat(array)

快速排序

找一个基准值(这里是选的数组中间的数,方便理解),根据基准值分别获取一个大于值的数组left,和一个小于基准值的数组right,两个数组left和right递归执行,当left和right只剩下一项的时候就返回自己本身,最后把值组合起来

        function quickSort(arr) {
            if (arr.length <= 1) return arr
            const len = arr.length
            const mid = Math.floor(len / 2)
            const midArray = arr.splice(mid, 1)
            const midValue = midArray[0]
            const left = []
            const right = []
            
            for (let i = 0; i < len; i++) {
                if (arr[i] >= midValue) {
                    right.push(arr[i])
                } else if (arr[i] < midValue) {
                    left.push(arr[i])
                }
            }

            return quickSort(left).concat(midArray, quickSort(right))
        }

n内素数的个数

素数除了1和該数自身外,無法被其他自然数整除的数,被除了1和自身外的数去余不为0就不是素数

let countPrimes = function (n: any) {
  let ans: any = 0;
  for (let i = 2; i <=n; ++i) {
    ans += isPrime(i);
  }
  return ans;
};

const isPrime = (x: any) => {
  for (let i = 2; i * i <= x; ++i) { 
    if (x % i == 0) {
      return false;
    } 
  } 
  return true;
};
countPrimes(100) //25

两数相加

使用对象记录值,key为值,value为index,两个value相加等于目标值时,就返回index

function twoSum (nums, target) {
    let obj = {}
    
    for (let i = 0; i < nums.length; i++) {
        if (obj[nums[i]] == undefined) {
            obj[nums[i]] = i
        }
        
        if (obj[target - nums[i]] !== undefined && obj[target - nums[i]] - i) {
            return [obj[target - nums[i]], i]
        }
    }
    
};
twoSum([2, 3, 4, 5, ], 9) // [2,3]

找出字符串出现次数最多的字符

function stringSort(str) {
  const obj = {}; // 存储字符串出现次数
  const len = str.length;
  let maxStr = ""; // 存储字符串
  let maxNum = 0; // 字符串出现的最多次数

  for (let index = 0; index < len; index++) {
    let key = str[index];
    // 对象key没有值就赋值1,有值就加1
    if (!obj[key]) {
      obj[key] = 1;
    } else {
      obj[key]++;
    }
    // 大于之前的字符串出现次数,就替换成新的字符串
    if (obj[key] > maxNum) {
      maxNum = obj[key];
      maxStr = key;
    }
  }

  return [maxStr, maxNum];
}
stringSort('dfgdfgaaadffg') // ["f", 4]

Promise.all

返回的数组排序根据传入的Promise顺序,而不是执行顺序,有一个Promise失败,直接返回reject

function promiseAll(array) {
  let count = 0;
  const len = array.length;
  const result = new Array(len);
  return new Promise((resolve, reject) => {
    for (let index = 0; index < array.length; index++) {
      Promise.resolve(array[index]).then(
        (value) => {
          result[index] = value;
          count++;
          if (count === array.length) {
            resolve(result);
          }
        },
        (value) => {
          reject(value);
        }
      );
    }
  });
}

usePersistFn

函数持久化,利用useRef的特性可以穿透闭包,获取最新的state,区别于useCallback可以不让子组件刷新的同时获取最新的state

function usePersistFn(fn) {
  const ref = useRef(fn);
  const fnRef = useRef()
  ref.current = fn;

  if (!fnRef.current) {
    fnRef.current = (...args) => {
      ref.current(...args)
    }
  }
  return fnRef.current;
}

栗子

  const Foo = () => {
    const [num, setNum] = useState(0);
    const show = () => {
      console.log(num);
    };
    return (
      <>
        <div>{num}</div>
        <button
          onClick={() => {
            setNum((num) => num + 1);
          }}
        >
          add
        </button>
        <Bar show={show} />
      </>
    );
  };
  
  const Bar = React.memo(({ show }) => {
    useEffect(() => {
      console.log('Bar'); // 没有每次add时调用
    });
    return (
      <>
        <button
          onClick={() => {
            show();
          }}
        >
          show
        </button>
      </>
    );
  });

useDebounce

一个常见的节流,但是在如果在react中使用并修改state,函数重新调用会让timeout不准确,防抖节流就失效了

function debounce(func, wait) {
  let timeout;

  let debounced = function () {
    let context = this;
    let args = arguments;

    if (timeout) clearTimeout(timeout);

    timeout = setTimeout(function () { 
      func.apply(context, args);
    }, wait);
  };

  debounced.cancel = function () {
    clearTimeout(timeout);
    timeout = null;
  };

  return debounced;
}

function useDebounce(fn, delay) {
  const ref = useRef(fn);
  ref.current = fn;

  let debounced = useCallback(
  // 这里使用上文的防抖
    debounce(() => {
      ref.current();
    }, delay),
    []
  );

  useEffect(() => {
    return () => {
      const callback = debounced;
      callback.cancel && callback.cancel();
    };
  }, [debounced]);

  return debounced;
}
}

栗子

const Test = () => {
  const [counter1, setCounter1] = useState(0);
  const [counter2, setCounter2] = useState(0);

  const handleClick = useDebounce(function () { 
    setCounter1(counter1 + 1);
  }, 500);

  useEffect(function () {
    const t = setInterval(() => {
      setCounter2((x) => x + 1);
    }, 500);
    return clearInterval.bind(undefined, t);
  }, []);

  return (
    <div style={{ padding: 30 }}>
      <Button
        onClick={function () {
          handleClick();
        }}
      >
        click
      </Button>
      <div>{counter1}</div>
      <div>{counter2}</div>
    </div>
  );
};

useThrottle

和useDebounce相似

function throttle(func, wait) {
    let timeout; 

    return function() {
        context = this;
        args = arguments;
        if (!timeout) {
            timeout = setTimeout(function(){
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}

function useThrottle(fn, delay ) {
  const ref = useRef(fn);
  ref.current = fn;

  let throttled = useCallback(
    throttle(() => {
      ref.current();
    }, delay),
    []
  );

  useEffect(() => {
    return () => {
      throttled.cancel && throttled.cancel();
    };
  }, [throttled]);

  return throttled;
}


useScroll

获取ref或者document的scrollLeft和scrollTop

const useScroll = (target) => {
  const [position, setPosition] = useState({ left: 0, top: 0 });

  const lister = (e) => {
    setPosition({
      left: e.target.scrollLeft || document.scrollingElement.scrollLeft,
      top: e.target.scrollTop || document.scrollingElement.scrollTop,
    });
  };

  useEffect(() => {
    let el = target === document ? document : target?.current;
    el && el.addEventListener('scroll', lister);

    return () => {
      el && el.removeEventListener('scroll', lister);
    };
  }, [target]);

  return position;
};

栗子


const Demo = ()=>{
  const dom = useRef(); 
  const position = useScroll(dom);
  return (
    <>
      <div>{`${position.top},${position.left}`}</div>
      <div
        ref={dom}
        style={{
          overflow: 'auto',
          width: '300px',
          height: '200px',
          border: '1px solid red',
        }}
      >
        <div
          style={{ width: '900px', height: '200px', border: '1px solid red' }}
        ></div>
        <div
          style={{ width: '900px', height: '200px', border: '1px solid red' }}
        ></div>
        <div
          style={{ width: '900px', height: '200px', border: '1px solid red' }}
        ></div>
        <div
          style={{ width: '900px', height: '200px', border: '1px solid red' }}
        ></div>
        <div
          style={{ width: '900px', height: '200px', border: '1px solid red' }}
        ></div>
      </div>
    </>
  )
}

参考文章