面试中的那些手写代码题目

话不多说,直接进入正题

JS 防抖函数

基本的防抖函数

1
2
3
4
5
6
7
8
9
10
11
export function debounce(fn: Function, wait: number = 1000) {
  let t: any;
  return function () {
    const _this = this;
    t && clearTimeout(t);

    t = setTimeout(function () {
      fn.apply(_this, arguments);
    }, wait);
  };
}

添加 immediate 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export function debounce(
  fn: Function,
  wait: number = 1000,
  immediate: boolean = true
) {
  let t: any;
  return function () {
    const _this = this;
    const _arg = arguments;
    t && clearTimeout(t);

    if (immediate) {
      let isDo = !t;
      t = setTimeout(function () {
        t = null;
      }, wait);
      if (isDo) {
        fn.apply(_this, _arg);
      }
    } else {
      t = setTimeout(function () {
        fn.apply(_this, _arg);
      }, wait);
    }
  };
}

JS 节流函数

节流的概念为多次点击的过程中只会在每次大于 wait 等待时长执行方法

1
2
3
4
5
6
7
8
9
10
11
export function throttle(fn: Function, wait: number = 1000) {
  let prev: number = +new Date();

  return function () {
    const now: number = +new Date();
    if (now - prev > wait) {
      fn.apply(this, arguments);
      prev = +new Date();
    }
  };
}

手写 call

call 返回一个执行结果,第二个及以后的参数为执行函数的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Function.prototype.selfCall = function (context: Window = window) {
  const ctx = context || {};
  ctx.fn = this;
  const args = Array.from(arguments).slice(1);
  const result = ctx.fn(args);
  delete ctx.fn;
  return result;
};

const bar = {
  a: "this is bar",
};
const foo = {
  a: 1,
  getA: function (name, age = 0) {
    console.log(`prop: ${this.a}`);
    console.log(`name: ${name}`);
    console.log(`age: ${age}`);
    console.log("-------------------");
  },
};

foo.getA.selfCall(bar, "selfCall");

// prop: this is bar
// name: selfCall
// age: 0
// -------------------

手写 apply

apply 同样也是返回一个执行结果,只是第二个参数为数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function.prototype.selfApply = function (context: Window = window) {
  const ctx = context || {};
  ctx.fn = this;
  const arg = Array.from(arguments).slice(1)[0];
  const result = ctx.fn(...arg);
  delete ctx.fn;
  return result;
};

// 接着call的对象信息执行代码
foo.getA.selfApply(bar, ["selfApply", 22]);

// prop: this is bar
// name: selfApply
// age: 22
// -------------------

手写 bind

注意:bind 方法只是返回一个可执行函数,执行需要由用户自己执行返回的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Function.prototype.selfBind = function (context: Window = window) {
  const fn = this;
  const ctx = context || {};
  const arg = Array.from(arguments).slice(1);
  return function () {
    fn.call(ctx, ...arg);
  };
};

foo.getA.selfBind(null, "selfBind", 22)();

// prop: undefined
// name: selfBind
// age: 22
// -------------------

手写 Object.create 方法

科里化函数

通过判断参数是否符合实际方法所需参数的数量进行执行函数或者继续递归判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
export function curry(fn: Function) {
  const _this = this;
  const args = Array.from(arguments).slice(1);
  // fn.length 属性指明函数的形参个数。
  const len = fn.length;

  return function () {
    const _args = Array.from(arguments);
    args.push(..._args);
    if (args.length < len) {
      return curry.call(_this, fn, ...args);
    }

    return fn.apply(_this, args);
  };
}

// test
const addCur = function (a, b, c) {
  console.log("a + b + c", a + b + c);
};

const reduceCur = function (a, b, c) {
  console.log("a - b - c", a - b - c);
};

const add = curry(addCur, 2);
s(1)(2); // a + b + c 6
s(1, 3); // a + b + c 6

const reduce = curry(reduceCur);
reduce(1)(2)(3); // a - b - c -1
reduce(1, 2, 3); // a - b - c -3

compose

类似 react 中组件, compose(fn1, fn2, fn3) (...args) = > fn1(fn2(fn3(...args)))

1
2
3
4
5
6
7
export function compose(...fn: Function[]) {
  return function (...args: any) {
    return fn.reduceRight((prevResult, currentFn) => {
      return currentFn.call(this, ...prevResult);
    }, args);
  };
}

实现一个 instanceof

instanceof 的原理就是判断这个变量是否来自于某一个构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export function selfInstanceOf(left, right) {
  while (true) {
    if (left === null) return false;
    if (left === right) return true;
    if (left.__proto__ === right.prototype) return true;
    left = left.__proto__;
  }
}

// test
selfInstanceOf({}, Object); // true
selfInstanceOf({}, Array); // false
selfInstanceOf([], Array); // true
selfInstanceOf([], Object); // true

const Fn = function () {};
const a = new Fn();
selfInstanceOf(a, Fn); // true
selfInstanceOf(a, Object); // true

数组去重

就不使用 Set 去实现去重了,使用 filter + indexOf 实现数组去重

方法一

1
2
3
4
5
6
export function uniqueArray(arr: any[]) {
  return arr.filter((item, index) => arr.indexOf(item) === index);
}

console.log(uniqueArray([1, 2, 3, 2, 1, 3, 4, 5, 6, 3, 8]));
// [1, 2, 3, 4, 5, 6, 8]

方法二

1
2
3
4
5
6
7
8
9
10
export function uniqueArray(arr: any[]) {
  const newArr = arr.sort();
  let result = [newArr[0]];
  for (let i = 1; i < newArr.length; i++) {
    newArr[i] !== newArr[i - 1] && result.push(newArr[i]);
  }
  return result;
}
console.log(uniqueArray([1, 2, 3, 2, 1, 3, 4, 5, 6, 3, 8]));
// [1, 2, 3, 4, 5, 6, 8]

JS 深拷贝

JSON.stringify 的问题导致我们需要自己手写基本的深拷贝方法 以下只做了 object array 的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export function deepClone(obj: Object | any) {
  let result: any = {};
  for (let k in obj) {
    let typeStr = Object.prototype.toString
      .call(obj[k])
      .match(/\[object (.*?)\]/)[1]
      .toLowerCase();
    switch (typeStr) {
      case "object":
        result[k] = deepClone(obj[k]);
        break;
      case "array":
        result[k] = obj[k].slice();
        break;
      default:
        result[k] = obj[k];
    }
  }
  return result;
}

这里需要考虑到一个对象重复引用的问题, 如果存在对象引用,会导致代码重复执行,可以参考 lodash 的深拷贝代码, WeakMap 可以解决这个问题,后面会针对这个代码进行优化

✏️ 如有问题,欢迎指正
上一篇 : 关于闭包下一篇 : js数据类型转换