前端面试(三):前端高频手撕代码

前言

在前端面试的时候,我们经常会遇到手撕代码题,但是如果我们没有用过手撕的功能,或者没有接触过此类的算法,写的时候可能会抓耳挠腮但下不了笔。这里按照专题整理了一些前端常见的手撕代码考题,希望大家找工作都大顺大利。

在看具体的题目前,我们需要看一下笔试或面试时可能会用到的输入输出格式:

acm格式输入输出

js 复制代码
// 读取输入的ACM格式数据

const readline = require('readline');

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

rl.on('line', (line) => {
// 处理输入的每一行
const input = line.split(' ').map(Number);
// 假设我们要计算两个数的和
const sum = input[0] + input[1];
// 输出结果
console.log(sum);
});

// 需要时可以关闭readline接口
// rl.close();

力扣模式输入输出

js 复制代码
/**
 * @param {Array<Function>} functions
 * @return {Promise<any>}
 */
var promiseAll = function(functions) {
    
};
ts 复制代码
type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
type Fn = (...args: JSONValue[]) => void

function cancellable(fn: Fn, args: JSONValue[], t: number): Function {
    
};

上下文和原型链

手撕instanceof

instanceof的用法是: 实例A instanceof 原型B, 如果B在A的原型链上,则表达式为true,否则为false。

实例的原型是指定在实例的_proto属性上的,可以用A._proto进行访问。

原型则是绑定在原型对象的prototype属性上的,可以用B.prototye进行访问。

js 复制代码
const myInstanceOf=(Left,Right)=>{
  if(!Left){
    return false
  }
  while(Left){
    if(Left.__proto__===Right.prototype){
      return true
    }else{
      Left=Left.__proto__
    }
  }
  return false
}

//验证
console.log(myInstanceOf({},Array));  //false

手撕call, bind, apply

call :fn.call(context, ...args) 支持将函数的上下文绑定到context,并以参数形式 传入函数运行所需的参数,且立即执行函数

js 复制代码
function foo(x,y){
  console.log(this.a,x+y);
}

const obj={
  a:1
}

Function.prototype.myCall=function(context,...args){
  if(typeof this !== 'function')  return new TypeError('is not a function')
  const fn = Symbol('fn') //使用Symbol尽可能降低myCall对其他的影响
  context[fn] = this  //this指向foo
  const res = context[fn](...args)  //解构,调用fn
  delete context[fn]  //不要忘了删除obj上的工具函数fn
  return res  //将结果返回
}

//验证
foo.myCall(obj,1,2)   //1,3

apply :fn.apply(context, args) 支持将函数的上下文绑定到context,并以参数数组 形式传入函数运行所需的参数,且立即执行函数

js 复制代码
function foo(x,y){
  console.log(this.a,x+y);
}

const obj={
  a:1
}

Function.prototype.myApply=function(context,args){
  if(typeof this !== 'function')  return new TypeError('is not a function')
  const fn = Symbol('fn') //使用Symbol尽可能降低myCall对其他的影响
  context[fn] = this  //this指向foo
  const res = context[fn](...args)  //解构,调用fn
  delete context[fn]  //不要忘了删除obj上的工具函数fn
  return res  //将结果返回
}

//验证
foo.myApply(obj,1,2)   //1,3

bind :bind和call,apply的区别是会返回一个新的函数,接收零散的参数

需要注意的是,官方bind的操作是这样的:

  • 当new了bind返回的函数时,相当于new了foo,且new的参数需作为实参传给foo
  • foo的this.a访问不到obj中的a
js 复制代码
function foo(x,y,z){
  this.name='zt'
  console.log(this.a,x+y+z);
}

const obj={
  a:1
}

Function.prototype.myBind=function(context,...args){
  if(typeof this !== 'function')  return new TypeError('It is not a function');
  context = context || window; // 上下文环境
  const _this = this; // 当前的函数的上下文this
  return function F(...arg) {
    //判断返回出去的F有没有被new,有就要把foo给到new出来的对象
    if (this instanceof F) {
       return new _this(...args, ...arg);
    } else {
       _this.call(this, ...args, ...arg);
    }
  }
}

//验证
const bar=foo.myBind(obj,1,2)
console.log(new bar(3));   //undefined 6  foo { name: 'zt' }

对象

实现浅拷贝

浅拷贝:只拷贝最外面一层数据;更深层次的对象,只拷贝引用; 这个链接讲的更加清楚哟

实现深拷贝

深拷贝:只拷贝最外面一层数据;拷贝多层数据;每一层级别的数据都会拷贝;这里展示一个层层拷贝的写法。

js 复制代码
//utils.js
//...
const deepCopy= (obj = {}) => {
        //变量先置空
        let newobj = null;

        //判断是否需要继续进行递归
        if (typeof (obj) == 'object' && obj !== null) {
            newobj = obj instanceof Array ? [] : {};
            //进行下一层递归克隆
            for (var i in obj) {
                newobj[i] = deepCopy(obj[i])
            }
            //如果不是对象直接赋值
        } else newobj = obj;
        console.log(newobj)
        return newobj;
    }
   

判断两个值是否相符

判断两个值是否相等,值可能是基本数据类型,也可能是对象或者数组,值相等视为===相等

