前端高频手写代码(带注释)

337 阅读5分钟

代码仓库👇

代码面试练习

call,apply,bind

Function.prototype.myCall1 = function(context) {
    // 判断当前调用call的是否是函数,不是的话就抛出错误
    if (typeof this !== 'function') { 
        throw new TypeError('Error') 
    }
    // 如果没有传或传的值为空 context 指向 window
    context = context || window
    // 创造一个独一无二的字符串
    let fn = Symbol(context)
    // 给context添加一个方法,该方法保存着调用call的函数
    context.fn = this 
    // 处理参数 去除第一个参数context,其它参数传入fn函数,返回一个新的参数数组
    let arg = [...arguments].slice(1)
    // 执行fn函数,也就是执行调用call的函数
    const result = context.fn(...arg) 
    // 删除该属性
    delete context.fn
    // 返回函数执行的结果
    return result
}


Function.prototype.myApply = function(context) { 
    if (typeof this !== 'function') { 
        throw new TypeError('Error') 
    } 
    context = context || window
    let fn = Symbol(context)
    context.fn = this
    // apply和call的区别是apply的第二个参数是数组
    let arg = [...arguments].slice(1) 
    const result = context.fn(arg)
    delete context.fn 
    return result 
}


Function.prototype.bind = function(context) {
    if (typeof this !== 'function') {
      throw new TypeError('Error');
    }

  var aArgs = [...arguments].slice(1),
  fToBind = this,
  fNOP    = function() {},
  fBound  = function() {
  // 因为返回了一个函数,我们可以 new F(),所以需要检测new
  // 如果当前函数的this指向的是构造函数中的this 则判定为new方式调用
  // 此时我们需要忽略传入的this也就是context
  // 因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2),所以我们需要将两边的参数拼接起来
    return fToBind.apply(this instanceof fBound
      ? this
      : context,
      // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
      aArgs.concat(Array.prototype.slice.call(arguments)));
  };

    // 维护原型关系
    if (this.prototype) {
      fNOP.prototype = this.prototype;
    }
    fBound.prototype = new fNOP();

    return fBound;
};

Promise家族

// Promise.all
// all的原理,只能使用Promise,不能使用promise.resolve
Promise.all = function(values){
    return new Promise((resolve,reject)=>{
        let results = []; // 结果数组
        let i = 0;
        let processData = (value,index)=>{
            results[index] = value;
            // 当成功的个数 和 当前的参数个数相等就把结果抛出去
            if(++i === values.length){
                resolve(results);
            }
        }
        for(let i = 0 ; i< values.length;i++){
            let current = values[i]; // 拿到数组中每一项
            // 判断是不是一个promise
            if((typeof current === 'object' &&  current !==null)|| typeof current == 'function'){
                // 如果是promise
                if(typeof current.then == 'function'){
                    // 就调用这个promise的then方法,把结果和索引对应上
                    // 如果任何一个失败了返回的proimise就是一个失败的promise
                    current.then(y=>{
                        processData(y,i);
                    },reject)
                }else{
                    processData(current,i);
                }
            }else{
                processData(current,i);
            }
        }
    });
}

// 可使用Promise和Promise.resolve和Promise.then方法来写
function promiseAll(promises) {
  return new Promise(function(resolve, reject) {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('arguments must be an array'));
    }
    var resolvedCounter = 0;
    var promiseNum = promises.length;
    var resolvedValues = new Array(promiseNum);
    for (let i = 0; i < promiseNum; i++) {
      // 使用Promise.resolve判断当前promise的状态是resolve还是reject
        Promise.resolve(promises[i]).then(function(value) {
          resolvedCounter++
          resolvedValues[i] = value
          if (resolvedCounter == promiseNum) {
            return resolve(resolvedValues)
          }
        }, function(reason) {
          return reject(reason)
        })
    }
  })
}
// promise.race
function promiseRace(promises) {
  if (!Array.isArray(promises)) {
      throw new Error("promises must be an array")
  }
  return new Promise(function (resolve, reject) {
      promises.forEach(p =>
          Promise.resolve(p).then(data => {
              resolve(data)
          }, err => {
              reject(err)
          })
      )
  })
}
// promise.catch
Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}
// Promise.allSettled
const formatSettledResult = (success, value) =>
  success
    ? { status: "fulfilled", value }
    : { status: "rejected", reason: value };

Promise.allSettled = function(iterators) {
  const promises = Array.from(iterators);
  const num = promises.length;
  const settledList = new Array(num);
  let settledNum = 0;

  return new Promise(resolve => {
    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(value => {
          settledList[index] = formatSettledResult(true, value);
          if (++settledNum === num) {
            resolve(settledList);
          }
        })
        .catch(error => {
          settledList[index] = formatSettledResult(false, error);
          if (++settledNum === num) {
            resolve(settledList);
          }
        });
    });
  });
};

