Javascript 常见方法实现

235 阅读2分钟

debounce

function debounce(func, wait) {
  let timer;
  return function () {
    if (timer) {
      window.clearTimeout(timer);
    }
    timer = setTimeout(() => {
      func.apply(this, arguments);
      timer = null;
    }, wait);
  };
}

throttle

function throttle(func, wait) {
  let time = 0;
  return function () {
    const current = Date.now();
    if (current - time < wait) {
      return;
    }
    // func()
    func.apply(this, arguments);
    time = current;
  };
}

memoize

function shallowEqual(arg1, arg2) {
  if (arg1.length !== arg2.length) {
    return false;
  }
  for (const n in arg1) {
    if (arg1[n] !== arg2[n]) {
      return false;
    }
  }
  return true;
}
function memoize(fn) {
  let lastThis,
    lastArgs = [],
    lastResult,
    triggerBefore = false;
  return function (...args) {
    if (triggerBefore && lastThis === this && shallowEqual(lastArgs, args)) {
      return lastResult;
    }
    lastThis = this;
    lastArgs = args;
    triggerBefore = true;
    return (lastResult = fn(...args));
  };
}

bind

Function.prototype.myBind = function (context, ...bindArgs) {
  const ctx = context || window;
  const self = this;
  return function (...args) {
    return self.apply(_this, [...args, ...bindArgs]);
  };
};

call

Function.prototype.myCall = function (context, ...args) {
  const ctx = context || window;
  const unique = new Symbol();
  ctx[unique] = this;
  ctx[unique](...args);
  delete ctx[unique];
};

apply

Function.prototype.myApply = function (context, args) {
  const ctx = context || window;
  const unique = new Symbol();
  ctx[unique] = this;
  ctx[unique](...args);
  delete ctx[unique];
};

定时器 hook

function useCountDown(init) {
  const [count, setCount] = useState(init);

  const [key, forceUpdate] = useReducer((x) => x + 1, 0);

  const reset = useCallback(() => {
    setCount(init);
    forceUpdate();
  }, [init]);

  useEffect(() => {
    const timer = window.setInterval(() => {
      setCount((prevCount) => {
        if (prevCount <= 0) {
          window.clearInterval(timer);
          return 0;
        }
        return prevCount - 1;
      });
    }, 1000);
    return function () {
      window.clearInterval(timer);
    };
  }, [key]);

  return [count, reset];
}

单例模式

const MyClass = (function () {
  let instance;
  return function (name) {
    if (instance) {
      return instance;
    }
    this.name = name;
    return (instance = this);
  };
})();

MyClass.prototype.showName = function () {
  console.log(this.name);
};

const a = new MyClass("aa");
const b = new MyClass("bb");

a.showName();
b.showName();

数组拍平

function flat(arr) {
  return arr.reduce(
    (total, curr) => total.concat(Array.isArray(curr) ? flat(curr) : curr),
    []
  );
}

深拷贝

需要考虑的点:

  • 准确的判断数据类型
  • 引用类型的复制
  • 方法的复制
  • 原型链的指向

要实现深拷贝,有且只有一种思路,就是判断不同的数据类型,如果遇到可遍历数据就进行遍历复制

function deepCopy(orgin) {
  switch (Object.prototype.toString.call(origin)) {
    case "[object Object]":
      const newObj = {};
      Object.entries(origin).forEach(
        ([key, value]) => (newObj[key] = deepCopy(value))
      );
      if (origin.__proto__ !== Object.prototype) {
        newObj.__proto__ = origin.prototype;
      }
      return newObj;
    case "[object Array]":
      return origin.map((item) => deepCopy(item));
    case "[object Number]":
    case "[object String]":
    case "[object Boolean]":
    case "[object Symbol]":
    case "[object Null]":
    case "[object Undefined]":
      return origin;
    default:
      return Object.assign(origin);
  }
}

p.s.

更严谨的方式是对一些不可遍历的数据也重新开辟新的存储空间,如 Number、String、Boolean 使用构造函数重新实例化(譬如new Number(origin)),对 Symbol 使用 Object(Symbol.prototype.valueOf.call(origin))

但是其实不这么严谨也行,因为不影响实际使用

以下方法所实现的拷贝均不是深拷贝

  • JSON.parse(JSON.stringify(obj))
  • arr.slice(0)

科里化,compose 方法

function compose(...fns) {
  return function (num) {
    return fns.reduce((total, fn) => fn(total), num);
  };
}
function fn1(x) {
  return x + 1;
}
function fn2(x) {
  return x + 2;
}
function fn3(x) {
  return x + 3;
}
function fn4(x) {
  return x + 4;
}
function fn5(x) {
  return x * 5;
}
const a = compose(fn1, fn2, fn3, fn4, fn5);
console.log(a(1)); // 1+1+2+3+4*5=55

科里化,add 方法

题目描述:实现一个 add 方法 使计算结果能够满足如下预期:

  • add(1)(2)(3)()=6
  • add(1,2,3)(4)()=10
function add(...numsOutter) {
  let result = numsOutter.reduce((total, curr) => total + curr, 0);
  const fn = function (...numsInner) {
    if (numsInner.length <= 0) {
      return result;
    }
    result = numsInner.reduce((total, curr) => total + curr, result);
    return fn;
  };
  return fn;
}

es5 实现继承

// 父类
function Human(gender) {
  this.gender = gender;
}
Human.prototype.showGender = function () {
  console.log(this.gender);
};

// 子类
function Teacher(name, level) {
  this.level = level;
  Human.call(this, name);
}
Teacher.prototype = Object.create(Human.prototype, {
  constructor: {
    value: Teacher,
  },
});
Teacher.prototype.showLevel = function () {
  console.log(this.level);
};

instanceof

function myInstanceof(a, b) {
  const target = b.prototype;
  let match = a.__proto__;
  while (match) {
    if (match === target) {
      return true;
    }
    match = match.__proto__;
  }
  return false;
}

new

function myNew(C, ...args) {
    /**
     * 创建一个空对象
     *
     * 下面写法有相同效果:
     *  - const obj = Object.create({});
     *  - const obj = {};
     */
    const obj = new Object();

    /**
     * 修改原型链
     *
     * 下面写法有相同效果:
     *  - obj.__proto__ = C.prototype;
     */
    Object.setPrototypeOf(obj, C.prototype);

    /**
     * 执行构造函数
     *
     * 需要接收结果用以判断
     */
    const result = C.apply(obj, args);

    /**
     * 需要判断调用构造函数后返回值 result 是否引用类型
     *
     * 如果是 result 是引用类型,意味着 new 操作符无效,返回 result
     * 如果不是,则忽略 result,返回前面一直要处理的对象 obj
     */
    return result instanceof Object ? result : obj;
}

上面 return 之所以要判断,是因为有些构造函数不正常,而你写的这个 myNew 要处理这些情况

你可以看下对下面三种构造函数使用 new 之后的产物就会明白了

// 正常写法
function Normal(name) {
    this.name = name;
}
// 不按套路来出牌,但也能正常实例化
function SpecialButOk(name) {
    this.name = name;
    return "abcdef";
}
// new 操作符无效
function NotOk(name) {
    this.name = name;
    return {
        age: 18,
    };
}

setTimeout 模拟实现 setInterval

带清除定时器的版本

思考下为什么要用 setTimeout 替代 setInterval

function mySetTimeout(fn, timeout) {
  let timer;
  const set = function () {
    timer = window.setTimeout(function () {
      fn();
      set();
    }, timeout);
  };
  set();
  return {
    cancel: function () {
      window.clearTimeout(timer);
      timer = null;
    },
  };
}