js 复制代码
function compare(data1, data2) {
  if (typeof data1 != typeof data2) {
    return false;
  }

  if (typeof data1 != "object" && typeof data1 == typeof data2) {
    return data1 === data2;
  }

  if ((!data1 instanceof Array && data2 instanceof Array) || (data1 instanceof Array && !data2 instanceof Array)) {
    return false;
  } else if (data1 instanceof Array  && data2 instanceof Array) {
    if (data1.length == data2.length) {
      for (let i = 0; i < data1.length; i++) {
        if (!compare(data1[i], data2[i])) {
          return false;
        }
      }
    } else {
      return false;
    }
  } else {
      if (Object.keys(data1).length == Object.keys(data2).length) {
      for (let key in data1) {
        if (!data2[key] || !compare(data1[key], data2[key])) {
          return false;
        }
      }
    } else {
      return false;
    }
  }
  return true;
}

实现new方法

js 复制代码
function myNew(Fn, ...args){
    let newObj = {};
    if(Fn.prototype){
        newObj.__proto__ = Fn.prototype;
    }
    let result = Fn.apply(newObj, args);

    // 如果new的结果是对象则返回这个对象,否则直接返回这个新创建的对象
    if((typeof result === 'object' && result !== null) || typeof result === 'function'){
        return result;
    }
    return newObj;
}

实现Object.create()

js 复制代码
// proto 可以是object 或者function
Object.myCreate = function (proto, defineProperties){
    if((typeof proto === 'object' && proto !== null) || typeof proto === 'function'){
        let obj = {};

        // obj.__proto__ = proto;
        Object.setPrototypeOf(obj, proto);
        Object.defineProperty(obj, defineProperties);
        return obj;
    }else {
        throw new TypeError('类型错误');
    }
}

实现Object.assign()

js 复制代码
Object.myAssign = function (newObj, oldObj){
    if((typeof proto === 'object' && proto !== null) || typeof proto === 'function'){
        let obj = {};

        // obj.__proto__ = proto;
        Object.setPrototypeOf(obj, proto);
        Object.defineProperty(obj, defineProperties);
        return obj;
    }else {
        throw new TypeError('类型错误');
    }
}

数组

数组去重

js 复制代码
// 方法一
function unique(arr) {
    for(let i = 0; i < arr.length; i++) {
      for(let j = i + 1; j < arr.length; j++) {
          if (arr[i] == arr[j]) {
              arr.splice(j, 1);
          }
      }
    }
    
    return arr;
}

// 方法二
function unique(arr) {
    let myset = new Set();
    arr.forEach(item => {
        myset.add(item);
    })
    
    return Array.from(myset);
}


// 方法三
function unique(arr) {
  return arr.filter(function(item, index, array) {
    return arr.indexOf(item) === index
  })
}

const arr = [1,1,'true','true',true,true,15,15,false,false];
console.log(unique(arr)) // [ 1, 'true', true, 15, false ]

数组快速排序

js 复制代码
function quickSort(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));

};

let arr = [1,3,4,2,6,3,5];

console.log(quickSort(arr));

实现Array.prototype.reduce

js 复制代码
if (!Array.prototype.myReduce) {
  Array.prototype.myReduce = function(callback, initialValue) {
    const array = this;
    let accumulator = initialValue !== undefined ? initialValue : array[0];
    let index = initialValue !== undefined ? 0 : 1;
 
    while (index < array.length) {
      accumulator = callback(accumulator, array[index], index, array);
      index++;
    }
 
    return accumulator;
  };
}
 
// 示例使用:
const array1 = [1, 2, 3, 4];
const sum = array1.myReduce((accumulator, currentValue) => accumulator + currentValue, 0);
 
console.log(sum); // 输出:10

实现Array.prototype.flat(数组扁平化)

js 复制代码
Array.prototype.myFlat = function(arr){
  let res = [];
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      res = res.concat(flatten(arr[i]));
    } else {
      res.push(arr[i]);
    }
  }
  return res;
};

let arr = ['a',['b',['c']]];
console.log(arr.myFlat()); // [ 'a', 'b', 'c' ]

实现Array.prototype.forEach

js 复制代码
Array.prototype.myForEach = function (fn) {
  for (let i = 0; i < this.length; i++) {
    fn(this[i], i, this);
  }
};

const arr = [1, 2, 3];
arr.forEach((item, index, arr) => {
  console.log(item, index, arr);
});

实现Array.prototype.map

js 复制代码
Array.prototype.myMap = function (fn, thisArg) {
  const res = [];
  for (let i = 0; i < this.length; i++) {
    const mappedValue = fn.call(thisArg, this[i], i, this);
    res.push(mappedValue);
  }
  return res;
};

const arr = [1, 2, 3];
const douleArr = arr.myMap((value,index,array) => value * 2);
console.log(douleArr);

实现Array.prototype.filter

js 复制代码
const arr = ['a', 'b', 'c', 'd', 'e']

Array.prototype.myFilter = function(item,start) {
  if (start < 0) { start += this.length}
  for (let i = start; i < this.length; i++) {
    if (this[i] === item){
      return true;
    }
  }
  return false;
}

//验证
const flag = arr.myFilter('c',3)  //查找的元素,从哪个下标开始查找
console.log(flag); //false

实现Array.prototype.includes

js 复制代码
const arr = ['a', 'b', 'c', 'd', 'e']

