js

106 阅读6分钟

重要api实现

用ES5实现数组的map方法

Array.prototype.MyMap = function(fn, context){
  //不修改原来的数组。由于是ES5所以就不用...展开符了。
  var arr = Array.prototype.slice.call(this);
  var mappedArr = [];
  for (var i = 0; i < arr.length; i++ ){
    mappedArr.push(fn.call(context, arr[i], i, this));
  }
  return mappedArr;
}

用ES5实现数组的reduce方法

Array.prototype.myReduce = function(fn, initialValue) {
  var arr = Array.prototype.slice.call(this);
  var res, startIndex;
  // 初始值不传怎么处理
  res = initialValue ? initialValue : arr[0];
  startIndex = initialValue ? 0 : 1;
  for(var i = startIndex; i < arr.length; i++) {
    res = fn.call(null, res, arr[i], i, this);
  }
  return res;
}

用ES5实现数组的filter方法

Array.prototype.filter = function (callback) {
    const res = []
    for (let i = 0; i < this.length; i++) {
        callback(this[i], i, this) && res.push(this[i])
    }
    return res
}

用ES5实现数组的findIndex方法

Array.prototype.findIndex = function (callback) {
    for (let i = 0; i < this.length; i++) {
        if (callback(this[i], i, this)) {
            return i
        }
    }
    return -1
}

实现call/apply

//实现apply只要把下一行中的...args换成args即可 
Function.prototype.myCall = function(context = window, ...args) {
  // 利用this的上下文特性。
  let func = this;
  let fn = Symbol("fn");
  context[fn] = func;

  //重点代码,利用this指向,相当于context.caller(...args)
  let res = context[fn](...args);
  
  delete context[fn];
  return res;
}

实现Object.assign方法

Object.prototype.assign = function (target, ...args) {
    if (target === null || target === undefined) {
        throw new TypeError('Cannot convert undefined or null to object')
    }
    target = Object(target)

    for (let nextObj of args) {
        for (let key in nextObj) {
            nextObj.hasOwnProperty(key) && (target[key] = nextObj[key])
        }
    }
    return target
}

实现Object.create方法

function create(proto) {
    function F() {};
    F.prototype = proto;
    F.prototype.constructor = F;
    
    return new F();
}

实现bind方法

// 对于普通函数,绑定this指向;对于构造函数,要保证原函数的原型对象上的属性不能丢失
Function.prototype.bind = function(context, ...args) {
    //this表示调用bind的函数
    let self = this;
    let fBound = function() {
        //this instanceof fBound为true表示构造函数的情况。如new func.bind(obj)
        return self.apply(
            this instanceof fBound ? this : context || window,
            args.concat(Array.prototype.slice.call(arguments))
        );
    }
    //保证原函数的原型对象上的属性不丢失
    fBound.prototype = Object.create(this.prototype);
    return fBound;
}

实现new关键字

// 创建一个全新的对象,这个对象的__proto__要指向构造函数的原型对象
// 执行构造函数,apply把this指向新对象
// 返回值为object类型则作为new方法的返回值返回,否则返回上述全新对象
function myNew(fn, ...args) {
    let instance = Object.create(fn.prototype);
    let res = fn.apply(instance, args);
    return typeof res === 'object' ? res: instance;
}

实现instanceof的作用

// 原型链的向上查找。
function myInstanceof(left, right) {
    let proto = Object.getPrototypeOf(left);
    while(true) {
        if(proto == null) return false;
        if(proto == right.prototype) return true;
        proto = Object.getPrototypeof(proto);
    }
}

实现单例模式

// 用闭包和Proxy属性拦截。
function proxy(func) {
    let instance;
    let handler = {
        construct(target, args) {
            if(!instance) {
                instance = Reflect.construct(func, args);
            }
            return instance;
        }
    }
    return new Proxy(func, handler);
}

实现数组的flat