JSON.stringify

// 不带`replacer`和`space`参数的简化版本
// 不考虑循环引用
if (!window.JSON) {
    window.JSON = {
        parse: function(jsonStr) {
            return eval('(' + jsonStr + ')');
        },
        stringify: function(jsonObj) {
            var result = '',
                curVal;
            if (jsonObj === null) {
                return String(jsonObj);
            }
            switch (typeof jsonObj) {
                case 'number':
                case 'boolean':
                    return String(jsonObj);
                case 'string':
                    return '"' + jsonObj + '"';
                case 'undefined':
                case 'function':
                    return undefined;
            }

            switch (Object.prototype.toString.call(jsonObj)) {
                case '[object Array]':
                    result += '[';
                    for (var i = 0, len = jsonObj.length; i < len; i++) {
                        curVal = JSON.stringify(jsonObj[i]);
                        result += (curVal === undefined ? null : curVal) + ",";
                    }
                    if (result !== '[') {
                        result = result.slice(0, -1);
                    }
                    result += ']';
                    return result;
                case '[object Date]':
                    return '"' + (jsonObj.toJSON ? jsonObj.toJSON() : jsonObj.toString()) + '"';
                case '[object RegExp]':
                    return "{}";
                case '[object Object]':
                    result += '{';
                    for (i in jsonObj) {
                        if (jsonObj.hasOwnProperty(i)) {
                            curVal = JSON.stringify(jsonObj[i]);
                            if (curVal !== undefined) {
                                result += '"' + i + '":' + curVal + ',';
                            }
                        }
                    }
                    if (result !== '{') {
                        result = result.slice(0, -1);
                    }
                    result += '}';
                    return result;

                case '[object String]':
                    return '"' + jsonObj.toString() + '"';
                case '[object Number]':
                case '[object Boolean]':
                    return jsonObj.toString();
            }
        }
    };
}

New操作符

function New(func) {
    // 创建一个全新的对象
    var res = {};
    // 链接到原型
    if (func.prototype !== null) {
        res.__proto__ = func.prototype;
    }
    // 执行函数,指定this指向创建的对象res
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
    // 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error)
    // 那么new表达式中的函数调用将返回该对象引用
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
        return ret;
    }
    return res;
}
var obj = New(A, 1, 2);
// equals to
var obj = new A(1, 2);

InstanceOf

// instanceof`可以正确的判断对象的类型,
// 因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype
function myInstanceof(left, right) {
    if (typeof left !== 'object' || left === null) return false // 基础类型一律为 false
    let proto = Object.getPrototypeOf(left) // 获取对象的原型
    while(true) {
    	if (proto === null) return false
        if (proto === right.prototype) return true
        proto = Object.getPrototypeOf(proto)
    }
}

简单深拷贝

function clone(target, map = new Map()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        for (const key in target) {
            cloneTarget[key] = clone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
};

函数柯里化

function curry(fn, args) {
    // 获取传进来的函数的参数个数
    var length = fn.length;
    // 获取初始柯里化的参数数组
    var args = args || [];
    return function(){
    // 柯里化的参数参数合并
        newArgs = args.concat(Array.prototype.slice.call(arguments));
        if (newArgs.length < length) {
            return curry.call(this,fn,newArgs);
        }else{
            return fn.apply(this,newArgs);
        }
    }
}

function multiFn(a, b, c) {
    return a * b * c;
}

var multi = curry(multiFn);

multi(2)(5)(4);
multi(2,5,4);
multi(2)(5,4);
multi(2,5)(4);

// 使用ES6方式
const curry = (fn, arr = []) => (...args) => (
  arg => arg.length === fn.length
    ? fn(...arg)
    : curry(fn, arg)
)([...arr, ...args])

let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4)
curryTest(1,2)(4)(3)
curryTest(1,2)(3,4)

节流函数

function throttle(fn) {
  let canRun = true; // 通过闭包保存一个标记
  return function () {
    if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
    canRun = false; // 立即设置为false
    setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
      fn.apply(this, arguments);
      // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。
      // 当定时器没有执行的时候标记永远是false,在开头被return掉
      canRun = true;
    }, 500);
  };
}
function sayHi(e) {
  console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));

防抖函数

// func是用户传入需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {
  // 缓存一个定时器id
  let timer = 0
  // 这里返回的函数是每次用户实际调用的防抖函数
  // 如果已经设定过定时器了就清空上一次的定时器
  // 开始一个新的定时器,延迟执行用户传入的方法
  return function(...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}

寄生组合式继承

function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}

function Child(value) {
  Parent.call(this, value)
}
// 不必为了指定子类型的原型而调用父类型的构造函数,我们所需要的无非就是父类型原型的一个副本而已。
Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true
  }
})

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true