Array.prototype.myIncludes = function(item,start) {
  if (start < 0) { start += this.length}
  for (let i = start; i < this.length; i++) {
    if (this[i] === item){
      return true;
    }
  }
  return false;
}

//验证
const flag = arr.myIncludes('c',3)  //查找的元素,从哪个下标开始查找
console.log(flag); //false

实现Array.prototype.fill

js 复制代码
Array.prototype.myFill = function (value,start,end) {
  if(!start && start!==0){
    start=0
  }
  end = end || this.length
  for(let i = start; i < end; i++){
    this[i] = value
  }
  return this
}

//验证
const arr=new Array(7).myFill('hh',null,3)  //往数组的某个位置开始填充到哪个位置,左闭右开
console.log(arr);   //[ 'hh', 'hh', 'hh', <4 empty items> ]

实现Array.prototype.join

js 复制代码
Array.prototype.myJoin = function (s = ',') {
  let str = '';
  for(let i = 0; i < this.length; i++) {
      str += this[i];
      str += ',';
  }
}

实现Array.prototype.find

js 复制代码
Array.prototype.my_find = function (callback) { 
    for (let i = 0; i < this.length; i++) { 
        if(callback(this[i], i, this)){
            return this[i] 
        } 
    } 
    
    return undefined 
} 


//验证 
let j = arr.myFind((item, index, arr) => { return item.age > 19 }) console.log(j); //{ name: 'cc', age: 21 }

实现Array.prototype.findIndex

js 复制代码
const arr = [
  { name: 'zt', age: 18 },
  { name: 'aa', age: 19 },
  { name: 'bb', age: 18 },
  { name: 'cc', age: 21 },
]

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


let j = arr.myFindIndex((item, index, arr) => {  
  return item.age > 19
})
console.log(j);  //3

实现Array.prototype.some

js 复制代码
const arr = [
  { name: 'zt', age: 18 },
  { name: 'aa', age: 19 },
  { name: 'bb', age: 18 },
  { name: 'cc', age: 21 },
]

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

//验证
const flag = arr.mySome((item, index, arr) => {
  return item.age > 20
})
console.log(flag);  //true

实现Array.prototype.every

js 复制代码
const arr = [
  { name: 'zt', age: 18 },
  { name: 'aa', age: 19 },
  { name: 'bb', age: 18 },
  { name: 'cc', age: 21 },
]

Array.prototype.myEvery = function (callback) {
  for (let i = 0; i < this.length; i++) {
    if(!callback(this[i], i, this)){
      return false
    }
  }
  return true
}

//验证
const flag = arr.my_every((item, index, arr) => {
  return item.age > 16
})
console.log(flag);  //true

字符串

字符串下划线和驼峰相互转换

js 复制代码
//方式一:操作字符串数组
function transformStr2Hump1(str) {
    if(str == null) {
        return "";
    }
    var strArr = str.split('-');
    for(var i = 1; i < strArr.length; i++) {
        strArr[i] = strArr[i].charAt(0).toUpperCase() + strArr[i].substring(1);
    }
    return strArr.join('');
}

//方式二:操作字符数组
function transformStr2Hump2(str) {
    if(str == null) {
        return "";
    }
    var strArr  =str.split('');
    for(var i = 0; i < strArr.length; i++) {
        if(strArr[i] == "-"){
            //删除-
            strArr.splice(i, 1);
            //将该处改为大写
            if(i < strArr.length) {
                strArr[i] = strArr[i].toUpperCase();
            }
        }
    }
    return strArr.join("");
}

//方式三:利用正则
function transformStr2Hump3(str) {
    if(str == null) {
        return "";
    }
    var reg = /-(\w)/g;//匹配字母或数字或下划线或汉字
    return str.replace(reg, function($0, $1) {
        return $1.toUpperCase();
    })
}

函数

函数柯里化

js 复制代码
const my_curry = (fn, ...args) => 
  args.length >= fn.length 
  ? fn(...args) 
  : (...args1) => curry(fn, ...args, ...args1);
   
function adder(x, y, z) {
    return x + y + z;
}
const add = my_curry(adder);
console.log(add(1, 2, 3));  //6
console.log(add(1)(2)(3));  //6
console.log(add(1, 2)(3));  //6
console.log(add(1)(2, 3));  //6

同步异步

实现Promise类

js 复制代码
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
/**
 * Promise构造函数
 * excutor: 内部同步执行的函数
 */
