前端面试经典手写题

1、手写Promise

class Promise2 {
  state = "pending";
  callbacks = [];
  constructor(fn) {
    fn(this.resolve.bind(this), this.reject.bind(this));
  }
  resolve(result) {
    if (this.state !== "pending") return;
    this.state = "fullFilled";

    nextTick(() => {
      this.callbacks.forEach((handle) => {
        if (typeof handle[0] === "function") {
          handle[0].call(undefined, result);
        }
      });
    });
  }
  reject(reason) {
    if (this.state !== "pending") return;
    this.state = "rejected";
    nextTick(() => {
      this.callbacks.forEach((handle) => {
        if (typeof handle[1] === "function") {
          handle[1].call(undefined, reason);
        }
      });
    });
  }
  then(success, fail) {
    const handle = [];
    if (typeof success === "function") {
      handle[0] = success;
    }
    if (typeof fail === "function") {
      handle[1] = fail;
    }
    this.callbacks.push(handle);
    return this;
  }
}

function nextTick(fn) {
  if (process !== undefined && typeof process.nextTick === "function") {
    return process.nextTick(fn);
  } else {
    var counter = 1;
    const observer = new MutationObserver(fn);
    var textNode = document.createTextNode(String(counter));

    observer.observe(textNode, {
      // 踪字符更改
      characterData: true,
    });
    counter += 1;
    textNode.data = String(counter);
  }
}

// 方法返回一个Promise实例,此实例在 iterable 参数内所有的promise 都完成(resolved)时回调完成(resolve);
// 如果参数中 promise有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败promise的结果。

Promise2.all = function(arrP) {
  let list = [];
  len = 0;
  return new Promise2((resolve, reject) => {
    for (let i = 0; i < arrP.length; i++) {
      arrP[i].then(
        (val) => {
          list[i] = val;
          len++;
          len === arrP.length && resolve(list);
        },
        (err) => {
          reject(error);
        }
      );
    }
  });
};

Promise.prototype.myAll = (iterator) => {
  return new Promise((resolve, reject) => {
    const ret = []
    let count = 0
    Array.from(iterator).forEach((item, index) => {
      Promise.resolve(item).then(data => {
        ret[index] = data
        count++
        if(count === iterator.length) {
          resolve(ret)
        }
      }, reject)
    })
  })
}

// 方法返回一个Promise实例,一旦迭代器中的某个 promise 完成(resolved)或失败(rejected),返回的 promise 就会 resolve 或 reject

Promise2.race = function(arrP) {
  let flag1 = false;
  let flag2 = false;
  return new Promise2((resolve, reject) => {
    for (let i = 0; i < arrP.length; i++) {
      arrP[i].then(
        (data) => {
          !flag2 && !flag1 && resolve(data);
          flag1 = true;
          return;
        },
        (error) => {
          !flag2 && !flag1 && reject(error);
          flag2 = true;
          return;
        }
      );
    }
  });
};

new Promise2((resolve, reject) => {
  let [val, time] = [Math.random(), Math.random() * 1000];
  setTimeout(() => {
    val > 0.2 ? resolve(val) : reject(val);
  }, time);
}).then(
  (val) => console.log("promise 测试:", val),
  (err) => console.error("promise 测试:" + err)
);

const getPList = () => {
  let arrP = [];
  for (let i = 0; i < 10; i++) {
    arrP[i] = new Promise2((resolve, reject) => {
      let [v, t] = [Math.random(), Math.random() * 1000];
      setTimeout(() => {
        v > 0.1 ? resolve(v) : reject(v);
      }, t);
    });
  }
  return arrP;
};

Promise2.all(getPList()).then(
  (data) => console.log("promise.all 测试:", data),
  (err) => console.error("promise.all 测试:" + err)
);

Promise2.race(getPList()).then(
  (data) => console.log("promise.race 测试:", data),
  (err) => console.error("promise.race 测试:" + err)
);

2、手写new

// 新生成一个对象
// 将构造函数的作用域赋值给新对象(即绑定新对象的 this)
// 执行构造函数中的代码(即为这个新对象添加属性)
// 返回新对象

