前端手写代码合集

一、用ES5实现数组的map方法

核心要点
1.回调函数的参数有哪些,返回值如何处理。
2.不修改原来的数组。
 /*
 var new_array = arr.map(function callback(currentValue[, index[, array]]) {
  // Return element for new_array
 }[, thisArg])
 */
 
 Array.prototype.myMap = function (fn, context) {
   let arr = Array.prototype.slice.call(this);
   let res = [];
   for (let i = 0; i < arr.length; i++) {
     res.push(fn.call(context, arr[i], i, arr));
   }
   return res;
 }

二、用ES5实现数组的reduce方法(累加器)

核心要点
1、初始值不传怎么处理
2、回调函数的参数有哪些,返回值如何处理。
 // array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
 Array.prototype.myReduce = function(fn, initialValue) {
   let arr = Array.prototype.slice.call(this);
   let total = initialValue ? initialValue : arr[0];
   let startIndex = initialValue ? 0 : 1;
   for (let i = startIndex; i < arr.length; i++) {
     total = fn.call(null, total, arr[i], i, this);
   }
   return total;
 }

【引申】用ES5实现数组的reduceRight方法

 Array.prototype.myReduceRight = function(fn, initialValue) {
   let arr = Array.prototype.slice.call(this);
   let len = arr.length;
   let total = initialValue ? initialValue : arr[len-1];
   let startIndex = initialValue ? len-1 : len-2;
   for (let i = startIndex; i >= 0; i--) {
     total = fn.call(null, total, arr[i], i, this);
   }
   return total;
 }

三、实现call/apply

思路: 利用this的上下文特性。
 // function.call(thisArg, arg1, arg2, ...)
 Function.prototype.myCall = function(context, ...args) {
   let func = this;
   let fn = Symbol('fn');
   context[fn] = func;
   let res = context[fn](...args);
   delete context[fn];
   return res;
 }
 
 Function.prototype.myApply = function(context, args) {
   let func = this;
   let fn = Symbol('fn');
   context[fn] = func;
   let res = context[fn](...args);
   delete context[fn];
   return res;
 }

四、实现Object.create方法(常用)

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
 // Object.create(proto[, propertiesObject])
 Object.myCreate = function(proto) {
   function F(){}
   F.prototype = proto;
   // F.prototype.constructor = F;
   return new F();
 }

五、实现bind方法