class Promise {
    constructor(excutor) {
        const self = this;
        self.status = PENDING;
        self.onFulfilled = [];// 成功的回调
        self.onRejected = [];// 失败的回调

        // 异步处理成功调用的函数
        // PromiseA+ 2.1 状态只能由Pending转为fulfilled或rejected;fulfilled状态必须有一个value值;rejected状态必须有一个reason值。
        function resolve(value) {
            if (self.status === PENDING) {
                self.status = FULFILLED;
                self.value = value;
                 // PromiseA+ 2.2.6.1 相同promise的then可以被调用多次,当promise变为fulfilled状态,全部的onFulfilled回调按照原始调用then的顺序执行
                self.onFulfilled.forEach(fn => fn());
            }
        }

        function reject(reason) {
            if (self.status === PENDING) {
                self.status = REJECTED;
                self.reason = reason;
                // PromiseA+ 2.2.6.2 相同promise的then可以被调用多次,当promise变为rejected状态,全部的onRejected回调按照原始调用then的顺序执行
                self.onRejected.forEach(fn => fn());
            }
        }

        try {
            excutor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }

    then(onFulfilled, onRejected) {
        // PromiseA+ 2.2.1 onFulfilled和onRejected是可选参数
        // PromiseA+ 2.2.5 onFulfilled和onRejected必须被作为函数调用
        // PromiseA+ 2.2.7.3 如果onFulfilled不是函数且promise1状态是fulfilled,则promise2有相同的值且也是fulfilled状态
        // PromiseA+ 2.2.7.4 如果onRejected不是函数且promise1状态是rejected,则promise2有相同的值且也是rejected状态
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

        const self = this;
        const promise = new Promise((resolve, reject) => {
            const handle = (callback, data) => {
                // PromiseA+ 2.2.4 onFulfilled或者onRejected需要在自己的执行上下文栈里被调用,所以此处用setTimeout
                setTimeout(() => {
                    try {
                         // PromiseA+ 2.2.2 如果onFulfilled是函数,则在fulfilled状态之后调用,第一个参数为value
                        // PromiseA+ 2.2.3 如果onRejected是函数,则在rejected状态之后调用,第一个参数为reason
                        const x = callback(data);
                        // PromiseA+ 2.2.7.1 如果onFulfilled或onRejected返回一个x值,运行这[[Resolve]](promise2, x)
                        resolvePromise(promise, x, resolve, reject);
                    } catch (e) {
                        // PromiseA+ 2.2.7.2 onFulfilled或onRejected抛出一个异常e,promise2必须以e的理由失败
                        reject(e);
                    }
                })
            }
            if (self.status === PENDING) {
                self.onFulfilled.push(() => {
                    handle(onFulfilled, self.value);
                });

                self.onRejected.push(() => {
                    handle(onRejected, self.reason);
                })
            } else if (self.status === FULFILLED) {
                setTimeout(() => {
                    handle(onFulfilled, self.value);
                })
            } else if (self.status === REJECTED) {
                setTimeout(() => {
                    handle(onRejected, self.reason);
                })
            }
        })

        return promise;
    }
}

function resolvePromise(promise, x, resolve, reject) {
    // PromiseA+ 2.3.1 如果promise和x引用同一对象,会以TypeError错误reject promise
    if (promise === x) {
        reject(new TypeError('Chaining Cycle'));
    }

    if (x && typeof x === 'object' || typeof x === 'function') {
        // PromiseA+ 2.3.3.3.3 如果resolvePromise和rejectPromise都被调用,或者对同一个参数进行多次调用,那么第一次调用优先,以后的调用都会被忽略。 
        let used;
        try {
            // PromiseA+ 2.3.3.1 let then be x.then
            // PromiseA+ 2.3.2 调用then方法已经包含了该条(该条是x是promise的处理)。
            let then = x.then;

            if (typeof then === 'function') {
                // PromiseA+ 2.3.3.3如果then是一个函数,用x作为this调用它。第一个参数是resolvePromise,第二个参数是rejectPromise
                // PromiseA+ 2.3.3.3.1 如果resolvePromise用一个值y调用,运行[[Resolve]](promise, y)
                // PromiseA+ 2.3.3.3.2 如果rejectPromise用一个原因r调用,用r拒绝promise。
                then.call(x, (y) => {
                    if (used) return;
                    used = true;
                    resolvePromise(promise, y, resolve, reject)
                }, (r) => {
                    if (used) return;
                    used = true;
                    reject(r);
                })
            } else {
                // PromiseA+ 如果then不是一个函数,变为fulfilled状态并传值为x
                if (used) return;
                used = true;
                resolve(x);
            }
        } catch (e) {
            // PromiseA+ 2.3.3.2 如果检索属性x.then抛出异常e,则以e为原因拒绝promise
            // PromiseA+ 2.3.3.4 如果调用then抛出异常,但是resolvePromise或rejectPromise已经执行,则忽略它
            if (used) return;
            used = true;
            reject(e);
        }

    } else {
        // PromiseA+ 2.3.4 如果x不是一个对象或函数,状态变为fulfilled并传值x
        resolve(x);
    }
}

实现Promise.resolve

js 复制代码
class Promise {
    // ...
    // 将现有对象转为 Promise 对象
    static resolve(value) {
        // 如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
        if (value instanceof Promise) return value;

        // 参数是一个thenable对象(具有then方法的对象),Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
        if (typeof value === 'object' || typeof value === 'function') {
            try {
                let then = value.then;
                if (typeof then === 'function') {
                    return new Promise(then.bind(value));
                }
            } catch (e) {
                return new Promise((resolve, reject) => {
                    reject(e);
                })
            }
        }

        // 参数不是具有then方法的对象,或根本就不是对象,Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
        return new Promise((resolve, reject) => {
            resolve(value);
        })
    }
}

实现Promise.reject

js 复制代码
class Promise {
    // ...
    // 返回一个新的 Promise 实例,该实例的状态为rejected。
    static reject(reason) {
        return new Promise((resolve, reject) => {
            reject(reason);
        })
    }
}

实现Promise.all

js 复制代码
class Promise {
    // ...
    // 用于将多个 Promise 实例,包装成一个新的 Promise 实例。只有所有状态都变为fulfilled,p的状态才会是fulfilled
    static all(promises) {
        const values = [];
        let resolvedCount = 0;
        return new Promise((resolve, reject) => {
            promises.forEach((p, index) => {
                Promise.resolve(p).then(value => {
                    resolvedCount++;
                    values[index] = value;
                    if (resolvedCount === promises.length) {
                        resolve(values);
                    }
                }, reason => {
                    reject(reason);
                })
            })
        })
    }
}

实现Promise.race

js 复制代码
class Promise {
    // ...
     // 只要有一个实例率先改变状态,状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给回调函数。
    static race(promises) {
        return new Promise((resolve, reject) => {
            promises.forEach((p, index) => {
                Promise.resolve(p).then(value => {
                    resolve(value);
                }, reason => {
                    reject(reason);
                })
            })
        })
    }
}

实现Promise.catch

js 复制代码
class Promise {
    // ...
    // 是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
    catch(onRejected) {
        return this.then(undefined, onRejected);
    }
}

实现Promise.any

如果有promise成功则返回,都没有成功返回error

js 复制代码
Promise.any = async function(promises) {
  if (promises.length === 0) {
    throw new AggregateError([], 'All promises were rejected');
  }
 
  for (const promise of promises) {
    try {
      return await promise;
    } catch {
      // 忽略错误并继续等待下一个promise
    }
  }
 
  throw new AggregateError([], 'All promises were rejected');
};

实现Promise.finally

js 复制代码
class Promise {
    // ...
    // 用于指定不管 Promise 对象最后状态如何,都会执行的操作。
    finally(callback) {
        return this.then(
            value => Promise.resolve(callback()).then(() => value),
            reason => Promise.resolve(callback()).then(() => { throw reason })
        )
    }
}

async await原理

js 复制代码
function run(genF) {
    // 返回值是Promise
    return new Promise((resolve, reject) => {
        const gen = genF();
        function step(nextF) {
            let next;
            try {
                // 执行该函数,获取一个有着value和done两个属性的对象
                next = nextF();
            } catch (e) {
                // 出现异常则将该Promise变为rejected状态
                reject(e);
            }

            // 判断是否到达末尾,Generator函数到达末尾则将该Promise变为fulfilled状态
            if (next.done) {
                return resolve(next.value);
            }

            // 没到达末尾,则利用Promise封装该value,直到执行完毕,反复调用step函数,实现自动执行
            Promise.resolve(next.value).then((v) => {
                step(() => gen.next(v))
            }, (e) => {
                step(() => gen.throw(e))
            })
        }

        step(() => gen.next(undefined));
    })
}

用async await实现一个中间件,计算函数执行时间

js 复制代码
function createTimingMiddleware() {
  return async (ctx, next) => {
    const start = Date.now();
    console.log('Function execution started at:', start);
 
    // 调用下一个中间件或操作
    await next();
 
    const end = Date.now();
    console.log('Function execution completed at:', end);
    console.log(`Function execution took: ${end - start}ms`);
  };
}
 
// 使用示例
const middleware = createTimingMiddleware();
 
// 假设你有一个需要计算执行时间的函数
const myFunction = async () => {
  // 一些异步操作
  await new Promise(resolve => setTimeout(resolve, 1000));
};
 
// 应用中间件
const timedFunction = middleware(myFunction);
 
// 执行被计算执行时间的函数
(async () => {
  await timedFunction();
})();

实现sleep函数

js 复制代码
function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('sleep...')
            resolve()
        }, ms);
    })
}