function myNew() {
  // 创建对象
  let obj = new Object();

  // 取第一个参数
  let fn = Array.prototype.shift.call(arguments);

  //obj.__proto__指向fn.prototype
  obj.__proto__ = fn.prototype;

  // 执行结果
  let result = fn.apply(obj, arguments);

  return typeof result === "object" ? result : obj;
}

function Person(name) {
  this.name = name;
}

// var p1 = myNew(Person, "xx");
// console.log(p1.name);

function P(name) {
  this.name = name;
  return 1;
}

var p2 = myNew(P, "xm");
console.log(p2);

3、手写instanceof

实现思路:

1、leftVaule代表实例对象

2.rightVaule代表构造函数

3.利用typeof方法,判断输入的leftVaule是否为对象,如果不是,则返回false

4.遍历leftVaule的原型链,直到找到rightVaule的prototype,如果查找失败的话,返回false,反之,返回true

function myInstanceof(leftValue, rightValue) {
  if (typeof leftValue !== "object" || leftValue === null) return false;

  let leftProto = leftValue.__proto__;
  let rightProto = rightValue.prototype;
  while (true) {
    if (leftProto === null) {
      return false;
    }
    if (leftProto === rightProto) {
      return true;
    }
    leftProto = leftProto.__proto__;
  }
}

myInstanceof([], Array);

4、并发请求限制

限制请求数,一个请求完成替换下一个请求

第一次分段

第二次添加下一个

控制startIndex与endIndex

终止态:返回值已等于请求数,执行cb

// class LimitFetch {}

// 
// 第一次分段
// 第二次添加下一个
// 控制startIndex与endIndex
// 终止态:返回值已等于请求数,执行cb

class LimitFetch {
  constructor(opts) {
    this.requestList = opts.requestList;
    this.limit = opts.limit;
    this.cb = opts.cb;
    this.startIndex = 0;
    this.result = {};
    this.resultCount = 0;
    this.batchRequest();
  }
  batchRequest(num) {
    const endIndex = this.startIndex + (num || this.limit);
    const len = this.requestList.length;

    for (let i = this.startIndex; i < endIndex; i++) {
      this.startIndex++;
      if (!this.requestList[i]) return;
      this.requestList[i]().then((res) => {
        this.result[i] = res;
        this.resultCount++;
        if (this.resultCount === len) {
          this.cb(this.result);
        }
        if (i < len - 1) {
          this.batchRequest(1);
        }
      });
    }
  }
}

// 函数写法
function limitFetch(requestList, limit, cb) {
  let startIndex = 0;
  let results = {};
  let resultCount = 0;

  function batchRequest(num) {
    const endIndex = startIndex + (num || limit);

    for (let i = startIndex, len = requestList.length; i < endIndex; i++) {
      if (!requestList[i]) continue;

      startIndex++;
      requestList[i]().then((res) => {
        resultCount++;
        results[i] = res;
        if (i < len - 1) {
          batchRequest(1);
        }
        if (resultCount === len) {
          cb(results);
        }
      });
    }
  }
  batchRequest();
}

let requestList = [];

function fn(time) {
  return function () {
    // console.log(time);
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log(time);
        resolve(time);
      }, time);
    });
  };
}

for (let i = 0; i < 5; i++) {
  requestList.push(fn(1 * 1000));
}
// limitFetch(requestList, 3, (res) => {
//   console.log(res);
// });

new LimitFetch({
  requestList,
  limit: 3,
  cb: (res) => {
    console.log(res);
  },
});


// 限制并发
// 可以新增加
class BtRequest{
  constructor(opts) {
    this.limit  = opts.limit
    this.isRequest = false
    this.queue = []
  }

  add(fn) {
    this.queue.push(fn)
    if(!this.isRequest) {
      this.request()
    }
  }

  request(end) {
    this.isRequest = true
    end = end || this.limit
    const requestList = this.queue.splice(0, end)
        
    if(!requestList.length) {
      this.isRequest = false
    }
   requestList.forEach(item => {
    Promise.resolve(item()).then((res) => {
        console.log(1,res);
        this.request(1)
      })
    })
    
  }
}