// 递归处理
let result = [];
let fn = function(ary) {
  for(let i = 0; i < ary.length; i++) {
    let item = ary[i];
    if (Array.isArray(ary[i])){
      fn(item);
    } else {
      result.push(item);
    }
  }
}

// 用栈实现
function flattenWithStack(arr) {
    const stack = [...arr]; 
    const result = []; 
    while (stack.length > 0) {
        const currentElement = stack.pop(); 
        if (Array.isArray(currentElement)) {
            stack.push(...currentElement); 
        } else {
            result.unshift(currentElement); 
        }
    }
    return result;
}


// 用 reduce 实现数组的 flat 方法
function flatten(ary) {
    return ary.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
    }, []);
}

实现防抖功能

// 在定时器的时间范围内再次触发,则重新计时。
const debounce = (fn, delay) => {
  let timer = null;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
};

// react hook版
function useDebounce(fn, delay, dep = []) {
  const { current } = useRef({ fn, timer: null });
  useEffect(function () {
    current.fn = fn;
  }, [fn]);

  return useCallback(function f(...args) {
    if (current.timer) {
      clearTimeout(current.timer);
    }
    current.timer = setTimeout(() => {
      current.fn(...args);
    }, delay);
  }, dep)
}

实现节流功能

// 在定时器的时间范围内再次触发,则不予理睬,等当前定时器完成,才能启动下一个定时器。
const throttle = (fn, delay = 500) => {
  let flag = true;
  return (...args) => {
    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(this, args);
      flag = true;
    }, delay);
  };
};

用发布订阅模式实现EventEmit

class Subscribe {
    constructor() {
        //创建容器
        this.pond = [];
    }
    //向容器中增加方法,注意去重
    add(fn) {
        let pond = this.pond,
            isExist = false;
        //去重环节
        pond.forEach(item => item === fn ? isExist = true : null);
        !isExist ? pond.push(fn) : null;
    }
    remove(fn) {
        let pond = this.pond;
        pond.forEach((item, index) => {
            if(item === fn) {
                //提一下我在这里遇到的坑,这里如果写item=null是无效的
                //例子:let a = {name: funtion(){}};
                //let b = a.name;
                //这个时候操作b的值对于a的name属性是没有影响的
                pond[index] = null;
            }
        })
    }
    fire(...arg) {
        let pond = this.pond;
        for(let i = 0; i < pond.length; i++) {
            let item = pond[i];
            //如果itme为空了,最好把它删除掉
            if (item === null) {
                pond.splice(i, 1);
                // 如果用了splice要防止数组塌陷问题,
                // 即删除了一个元素后,后面所有元素的索引默认都会减1
                i--;
                continue;
            }
            item(...arg);
        }
    }
}

实现深拷贝

const clone = parent => {
  // 判断类型
  const isType =  (target, type) => `[object ${type}]` === Object.prototype.toString.call(target)

  // 处理正则
  const getRegExp = re => {
    let flags = "";
    if (re.global) flags += "g";
    if (re.ignoreCase) flags += "i";
    if (re.multiline) flags += "m";
    return flags;
  };

  const _clone = parent => {
    if (parent === null) return null;
    if (typeof parent !== "object") return parent;

    let child, proto;

    if (isType(parent, "Array")) {
      // 对数组做特殊处理
      child = [];
    } else if (isType(parent, "RegExp")) {
      // 对正则对象做特殊处理
      child = new RegExp(parent.source, getRegExp(parent));
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (isType(parent, "Date")) {
      // 对Date对象做特殊处理
      child = new Date(parent.getTime());
    } else {
      // 处理对象原型
      proto = Object.getPrototypeOf(parent);
      // 利用Object.create切断原型链
      child = Object.create(proto);
    }
    for (let i in parent) {
      // 递归
      child[i] = _clone(parent[i]);
    }
    return child;
  };
  return _clone(parent);
};

实现Promise

见:juejin.cn/post/712758…

使用ES5实现类的继承效果

ES5实现继承详情

  function Parent5 () {
    this.name = 'parent5';
    this.play = [1, 2, 3];
  }
  function Child5() {
    Parent5.call(this);
    this.type = 'child5';
  }
  Child5.prototype = Object.create(Parent5.prototype);
  Child5.prototype.constructor = Child5;

实现原生的AJAX请求

const ajax = {
    get(url, fn) {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', url, true)// 第三个参数异步与否
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                fn(xhr.responeText)
            }
        }
        xhr.send()
    },
    post(url, data, fn) {
        const xhr = new XMLHttpRequest()
        xhr.open('POST', url, true)
        xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded')
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                fn(xhr.responeText)
            }
        }
        xhr.send(data)
    }
}