async function test(){
    console.log('1');
    await sleep(400)
    console.log('2')
}

test();

Node内置对象

实现Event类

js 复制代码
class Event {
    constructor(){}
    handlers = {};

    addEventListener(type, handler) {
        if (!(type in this.handlers)) {
            this.handlers[type] = [];
        }

        this.handlers[type].push(handler);
    }

    dispatchEvent(type, ...args) {
        if (!(type in this.handlers)) {
            return new Error('not registered!!!')
        }

        this.handlers[type].forEach(handler => {
            handler(...args);
        });
    }

    removeEventListener(type, handler) {
        if (!(type in this.handlers)) {
            return new Error('invalid event!!!')
        }

        if (!handler) {
            delete this.handlers[type]
        } else {
            const idx = this.handlers[type].findIndex(ele => ele === handler);
            if (idx === -1) {
                return new Error('invalid event!!!')
            }
            this.handlers[type].splice(idx, 1);
            if (this.handlers[type].length === 0) {
                delete this.handlers[type];
            }
        }
    }
}

var event = new Event();
function load(){
    console.log('load: ',...arguments);
}
function load2(){
    console.log('load2: ',...arguments);
}
event.addEventListener('load', load)
event.addEventListener('load', load2)

event.dispatchEvent('load', 'hello, world');

event.removeEventListener('load', load);
event.dispatchEvent('load', 'hello, world');

请求响应

ajax请求过程

优化性能

实现节流函数

函数节流 方法是一个函数,它在 t 毫秒内只能执行一次,如果在这个时间窗口内再次调用它,它将不会再次执行。

例如,假设 t = 50ms ,函数分别在 30ms60ms100ms 时调用。第一个函数会在80ms执行,第二个函数将不被执行,第三个函数将在150ms执行。如果改为 t = 75ms ,则第一个调用将在 105ms 执行,第二、三个调用将被取消。