const request = new BtRequest({limit: 1})

request.add(() => {
  console.log(100);
  // return 500
})
request.add(() => {
  console.log(200);
  // return 600

})
request.add(() => {
  console.log(300);
  // return 700

})
request.add(() => {
  console.log(400);
  // return 800

})

5、手写发布订阅者

使用一个对象作为缓存

on 负责把方法发布到缓存的 EventName 对应的数组

emit 负责遍历触发(订阅) EventName 下的方法数组

off 找方法的索引,并删除

// 使用一个对象作为缓存
// on 负责把方法发布到缓存的 EventName 对应的数组
// emit 负责遍历触发(订阅) EventName 下的方法数组
// off 找方法的索引,并删除

function indexOf(a, b) {
  return a.indexOf(b);
}

class EventBus {
  constructor() {
    this.cache = {};
  }

  on(eventName, fn) {
    this.cache[eventName] = this.cache[eventName] || [];
    this.cache[eventName].push(fn);
  }

  off(eventName, fn) {
    const index = this.cache[eventName].indexOf(fn);
    if (index !== -1) {
      this.cache[eventName].splice(index, 1);
    }
  }

  emit(eventName) {
    this.cache[eventName].forEach((fn) => {
      fn();
    });
  }

 once(eventName, cb) {
    const one = (...args) => {
      cb(...args)
      this.off(eventName, one)
    }
    this.on(eventName,one)
  }
}

6、手写一个搜索的组件

支持防抖

<template>
  <div>
    {{a}}
    <input type='text' @input="onInput()">
    <p>{{res}}</p>
  </div>
</template>
<script>

const fetch = () => Promise.resolve('this is fetch data')
export default{
  data() {
    return {
      a: '1',
      res: undefined
    }
  },
  methods: {
    deboundce(fn, time) {
      let timer;

      return function() {
        if(timer) {
          clearTimeout(timer)
        }
        timer = setTimeout(fn, time)
      }
    },
    async fetchData() {
      console.log(11)
      // return Promise.resolve(1)
      this.res = await fetch()
    },
    async onInput() {
      const fn =  this.deboundce(this.fetchData, 1000)
      fn()
      // console.log(data)

    }
  }
}

</script>

7、手写Promise.allSettled

Promise.allSettled 只关心所有 promise 是不是都被 settle 了,不管其是 rejected状态的 promise,还是非 rejected状态(即fulfilled)的 promise, 我都可以拿到它的最终状态并对其进行处理

Promise.allSettled 的结果数组中可能包含以下两种格式的数据

{status:"fulfilled", value:result} 对于成功的响应

{status:"rejected", reason:error} 对于 error

const promise1 = Promise.resolve(3)
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100,'foo'))
const promise3 = [promise1, promise2]

Promise.myAllSettled = function(promises) {
  return new Promise(resolve => {
    const data = [], len = promises.length
    let count = len;
    for(let i = 0; i < len; i++) {
      const promise = promises[i]
      promise.then(res => {
        data[i] = {status: 'fulfilled', value: res}
      },error => {
        data[i] = {status: 'rejected', value: error}
      }).finally(() => {
        if(!--count) {
          resolve(data)
        }
      })

    }
  })
}

Promise.myAllSettled(promise3)
.then(results => results.forEach(result => console.log(result.status)))

8、手写bind

要支持能做为构造函数

思路:Function 的原型对象上增加一个函数,返回值是一个函数,函数的fn.prototype.constructor 指向函数和函数的 prototype 指向 Object.create(this.prototype)

const obj = {
name: 'xiao'
}

function func(first,last){
console.log(first + this.name, last);

}

Function.prototype.myBind = function(context,...args ){
console.log(context);
context.fn = this;

const fn = function() {
context.fn.apply(context,[...args])
}

fn.prototype = Object.create(this.prototype)
fn.prototype.constructor = this;

return fn
}
const fn1 = func.myBind(obj,'li', 'ming')
fn1()

const fn2 = new fn1()

console.log(fn2);

9、手写一个防抖