实现一个柯里化函数

function curry(fn, args) {
    length = fn.length;

    args = args || [];

    return function() {

        var _args = args.slice(0),

            arg, i;

        for (i = 0; i < arguments.length; i++) {

            arg = arguments[i];

            _args.push(arg);

        }
        if (_args.length < length) {
            return curry.call(this, fn, _args);
        }
        else {
            return fn.apply(this, _args);
        }
    }
}


var fn = curry(function(a, b, c) {
    console.log([a, b, c]);
});

fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]

实现一个compose函数

//  函数嵌套调用执行顺序是先内后外。a(b(c(d()))) ====> d() -> c() -> b() -> a()
// `pipe` 函数与 `compose`函数的共同点是都返回“组合函数”,
//  区别则是执行的顺序不同,前者是从左向右执行,后者则是从右向左执行。
function compose(...funs) {
  if (funs.length === 0) {
    return arg => arg;
  }
  if (funs.length === 1) {
    return funs[0];
  }

  return funs.reverse().reduce((a, b) => (...arg) => b(a(arg)));
}

实现一个LRU缓存函数

class LRUCache {
  constructor(size) {
    this.size = size
    this.cache = new Map()
  }

  get(key) {
    const hasKey = this.cache.has(key)
    if (hasKey) {
      const val = this.cache.get(key)
      this.cache.delete(key)
      this.cache.set(key, val)
      return val
    } else {
      return -1
    }
  }

  put(key, val) {
    const hasKey = this.cache.has(key)
    if (hasKey) {
      this.cache.delete(key)
    }
    this.cache.set(key, val)
    if (this.cache.size > this.size) {
      this.cache.delete(this.cache.keys().next().value)
    }
  }

}

实现一个快速排序

var quickSort = function(arr) {
  if (arr.length <= 1) { return arr; }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < arr.length; i++){
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
};

实现一个倒计时

const useCountdown = (options) => {
  // 首次初始化数据,显示清除的数据
  const [timeInfo, setTimeInfo] = useState(options.showMillisecond);
  useEffect(() => {
    let timer = 0;

    function countdown() {
      const nowTime = Date.now();
      // 截止时间 - 当前时间 = 剩余时间
      const remainTime = (options.deadlineTime - nowTime) / 1000;
      // 剩余时间大于 0 才开始倒计时
      if (remainTime > 0) {
        // 未结束时直接定时下一次在执行判断 countdown
        timer = setTimeout(
          countdown,
          options.showMillisecond ? 100 : 1000 // 毫秒级则修改定时器时间
        );
      }
      setTimeInfo(remainTime);
    }

    // 开始倒计时
    countdown();

    return () => {
      // 清除定时器
      timer && clearTimeout(timer);
    };
  }, [options.deadlineTime, options.showMillisecond]);
  return timeInfo;
};

原生JS

原生JS灵魂之问(上)juejin.cn/post/684490…

原生JS灵魂之问(中)juejin.cn/post/684490…

原生JS灵魂之问(下)juejin.cn/post/684490…

关于JS中一些重要的api实现 juejin.cn/post/684490…

56个JS高级的手写 juejin.cn/post/702390…

一篇够用的TypeScript总结 juejin.cn/post/698172…