js 复制代码
/**
 * @param {Function} fn
 * @param {number} t milliseconds
 * @return {Function}
 */
var debounce = function(fn, t) {
    let timer = null;
    return function(...args) {
        if (!timer) {
            timer = setTimeout(()=>{
                fn.apply(this, args)
            }, t);
        }
    }
};
ts 复制代码
type F = (...args: number[]) => void

function debounce(fn: F, t: number): F {
    let timer = null;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(()=>{
            fn.apply(this, args);
        },t)
    }
};

实现防抖函数

函数防抖 方法是一个函数,它的执行被延迟了 t 毫秒,如果在这个时间窗口内再次调用它,它的执行将被取消。你编写的防抖函数也应该接收传递的参数。

例如,假设 t = 50ms ,函数分别在 30ms60ms100ms 时调用。前两个函数调用将被取消,第三个函数调用将在 150ms 执行。如果改为 t = 35ms ,则第一个调用将被取消,第二个调用将在 95ms 执行,第三个调用将在 135ms 执行。

js 复制代码
/**
 * @param {Function} fn
 * @param {number} t milliseconds
 * @return {Function}
 */
var debounce = function(fn, t) {
    let timer = null;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(()=>{
            fn.apply(this, args)
        }, t);
    }
};
ts 复制代码
type F = (...args: number[]) => void

function debounce(fn: F, t: number): F {
    let timer = null;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(()=>{
            fn.apply(this, args);
        },t)
    }
};

实现一个限流器

js 复制代码
// https://blog.csdn.net/zz_jesse/article/details/107293743?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-5-107293743.nonecase&utm_term=js%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E5%B8%A6%E5%B9%B6%E5%8F%91%E9%99%90%E5%88%B6%E7%9A%84%E5%BC%82%E6%AD%A5%E8%B0%83%E5%BA%A6%E5%99%A8
class Scheduler {
    // TODO
    list = [];
    constructor(){}
    add(promiseCreator) {
        this.list.push(promiseCreator)
    }
    max = 2;
    startIndex = 0;

    enter(){
        for(var i = 0; i < this.max; i++){
            this.run();
        }
    }

    run(){
        if (this.list.length == 0 || this.startIndex >= this.max) return;
        this.startIndex++;
        this.list.shift()().then(() => {
            this.startIndex--;
            this.enter();
        })
    }
}

const scheduler = new Scheduler();

const timeout = time => {
    return new Promise(resolve => setTimeout(resolve, time))
}

const addTask = (time, order) => {
    scheduler.add(() => timeout(time).then(() => console.log(order)))
}

addTask(1000, 1)
addTask(500, 2)
addTask(300, 3)
addTask(400, 4)

scheduler.enter();

// 2 3 1 4

前端打包

实现类似rollup的打包功能

rollup原理和简易实现

实现tree-shaking

tree-shaking算法

python 复制代码
import ast
 
def is_node_side_effect_free(node):
    # 判断节点是否为副作用免的表达式
    return isinstance(node, ast.Expr) and isinstance(node.value, ast.Constant)
 
def perform_tree_shaking(tree):
    # 遍历AST,移除副作用免的节点
    for node in tree.body:
        if is_node_side_effect_free(node):
            tree.body.remove(node)
 
# 示例代码
code = """
console.log('Hello, World!');
var x = 1 + 2;
console.log(x);
"""
 
# 使用ast模块解析代码
tree = ast.parse(code)
 
# 执行tree-shaking
perform_tree_shaking(tree)
 
# 将修改后的AST编译回代码
compiled_code = astunparse.unparse(tree)
print(compiled_code)

DOM

虚拟DOM的渲染

详情请参考:# 手写简易前端框架:vdom 渲染和 jsx 编译

js 复制代码
const render = (vdom, parent = null) => {
    const mount = parent ? (el => parent.appendChild(el)) : (el => el);
    if (isTextVdom(vdom)) {
        return mount(document.createTextNode(vdom));
    } else if (isElementVdom(vdom)) {
        const dom = mount(document.createElement(vdom.type));
        for (const child of vdom.children) {
            render(child, dom);
        }
        for (const prop in vdom.props) {
            setAttribute(dom, prop, vdom.props[prop]);
        }
        return dom;
    }
};

模版渲染

模版渲染戳这里

Diff算法

Diff算法是一种用来比较两个数据集合差异的算法。在JavaScript中,我们可以使用diff算法来比较两个数组或者两棵树的差异,并记录下这些差异,以便我们可以高效地更新DOM。 以下是一个简单的实现:

js 复制代码
function diff(oldArr, newArr) {
    let result = [];
 
    for (let i = 0; i < newArr.length; i++) {
        if (!oldArr.includes(newArr[i])) {
            result.push({ type: 'add', item: newArr[i] });
        }
    }
 
    for (let i = 0; i < oldArr.length; i++) {
        if (!newArr.includes(oldArr[i])) {
            result.push({ type: 'remove', item: oldArr[i] });
        }
    }
 
    return result;
}
 
// 使用示例
let oldList = [1, 2, 3, 4, 5];
let newList = [1, 2, 3, 6, 7];
 
let changes = diff(oldList, newList);
console.log(changes); // [{ type: 'add', item: 6 }, { type: 'add', item: 7 }]

输出DOM结构