核心要点
1.对于普通函数,绑定this指向
2.对于构造函数,要保证原函数的原型对象上的属性不能丢失
当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。
 // function.bind(thisArg[, arg1[, arg2[, ...]]])
 Function.prototype.myBind = function (context, ...args) {
   let self = this;
   let fBound = function () {
     // this instanceof fBound:当作为构造函数时,this为实例,为true,指向this;当为普通函数,this为window,指向context
     return self.apply(this instanceof fBound ? this : context, args.concat(Array.prototype.slice.call(arguments)));
     // apply 如果第一个参数context是null或undifined,默认为window(严格模式下默认 context 是 undefined) 所以这里无需加判断
     // 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关键字

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

七、实现instanceof的作用

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
核心要点:原型链的向上查找。
 function myInstanceof (left, right) { // left:实例对象 right:构造函数
   let proto = Object.getPrototypeOf(left)
   while (proto) {
     if (proto === right.prototype) return true;
     proto = Object.getPrototypeOf(proto);
   }
   return false;
 }

八、实现单例模式

核心要点: 用闭包和Proxy属性拦截
function getSingle (func) {
  let instance = null;
  let handler = {
    construct: function (target, args) {
     	if (!instance) {
        instance = new func(...args);
        // instance = Reflect.construct(func, args);
      }
      return instance;
    }
  }
  return new Proxy(func, handler);
}
new Proxy(target, handler)

九、实现数组的flat

var arr = [1,[2,[3,{a:4}],5,6]]
// 实现:flatten(arr) // [1, 2, 3, {a:4}, 5, 6]

// 方法一:直接调用flat
function flatten(arr) {
  return arr.flat(Infinity);
}

// 方法二:利用JSON 和 正则
function flatten(arr) {
  var str = JSON.stringify(arr);
  str = str.replace(/(\[|\])/g, ''); // 替换掉[]
  str = '[' + str + ']';
  return JSON.parse(str);
}

// 方法三:递归处理
function flatten(arr) {
  let res = [];
  for (let item of arr) {
    if (Array.isArray(item)) {
      res = res.concat(flatten(item));
    }
    else res.push(item);
  }
  return res;
}

// 方法四:利用 reduce
function flatten(arr) {
  return arr.reduce((total, value) => {
    return total.concat(Array.isArray(value) ? flatten(value) : value);
  }, [])
}

// 方法五:扩展运算符
function flatten(arr) {
  while (arr.some(Array.isArray)) {
    arr = [].concat(...arr);
  }
  return arr;
}
arr.some(callback(element[, index[, array]])[, thisArg])

【拓展】实现数组去重

var arr = [1,2,3,1,1,4]
function fun(arr) {
  return Array.from(new Set(arr)) // Array.from 把set转为数组
}
已知如下数组:var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];

十、实现防抖功能

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

十一、实现节流功能

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

十二、实现 generator 异步自动执行器

function takeLongTime(n) {
    console.log(n)
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

var doIt = function *() {
    const time1 = 300;
    const time2 = yield takeLongTime(time1); // 返回一个promise
    const result = yield takeLongTime(time2);
    console.log(`result is ${result}`);
}

// 自动执行器
function run(gen) {
  let g = gen();
  function _next(data) {
    let temp = g.next(data); // data赋值给上一个执行完的yield语句左边的变量
    if (temp.done) { // 已经执行完
      console.log('done')
    } else { // 未执行完
      temp.value.then((data) => {
        _next(data)
      })
    }
  }
}

十三、实现async函数

/*
async function fn(args){
  // ...
}
*/

function fn(args) {
  function _run(gen) {
    return new Promise((resolve, reject) => {
      let g = gen();
      function _next(data) {
        let temp = g.next(data);
        if (temp.done) {
          return resolve(temp.value);
        }
        temp.value.then((data) => {
          _next(data);
        })
      }
      _next();
    })
  }
  return _run(function *() {
    // ...
  })
}


function fn(args){
  function _spawn(genF) {
    return new Promise(function(resolve, reject) { // 返回一个promise
      var gen = genF();
      function step(nextF) {
        try {
          var next = nextF();
        } catch(e) {
          return reject(e);
        }
        if(next.done) {
          return resolve(next.value);
        }
        Promise.resolve(next.value).then(function(v) {
          step(function() { return gen.next(v); });
        }, function(e) {
          step(function() { return gen.throw(e); });
        });
      }
      step(function() { return gen.next(undefined); });
    });
  }
  return _spawn(function*() {
    // ...
  });
}

十四、实现trim函数

trim() 方法用于删除字符串的头尾空格
// str.trim()
String.prototype.myTrim = function () {
  let str = this;
  str.replace(/^\s+|\s+$/g, '')
}
\s: space, 空格 +: 一个或多个 ^: 开始,^\s,以空格开始 $: 结束,\s$,以空格结束 |:或者 /g:global, 全局

十五、格式化数字(每三位加逗号)

var num = 132435234.123
// 方法一
function formatNum(num) {
  return num.toLocaleString()
}
// 方法二
function formatNum(num) {
  var res = [], counter = 0;
  let numArr = (num || 0).toString().split('.');
  // 格式化小数点左边
  res[0] = '';
  for (let i = numArr[0].length-1; i >= 0; i--) {
    counter++;
    res[0] = numArr[0].charAt(i) + res[0];
    if (counter % 3 === 0 && i !== 0) res[0] = ',' + res[0]; // 排除刚好为3位的情况
  }

  // 格式化小数点右边
  if (numArr[1]) {
    res[1] = '', counter = 0;
    for (let i = 0; i < numArr[1].length; i++) {
      counter++;
      res[1] = res[1] + numArr[1].charAt(i);
      if (counter % 3 === 0 && i !== numArr[1].length-1) res[1] = res[1] + ','; // 排除刚好为3位的情况
    }
  }
  return res.join('.');
}

// 方法三
function formatNum(num) {
  var res = [];
  let numArr = (num || 0).toString().split('.');
  res.push(numArr[0].replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')); // 格式化小数点左边
  if (numArr[1]) {
    res.push(numArr[1].replace(/(\d)(?=(?:\d{3})+)/g, '$1,')); // 格式化小数点右边
  }
  return ;
}
\b:匹配单词边界 如1234551277.8945511,匹配并替换结果为 ,1234551277,.,894551, \B:匹配出\b之外的 如1234551277.8945511,匹配并替换结果为 1,2,3,4,5,5,1,2,7,7.8,9,4,5,5,1 \B(?=):匹配\B,同时符合后面条件的 \d{3}:匹配三个数字 \d{3}+:多次匹配 \d{3})+.:匹配到的位置后面存在多个\d{3}且后面刚好接一个.
function formatNumbers(num) {
    let numStr = ''+num;
    let regExp = /\B(?=(\d{3})+\.)/g;
    return (numStr.replace(regExp, ','));
}

十六、冻结对象

var constantize = (obj) => {
	Object.freeze(obj);
  Object.keys(obj).forEach((key)=>{
    if (typeof obj[key] === 'object') constantize(obj[key])
  })
}

十七、手写原生ajax

var xhr;
if(window.XMLHttpRequest){
    xhr = new XMLHttpRequest();
}else{
    xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
// const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = () => {
  if (xhr.readyState === 4) {
    console.log('请求响应完毕');
    if (xhr.status >= 200 && xhr.status < 300) {
      console.log('响应成功');
      let res = xhr.responseTest;
      console.log(res);
    } else if (xhr.status > 400) {
      console.log('响应失败')
    }
  }
}
xhr.ontimeout = (e) => {
  console.log('请求超时')
}
xhr.timeout = 3000;
xhr.send();

十八、函数柯里化

// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;


function add() {
  let _args = Array.prototype.slice.call(arguments);
  let _adder = function () { // 收集所有arguments
    _args.push(...arguments);
    return _adder;
  }
  _adder.toString = function () { // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    return _args.reduce((total, num) => {
      return total + num;
    })
  }
  return _adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9

十九、用 es5 实现 const

// 数据描述符实现(无法抛出错误)
function myConst(key, value) {
    Object.defineProperty(window, key, {
        value: value,
        writable: false
    })
}
// 存取描述符
function myConst(key, value) {
    window[key] = value;
    Object.defineProperty(window, key, {
        get: function () {
            return value;
        },
        set: function (newValue) {
          throw new TypeError('Assignment to constant variable.');
        }
    })
}

二十、大数相加

function bigNumAdd(num1, num2) { // 传入字符串
    let arr1 = num1.split('').reverse();
    let arr2 = num2.split('').reverse();
    let maxLen = Math.max(arr1.length, arr2.length);
    let temp1, temp2, sum; //当前位值、当前和
    let temp = 0; // 进位
    let res = []; // 结果
    for (let i = 0; i < maxLen; i++) {
        temp1 = arr1[i] || 0;
        temp2 = arr2[i] || 0;
        sum = Number(temp1) + Number(temp2) + temp;
        if (sum > 9) {
            temp = 1;
            res.push(sum%10);
        } else {
            temp = 0;
            res.push(sum);
        }
    }
    return res.reverse().join('');
}
 
你觉得这篇文章怎么样?
YYDS
比心
加油
菜狗
views

Loading Comments...