// 防抖 // 不管事件触发频率多高,一定在事件触发n秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,就以新的事件的时间为准,n秒后才执行,总之,触发完事件 n 秒内不再触发事件,n秒后再执行。 // 关联记忆: 函数防抖就是法师发技能的时候要读条,技能读条没完再按技能就会重新读条。

function debounce(fn, wait, immediate) {
  let timeout;

  return function () {
    let context = this;
    let args = arguments;
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      var callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait);
      if (callNow) fn.apply(context, args);
    } else {
      timeout = setTimeout(function () {
        fn.apply(context, args);
      }, wait);
    }
  };
}

10、手写一个 深拷贝

// 基础版本
function clone2=(target, map = new WeakMap()) {

  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 isObject (target) {
  const type = typeof target
  return target !== null && (type === 'object' || type === 'function')
}

function getType(target) {
  return Object.prototype.toString.call(target)
}

function getInit(target) {
  const Ctor = target.constructor
  return new Ctor()
}
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]'

const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const funcTag = '[object Function]'

function forEach(array, iteratee) {
  let index = -1;
  const length = array.length;
  while (++index < length) {
      iteratee(array[index], index);
  }
  return array;
}

const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag]

//TODO: Reg 的拷贝方法
function cloneReg(targe) {
  const reFlags = /\w*$/;
  const result = new targe.constructor(targe.source, reFlags.exec(targe));
  result.lastIndex = targe.lastIndex;
  return result;
}

//TODO:  Symbol 的拷贝方法
function cloneSymbol(targe) {
  return Object(Symbol.prototype.valueOf.call(targe));
}

//TODO: cloneFunction
function cloneFunction(func) {
  const bodyReg = /(?<={)(.|\n)+(?=})/m;
  const paramReg = /(?<=().+(?=)\s+{)/;
  const funcString = func.toString();
  if (func.prototype) {
      console.log('普通函数');
      const param = paramReg.exec(funcString);
      const body = bodyReg.exec(funcString);
      if (body) {
          console.log('匹配到函数体:', body[0]);
          if (param) {
              const paramArr = param[0].split(',');
              console.log('匹配到参数:', paramArr);
              return new Function(...paramArr, body[0]);
          } else {
              return new Function(body[0]);
          }
      } else {
          return null;
      }
  } else {
      return eval(funcString);
  }
}

function cloneOtherType(targe, type) {
  //TODO: constructor
  const Ctor = targe.constructor;
  switch (type) {
      case boolTag:
      case numberTag:
      case stringTag:
      case errorTag:
      case dateTag:
          return new Ctor(targe);
      case regexpTag:
          return cloneReg(targe);
      case symbolTag:
          return cloneSymbol(targe);
      case funcTag:
        return cloneFunction(targe)
      default:
          return null;
  }
}

function clone(target, map = new WeakMap()) {
  if(!isObject(target)) return target

  const type = getType(target)
  let cloneTarget
  if(deepTag.includes(type)) {
    cloneTarget = getInit(target, type)
  }else {
    return cloneOtherType(target, type)
  }

  if (map.get(target)) {
    return map.get(target);
  }
  map.set(target, cloneTarget);

  if(type === setTag) {
    target.forEach(val => {
      cloneTarget.add(clone(val, map))
    })
    return cloneTarget
  }

  // 克隆map
  if (type === mapTag) {
    target.forEach((value, key) => {
        cloneTarget.set(key, clone(value,map));
    });
    return cloneTarget;
  }

   // 克隆对象和数组
    const keys = type === arrayTag ? undefined : Object.keys(target);
    forEach(keys || target, (value, key) => {
        if (keys) {
            key = value;
        }
        cloneTarget[key] = clone(target[key], map);
    });

    return cloneTarget;

}
相关推荐
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
老码沉思录1 小时前
写给初学者的React Native 全栈开发实战班
javascript·react native·react.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂1 小时前
工程化实战内功修炼测试题
前端·javascript
爱吃生蚝的于勒1 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
小白学大数据3 小时前
Python爬虫开发中的分析与方案制定
开发语言·c++·爬虫·python
冰芒猓4 小时前
SpringMVC数据校验、数据格式化处理、国际化设置
开发语言·maven
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css