js 复制代码
// 输出DOM结构
var str = '';
var el = document.documentElement;
var empty;
var level = 0;

function output(el, level) {
    if (el) {
        if (level > 0) {
            empty = new Array(level).fill(' ');
            str += empty.join('');
        }
        str += el.tagName;
        str += '\n'; // 换行
    }
    if (el.children) {
        for(let i = 0; i < el.children.length; i++) {
            output(el.children[i], level+1);
        }
    }
}

output(el, level);
console.log(str)

实现红绿灯效果

js 复制代码
const timeout = (time) => {
    return new Promise(resolve => setTimeout(resolve, time))
}

var el = document.querySelector('body')
function change(){
    timeout(2000).then(() => {
        el.style.backgroundColor = 'red';
        return timeout(3000)
    }).then(() => {
        el.style.backgroundColor = 'yellow';
        return timeout(1000)
    }).then(() => {
        el.style.backgroundColor = 'green';
        change()
    })
}

change();

其他

url解析

js 复制代码
function parseURL(url) {
    var result = {};
    var match = url.match(/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/);
 
    if (match) {
        result.protocol = match[1];
        result.slashes = match[2];
        result.host = match[3];
        result.port = match[4];
        result.path = match[5];
        result.query = match[6];
        result.hash = match[7];
    }
 
    return result;
}
 
// 示例使用
var myURL = "https://www.example.com:80/pathname/?search=test#hash";
console.log(parseURL(myURL));

懒加载

1)首先,不要将图片地址放到src属性中,而是放到其它属性(data-original)中。

2)页面加载完成后,根据scrollTop判断图片是否在用户的视野内,如果在,则将data-original属性中的值取出存放到src属性中。

3)在滚动事件中重复判断图片是否进入视野,如果进入,则将data-original属性中的值取出存放到src属性中。

elementNode.getAttribute(name):方法通过名称获取属性的值。

elementNode.setAttribute(name, value):方法创建或改变某个新属性。

elementNode.removeAttribute(name):方法通过名称删除属性的值。

js 复制代码
//懒加载代码实现
var viewHeight = document.documentElement.clientHeight;//可视化区域的高度

function lazyload () {
    //获取所有要进行懒加载的图片
    let eles = document.querySelectorAll('img[data-original][lazyload]');//获取属性名中有data-original的
    Array.prototype.forEach.call(eles, function(item, index) {
        let rect;
        if(item.dataset.original === '') {
            return;
        }

        rect = item.getBoundingClientRect();

        //图片一进入可视区,动态加载
        if(rect.bottom >= 0 && rect.top < viewHeight) {
            !function () {
                let img = new Image();
                img.src = item.dataset.original;
                img.onload = function () {
                    item.src = img.src;
                }
                item.removeAttribute('data-original');
                item.removeAttribute('lazyload');
            }();
        }
    })
}

lazyload();

document.addEventListener('scroll', lazyload);

实现事件发射器EventEmitter(leetcode有)

设计一个 EventEmitter 类。这个接口与 Node.js 或 DOM 的 Event Target 接口相似,但有一些差异。EventEmitter 应该允许订阅事件和触发事件。

你的 EventEmitter 类应该有以下两个方法:

  • subscribe - 这个方法接收两个参数:一个作为字符串的事件名和一个回调函数。当事件被触发时,这个回调函数将被调用。 一个事件应该能够有多个监听器。当触发带有多个回调函数的事件时,应按照订阅的顺序依次调用每个回调函数。应返回一个结果数组。你可以假设传递给 subscribe 的回调函数都不是引用相同的。 subscribe 方法还应返回一个对象,其中包含一个 unsubscribe 方法,使用户可以取消订阅。当调用 unsubscribe 方法时,回调函数应该从订阅列表中删除,并返回 undefined。
  • emit - 这个方法接收两个参数:一个作为字符串的事件名和一个可选的参数数组,这些参数将传递给回调函数。如果没有订阅给定事件的回调函数,则返回一个空数组。否则,按照它们被订阅的顺序返回所有回调函数调用的结果数组。
js 复制代码
class EventEmitter {
    constructor() {
        this.events = {};
    }

    /**
     * @param {string} eventName
     * @param {Function} callback
     * @return {Object}
     */
    subscribe(eventName, callback) {
        //  this.events对象中的键值对,键为事件名称,值为其回调函数数组
        this.events[eventName] = this.events[eventName] ?? [];
        this.events[eventName].push(callback);
        return {
            // 取消某个事件的某个callback
            unsubscribe: () => {
                this.events[eventName] = this.events[eventName].filter(f => f !== callback);
                // 为避免内存泄漏,添加清理条件
                if (this.events[eventName].length === 0) { delete this.events[event] }
            }
        };
    }
    
    /**
     * @param {string} eventName
     * @param {Array} args
     * @return {Array}
     */
    emit(eventName, args = []) {
        // 如果事件不被监听,则返回[]
        if (!(eventName in this.events)) return [];
        // 否则返回事件回掉函数的所有处理结果
        return this.events[eventName].map(f => f(...args));
    }
}

/**
 * const emitter = new EventEmitter();
 *
 * // Subscribe to the onClick event with onClickCallback
 * function onClickCallback() { return 99 }
 * const sub = emitter.subscribe('onClick', onClickCallback);
 *
 * emitter.emit('onClick'); // [99]
 * sub.unsubscribe(); // undefined
 * emitter.emit('onClick'); // []
 */