EventEmitter

class EventEmitter {
	constructor() {
		this.queue = {} //可触发多次的事件
		this.onceQueue = {} //只能触发一次的事件
	}
	on(event, fn) {  //监听事件,可以触发多次
		if (!this.queue[event]) this.queue[event] = []
		this.queue[event].push(fn)
	}
	once(event, fn) {   //监听事件,只能触发一次
		if (!this.onceQueue[event]) {
			this.onceQueue[event] = {
				fns: [],
				hasFired: false
			}
		}
		this.onceQueue[event].fns.push(fn)
	}
	fire() {  // 触发指定的事件
        // 取得事件名称
		const event = [].shift.call(arguments), 
                // 取得该事件里所有的回调函数(可以触发多次的事件)
                // 取得该事件里所有的回调函数(只能触发一次的事件)
			fns = this.queue[event],  
			onceFns = this.onceQueue[event]  

		if (fns && fns.length != 0) {
			let i = 0,fn
			while (fn = fns[i++]) {
				fn.apply(this, arguments)
			}
		}
		if (onceFns && !onceFns.hasFired) {
			let i = 0,fn
			while (fn = onceFns.fns[i++]) {
				fn.apply(this, arguments)
			}
			this.onceQueue[event].hasFired = true
		}
	}
	off(event, fn = null) { //可移除特定事件里的某个回调函数或者所有回调函数
		const fns = this.queue[event]
		if (!fns || fns.length == 0) return

		if (fn) { //移除该事件特定的回调
			this.queue[event] = fns.filter(item => {
				return item !== fn
			})
		} else { //移除该事件所有的回调
			this.queue[event] = []
		}
	}
}

大数相加

function add(a ,b){
   //取两个数字的最大长度
   let maxLength = Math.max(a.length, b.length);
   //用0去补齐长度
   a = a.padStart(maxLength , 0);//"0009007199254740991"
   b = b.padStart(maxLength , 0);//"1234567899999999999"
   //定义加法过程中需要用到的变量
   let t = 0;
   let f = 0;   //"进位"
   let sum = "";
   for(let i=maxLength-1 ; i>=0 ; i--){
      t = parseInt(a[i]) + parseInt(b[i]) + f;
      f = Math.floor(t/10);
      sum = t%10 + sum;
   }
   if(f == 1){
      sum = "1" + sum;
   }
   return sum;
}

控制并发请求数量

// 原理:使用一个队列维护所有的请求,然后使用async/await或者promise对请求进行控制
// 当前面的请求完成就从队列中出队下一个请求
class LimitResquest {
  constructor(limit) {
    this.limit = limit
    this.currentSum = 0
    this.requests = []
  }
 
  request (reqFn) {
    if (!reqFn || !(reqFn instanceof Function)) {
      console.error('当前请求不是一个Function', reqFn)
      return
    }
    this.requests.push(reqFn)
    if (this.currentSum < this.limit) {
      this.run()
    }
  }
 
  async run() {
    try {
      ++this.currentSum
      const fn = this.requests.shift()
      await fn()
    } catch(err) {
      console.log('Error', err)
    } finally {
      --this.currentSum
      if (this.requests.length > 0) {
        this.run()
      }
    }
  }
}

let a = () => new Promise((resolve) => {
  setTimeout(() => {resolve(1)}, 1000)
}).then((data) => console.log(data))
 
 
let b = () => new Promise((resolve) => {
  setTimeout(() => {resolve(2)}, 1000)
}).then((data) => console.log(data))
 
 
let c = () => new Promise((resolve) => {
  setTimeout(() => {resolve(3)}, 1000)
}).then((data) => console.log(data))
 
 
let d = () => new Promise((resolve) => {
  setTimeout(() => {resolve(4)}, 1000)
}).then((data) => console.log(data))
 // 可以调整限制数查看控制效果
let limitResquest = new LimitResquest(2)
limitResquest.request(a)
limitResquest.request(b)
limitResquest.request(c)
limitResquest.request(d)
limitResquest.request(a)
limitResquest.request(b)
limitResquest.request(c)
limitResquest.request(d)

遍历嵌套对象

const obj = {
    a:{
        b:{
            c:666
        }
    }
}
 
var str = 'a.b.c';
const getData = ()=>{
  var newArr =  str.split('.').reduce((o,s)=>{ return  o[s]},obj)
    return newArr
}
console.log( getData());

// 第二种实现方式
const obj = {
    a:{
        b:{
            c:666
        }
    }
}
 
var str = 'a.b.c';
const getData=(obj,str)=>{
 str.split('.').forEach(element =>{ 
     obj= obj[element]
})    
return obj;
}
console.log(getData(obj,str));