发布订阅模式

ts 复制代码
// 发布订阅(TypeScript版)
interface Publish {
    registerObserver(eventType : string, subscribe : Subscribe) : void;
    remove(eventType : string, subscribe ?: Subscribe) : void;
    notifyObservers(eventType : string) : void;
}
interface SubscribesObject{
    [key : string] : Array<Subscribe>
}
class ConcretePublish implements Publish {
    private subscribes : SubscribesObject;

    constructor() {
        this.subscribes = {};
    }

    registerObserver(eventType : string, subscribe : Subscribe) : void {
        if (!this.subscribes[eventType]) {
            this.subscribes[eventType] = [];
        }

        this.subscribes[eventType].push(subscribe);
    }

    remove(eventType : string, subscribe ?: Subscribe) : void {
        const subscribeArray = this.subscribes[eventType];
        if (subscribeArray) {
            if (!subscribe) {
                delete this.subscribes[eventType];
            } else {
                for (let i = 0; i < subscribeArray.length; i++) {
                    if (subscribe === subscribeArray[i]) {
                        subscribeArray.splice(i, 1);
                    }
                }
            }
        }
    }

    notifyObservers(eventType : string, ...args : any[]) : void {
        const subscribes = this.subscribes[eventType];
        if (subscribes) {
            subscribes.forEach(subscribe => subscribe.update(...args))
        }
    }
}

interface Subscribe {
    update(...value : any[]) : void;
}

class ConcreteSubscribe1 implements Subscribe {
    public update(...value : any[]) : void {
        console.log('已经执行更新操作1,值为', ...value);
    }
}
class ConcreteSubscribe2 implements Subscribe {
    public update(...value : any[]) : void {
        console.log('已经执行更新操作2,值为', ...value);
    }
}

function main() {
    const publish = new ConcretePublish();
    const subscribe1 = new ConcreteSubscribe1();
    const subscribe2 = new ConcreteSubscribe2();

    publish.registerObserver('1', subscribe1);
    publish.registerObserver('2', subscribe2);

    publish.notifyObservers('2', '22222');
}

main();

实现eventBus

js 复制代码
class EventBus{
    constructor() {
        this.listeners = {};
    }

    on(event, listener){
        if(!this.listeners[event]){
            this.listeners[event] = [];
        }
        this.listeners[event].push(listener);
    }

    off(event, listener){
        if(this.listeners[event]){
            const index = this.listeners[event].indexOf(listener);
            if(index > -1){
                this.listeners[event].splice(index, 1);
            }
        }
    }

    emit(event, ...args){
        if(this.listeners[event]){
            this.listeners[event].forEach(listener => listener(...args));
        }
    }
}

const bus = new EventBus();

bus.on('login', function(name){
    console.log('12313', name);
})

bus.on('login', function(name){
    console.log('1231223', name);
})

bus.emit('login', 1222313);

FileReader使用

js 复制代码
function uploadMulFile(uploadFile) {
    return new Promise((resolve, reject) => {
        let fileLength = 0;
        let reader = new FileReader();
        reader.readAsText(uploadFile[fileLength]);
        reader.onabort = function(e) {
            console.log("文件读取异常");
        }
        reader.onerror = function(e) {
            console.log("文件读取错误");
        }

        reader.onload = function(e){
            if(e.target.result) {

                fileLength++;
                if(fileLength < uploadFile.length) {
                    reader.readAsText(uploadFile[fileLength]);
                }else{
                    resolve({
                        carArr,
                        crossArr,
                        roadArr
                    })
                }
            }
        }
    })
}

参考文档

  1. # Promise 手撕
  2. # 【前端】从小白视角上手Promise、Async/await和手撕代码
  3. # 前端面试之JavaScript常见手撕代码题汇总
  4. # 面试官为啥总是让我们手撕call、apply、bind?
  5. # js十大手撕代码
  6. # 一篇搞定前端高频手撕算法题(36道)
  7. # 前端手撕代码汇总
相关推荐
shymoy22 分钟前
Radix Sorts
数据结构·算法·排序算法
风影小子30 分钟前
注册登录学生管理系统小项目
算法
黑龙江亿林等保32 分钟前
深入探索哈尔滨二级等保下的负载均衡SLB及其核心算法
运维·算法·负载均衡
lucy1530275107935 分钟前
【青牛科技】GC5931:工业风扇驱动芯片的卓越替代者
人工智能·科技·单片机·嵌入式硬件·算法·机器学习
杜杜的man1 小时前
【go从零单排】迭代器(Iterators)
开发语言·算法·golang
小沈熬夜秃头中୧⍤⃝1 小时前
【贪心算法】No.1---贪心算法(1)
算法·贪心算法
木向2 小时前
leetcode92:反转链表||
数据结构·c++·算法·leetcode·链表
阿阿越2 小时前
算法每日练 -- 双指针篇(持续更新中)
数据结构·c++·算法
skaiuijing2 小时前
Sparrow系列拓展篇:对调度层进行抽象并引入IPC机制信号量
c语言·算法·操作系统·调度算法·操作系统内核
Star Patrick2 小时前
算法训练(leetcode)二刷第十九天 | *39. 组合总和、*40. 组合总和 II、*131. 分割回文串
python·算法·leetcode