手写总结

简介: 手写总结

手写总结


手写JS

JS 如何实现类?

方法一:使用原型

function Dog(name){ 
  this.name = name
  this.legsNumber = 4
}
Dog.prototype.kind = '狗'
Dog.prototype.say = function(){
  console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}
Dog.prototype.run = function(){
  console.log(`${this.legsNumber}条腿跑起来。`)
}
const d1 = new Dog('啸天') // Dog 函数就是一个类
d1.say()

请试着实现一个 Chicken 类,没 name 会 say 会 fly。

方法二:使用 class

Js的类语法:

  • 请使用关键字class创建一个类;
  • 请添加一个名为constructor()的方法;
  • this指的是对象的所有者,Dog上面的例子创建了一个名为 "Dog" 的类。
class Dog {
// 等价于在 constructor 里写 
  constructor(name) {
    this.kind = '狗'
    this.name = name
    this.legsNumber = 4
    // 思考:kind 放在哪,放在哪都无法实现上面的一样的效果
  }
  say(){
    console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
  }
  run(){
    console.log(`${this.legsNumber}条腿跑起来。`)
  }
}
const d1 = new Dog('啸天')
d1.say() 

请试着实现一个 Chicken 类,没 name 会 say 会 fly。

JS 如何实现继承?

js 六种继承方式介绍及优缺点

方法一:使用原型链

将父类的实例对象 赋值给子类的原型对象

缺点:

  • 所有子类实例,共享可修改的原型属性。
  • 子类 new 时不能向 父类 构造函数 传参。
  • 要手动修改 prototype.constructor 为子类。
function Animal(legsNumber){
  this.legsNumber = legsNumber
}
Animal.prototype.kind = '动物'
function Dog(name){ 
  this.name = name
  Animal.call(this, 4) // 关键代码1
}
Dog.prototype.__proto__ = Animal.prototype // 关键代码2,但这句代码被禁用了,怎么办
Dog.prototype.kind = '狗'
Dog.prototype.say = function(){
  console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}
const d1 = new Dog('啸天') // Dog 函数就是一个类
console.dir(d1)

如果面试官问被 ban 的代码如何替换,就说下面三句:

var f = function(){ }
f.prototype = Animal.prototype
Dog.prototype = new f()

方法二:使用 class

实现:子类 调用 父类构造函数。

  • 属性定义在 this,避免共享。
  • 可以向 父类 构造函数 传参。

缺点:

  • 父类方法,不能通过 prototype 访问。
  • 方法必须在父类构造函数中定义,且每次创建实例都会创建一遍方法。
class Animal{
  constructor(legsNumber){
    this.legsNumber = legsNumber
  }
  run(){}
}
class Dog extends Animal{
  constructor(name) {
    super(4)
    this.name = name
  }
  say(){
    console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
  }
}

手写节流 throttle、防抖 debounce

记忆题,写博客,甩链接。

节流:只执行第一次点击,在第一次点击完成前,后面的点击都会无效

// 节流就是「技能冷却中」
const throttle = (fn, time) => {
  let cooling = false
  return (...args) => {// 如果没有开启定时器,开启一个
    if(cooling) return
    fn.call(undefined, ...args)
    cooling = true
    setTimeout(()=>{
      cooling = false
    }, time)
  }
}
function throttle(fn, wait) {
    let timer = null;
    return function(...args) {
        // 如果没有开启定时器,开启一个
        if (!timer) {
          timer = setTimeout(()=>{
                fn.apply(this,args)
                // 执行完fn后将定时器timer清空
                timer = null;
            }, wait); 
        }
    }
}
// 还有一个版本是在冷却结束时调用 fn
// 简洁版,删掉冷却中变量,直接使用 timer 代替
const throttle = (f, time) => {
  let timer = null
  return (...args) => {
    if(timer) {return}
    f.call(undefined, ...args)
    timer = setTimeout(()=>{
      timer = null
    }, time)
  }
}

使用方法:

const f = throttle(()=>{console.log('hi')}, 3000)
f() // 打印 hi
f() // 技能冷却中

防抖:防抖是在多次点击中,只执行最后一次,前面的点击都会被取消

// 防抖就是「回城被打断」
const debounce = (fn, time) => {
  let timer = null
  return (...args)=>{
    if(timer !== null) {
      clearTimeout(timer)
    }
   timer = setTimeout(()=>{
      fn.call(undefined, ...args)
      timer = null
    }, time)
  }
}
function debounce(fun,time){
    let timer
    return function(...args){
        clearTimeout(timer) // 打断回城
         // 重新回城
        timer=setTimeout(()=>{
          fun.apply(this,args) // 回城后调用 fn
        },time)
    }
}

手写发布订阅

记忆题,写博客,甩链接

  • 创建一个 EventHub
  • 在该类上创建一个事件中心(Map)
  • on 方法用来把函数 fn 都加到事件中心中(订阅者注册事件到调度中心)
  • emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应事件中心中的函数(发布者发布事件到调度中心,调度中心处理代码)
  • off 方法可以根据 event 值取消订阅(取消订阅)
const eventHub = {
  map: {
    // click: [f1 , f2]
  },
  on: (name, fn)=>{
    eventHub.map[name] = eventHub.map[name] || []
    eventHub.map[name].push(fn)
  },
  emit: (name, data)=>{
    const q = eventHub.map[name]
    if(!q) return
    q.map(f => f.call(null, data))
    return undefined
  },  
  off: (name, fn)=>{
    const q = eventHub.map[name]
    if(!q){ return }
    const index = q.indexOf(fn)
    if(index < 0) { return }
    q.splice(index, 1)
  }
}
eventHub.on('click', console.log)
eventHub.on('click', console.error)
setTimeout(()=>{
  eventHub.emit('click', 'frank')
},3000)
复制代码

也可以用 class 实现。

class EventHub {
  map = {}
  on(name, fn) {
    this.map[name] = this.map[name] || []
    this.map[name].push(fn)
  }
  emit(name, data) {
    const fnList = this.map[name] || []
    fnList.forEach(fn => fn.call(undefined, data))
  }
  off(name, fn) {
    const fnList = this.map[name] || []
    const index = fnList.indexOf(fn)
    if(index < 0) return
    fnList.splice(index, 1)
  }
}
// 使用
const e = new EventHub()
e.on('click', (name)=>{
  console.log('hi '+ name)
})
e.on('click', (name)=>{
  console.log('hello '+ name)
})
setTimeout(()=>{
  e.emit('click', 'frank')
},3000)

手写 AJAX

AJAX(Asynchronous JavaScript and XML),指的是通过 JavaScript 的异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。

1.创建XMLHttpRequest对象,创建一个异步调用对象.

2.创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.

3.设置响应HTTP请求状态变化的函数.

4.发送HTTP请求

记忆题,写博客吧

const ajax = (method, url, data, success, fail) => {
  var request = new XMLHttpRequest()
  request.open(method, url);
  request.onreadystatechange = function () {
    if(request.readyState === 4) {
      if(request.status >= 200 && request.status < 300 || request.status === 304) {
        success(request)
      }else{
        fail(request)
      }
    }
  };
  request.send();
}

快速排序

这里对快排思想不太明白的同学可以看下这个讲解的很清晰的视频:快速排序算法

function sortArray(nums) {
  quickSort(0, nums.length - 1, nums);
  return nums;
}
function quickSort(start, end, arr) {
  if (start < end) {
    const mid = sort(start, end, arr);
    quickSort(start, mid - 1, arr);
    quickSort(mid + 1, end, arr);
  }
}
function sort(start, end, arr) {
  const base = arr[start];
  let left = start;
  let right = end;
  while (left !== right) {
    while (arr[right] >= base && right > left) {
      right--;
    }
    arr[left] = arr[right];
    while (arr[left] <= base && right > left) {
      left++;
    }
    arr[right] = arr[left];
  }
  arr[left] = base;
  return left;
}

instanceof

instanceof 来判断对象的具体类型,其实 instanceof 主要的作用就是判断一个实例是否属于某种类型 这个手写一定要懂原型及原型链。

function myInstanceof(target, origin) {
  if (typeof target !== "object" || target === null) return false;
  if (typeof origin !== "function")
    throw new TypeError("origin must be function");
  let proto = Object.getPrototypeOf(target); // 相当于 proto = target.__proto__;
  while (proto) {
    if (proto === origin.prototype) return true;
    proto = Object.getPrototypeOf(proto);
  }
  return false;
}
复制代码

数组扁平化

重点,不要觉得用不到就不管,这道题就是考察你对 js 语法的熟练程度以及手写代码的基本能力。

function flat(arr, depth = 1) {
  if (depth > 0) {
    // 以下代码还可以简化,不过为了可读性,还是....
    return arr.reduce((pre, cur) => {
      return pre.concat(Array.isArray(cur) ? flat(cur, depth - 1) : cur);
    }, []);
  }
  return arr.slice();
}

promise是什么与使用方法?

  1. 概念:异步编程的一种解决方案,解决了地狱回调的问题
  2. 使用方法:new Promise((resolve,reject) => {
    resolve(); reject();
    })
  3. 里面有多个resovle或者reject只执行第一个。如果第一个是resolve的话后面可以接.then查看成功消息。如果第一个是reject的话,.catch查看错误消息。

手写简化版 Promise

async/await 和 Promise 的关系

async 声明一个函数为异步函数,这个函数返回的是一个 Promise 对象;

await 用于等待一个 async 函数的返回值(注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。

  • async/await 是消灭异步回调的终极武器。
  • 但和 Promise 并不互斥,反而,两者相辅相成。
  • 执行 async 函数,返回的一定是 Promise 对象。
  • await 相当于 Promise 的 then。
  • tru...catch 可捕获异常,代替了 Promise 的 catch。
class Promise2 {
  #status = 'pending'
  constructor(fn){
    this.q = []
    const resolve = (data)=>{
      this.#status = 'fulfilled'
      const f1f2 = this.q.shift()
      if(!f1f2 || !f1f2[0]) return
      const x = f1f2[0].call(undefined, data)
      if(x instanceof Promise2) {
        x.then((data)=>{
          resolve(data)
        }, (reason)=>{
          reject(reason)
        })
      }else {
        resolve(x)
      }
    }
    const reject = (reason)=>{
      this.#status = 'rejected'
      const f1f2 = this.q.shift()
      if(!f1f2 || !f1f2[1]) return
      const x = f1f2[1].call(undefined, reason)
      if(x instanceof Promise2){
        x.then((data)=>{
          resolve(data)
        }, (reason)=>{
          reject(reason)
        })
      }else{
        resolve(x)
      }
    }
    fn.call(undefined, resolve, reject)
  }
  then(f1, f2){
    this.q.push([f1, f2])
  }
}
const p = new Promise2(function(resolve, reject){
  setTimeout(function(){
    reject('出错')
  },3000)
})
p.then( (data)=>{console.log(data)}, (r)=>{console.error(r)} )

手写 Promise.all

记忆题,写博客吧。

要点:

  1. 知道要在 Promise 上写而不是在原型上写
  2. 知道 all 的参数(Promise 数组)和返回值(新 Promise 对象)
  3. 知道用数组来记录结果
  4. 知道只要有一个 reject 就整体 reject
Promise.prototype.myAll
Promise.myAll = function(list){
  const results = []
  let count = 0
  return new Promise((resolve,reject) =>{
    list.map((item, index)=> {
      item.then(result=>{
          results[index] = result
          count += 1
          if (count >= list.length) { resolve(results)}
      }, reason => reject(reason) )
    })
  })
}

进一步提问:是否知道 Promise.allSettled():当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。

相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。

手写深拷贝

这个题一定要会啊!笔者面试过程中疯狂被问到!

  • 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象
  • 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象

文章推荐:如何写出一个惊艳面试官的深拷贝?

/**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 * @param {Map} map 用于存储循环引用对象的地址
 */
function deepClone(obj = {}, map = new Map()) {
  if (typeof obj !== "object") {
    return obj;
  }
  if (map.get(obj)) {
    return map.get(obj);
  }
  let result = {};
  // 初始化返回结果
  if (
    obj instanceof Array ||
    // 加 || 的原因是为了防止 Array 的 prototype 被重写,Array.isArray 也是如此
    Object.prototype.toString(obj) === "[object Array]"
  ) {
    result = [];
  }
  // 防止循环引用
  map.set(obj, result);
  for (const key in obj) {
    // 保证 key 不是原型属性
    if (obj.hasOwnProperty(key)) {
      // 递归调用
      result[key] = deepClone(obj[key], map);
    }
  }
  // 返回结果
  return result;
}

方法一,用 JSON:

const b = JSON.parse(JSON.stringify(a))

答题要点是指出这个方法有如下缺点:

  1. 不支持 Date、正则、undefined、函数等数据
  2. 不支持引用(即环状结构)
  3. 必须说自己还会方法二

方法二,用递归:

要点:

  1. 递归
  2. 判断类型
  3. 检查环
  4. 不拷贝原型上的属性
const deepClone = (a, cache) => {
  if(!cache){
    cache = new Map() // 缓存不能全局,最好临时创建并递归传递
  }
  if(a instanceof Object) { // 不考虑跨 iframe
    if(cache.get(a)) { return cache.get(a) }
    let result 
    if(a instanceof Function) {
      if(a.prototype) { // 有 prototype 就是普通函数
        result = function(){ return a.apply(this, arguments) }
      } else {
        result = (...args) => { return a.call(undefined, ...args) }
      }
    } else if(a instanceof Array) {
      result = []
    } else if(a instanceof Date) {
      result = new Date(a - 0)
    } else if(a instanceof RegExp) {
      result = new RegExp(a.source, a.flags)
    } else {
      result = {}
    }
    cache.set(a, result)
    for(let key in a) { 
      if(a.hasOwnProperty(key)){
        result[key] = deepClone(a[key], cache) 
      }
    }
    return result
  } else {
    return a
  }
}
const a = { 
  number:1, bool:false, str: 'hi', empty1: undefined, empty2: null, 
  array: [
    {name: 'frank', age: 18},
    {name: 'jacky', age: 19}
  ],
  date: new Date(2000,0,1,20,30,0),
  regex: /.(j|t)sx/i,
  obj: { name:'frank', age: 18},
  f1: (a, b) => a + b,
  f2: function(a, b) { return a + b }
}
a.self = a
const b = deepClone(a)
b.self === b // true
b.self = 'hi'
a.self !== 'hi' //true

手写数组去重

手写数组去重的12种方法 - 掘金 (juejin.cn)

  1. 使用计数排序的思路,缺点是只支持字符串
function unique(arr) {
    arr = arr.sort()
    let array= [arr[0]];
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i-1]) {
            array.push(arr[i]);
        }
    }
    return array;
}
let array=[1,2,3,1,2,'true','true',false,false,undefined,undefined,null,null,NaN,NaN,{},{}];
console.log(unique(array));// 1, 2, 3, NaN, NaN, {}, {}, false, null, "true", undefined
  1. 使用 Set(面试已经禁止这种了,因为太简单)
function unique (arr) {
    return Array.from(new Set(arr))
}
let array=[1,2,3,1,2,'true','true',false,false,undefined,undefined,null,null,NaN,NaN,{},{}]
console.log(unique(array));//  1,2,3,'true',false,undefined,null,NaN,{},{}
  1. 使用 Map,缺点是兼容性差了一点
var uniq = function(a){
  var map = new Map()
  for(let i=0;i<a.length;i++){
    let number = a[i] // 1 ~ 3
    if(number === undefined){continue}
    if(map.has(number)){
      continue
    }
    map.set(number, true)
  }
  return [...map.keys()]
}

手写事件委托

<ul id="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
 </ul>
let ul = document.querySelector('#list');
ul.addEventListener('click', function(e){
    let target = e.target;
    while( target.tagName !== 'LI' ){
           if ( target.tagName === 'UL' ){
                target = null;
                break;
           }
           target = target.parentNode;
    }
    if ( target ){
        console.log('你点击了ui里的li')
    }
})

手写 一个 div 拖拽

缕清思路

  1. 要有一个div,要为相对对位
  2. 鼠标 'mousedown' 时,标志着 可以移动啦,要记录下当前位置
  3. 鼠标 ' mousemove' 时,真正的会有位置上的移动
  4. 鼠标 'mouseup ' 时,停止移动。

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>div 拖拽</title>
</head>
<body>
<div id="xxx"></div>
</body>
</html>

style.css

*{margin:0; padding: 0;}
div{
  border: 1px solid black;
  position: absolute;
  top: 0;
  left: 0;
  width: 50px;
  height: 50px;
}

main.js 整体代码

var flag = false//是否进行拖拽的标志
var position = null
xxx.addEventListener('mousedown',function(e){
  flag = true
  //存一下当前位置信息,外层声明一个 position
  position = [e.clientX, e.clientY]
})
document.addEventListener('mousemove', function(e){//注意要监听document,否则鼠标移动快了div会掉下去。
  if(flag === false){return ;}
  const x = e.clientX
  const y = e.clientY
  // 位移 = 当前位置 - 上次位置,position[0]为上次位置的横坐标,position[1],为上次位置的纵坐标
  const deltaX = x - position[0]
  const deltaY = y - position[1]
// xxx.style.left 的值是带像素的字符串,所以用parseInt()转化为number类型,
// 当left为空的时候,parseInt('') = NaN, 所以要用 0 作为保底值
  const left = parseInt(xxx.style.left || 0)
  const top = parseInt(xxx.style.top || 0)
  xxx.style.left = left + deltaX + 'px'
  xxx.style.top = top + deltaY + 'px'
  // 保存一下当前位置
  position = [x, y]
})
document.addEventListener('mouseup', function(e){//注意要监听document,
  flag = false
})

算法题

大数相加

题目

const add = (a, b) => {
  ...
  return sum
}
console.log(add("11111111101234567","77777777707654321"))
console.log(add("911111111101234567","77777777707654321"))

答案

function add(a ,b){
  const maxLength = Math.max(a.length, b.length)
  let overflow = false
  let sum = ''
  for(let i = 1; i <= maxLength; i++){
    const ai = a[a.length-i] || '0'
    const bi = b[b.length-i] || '0'
    let ci = parseInt(ai) + parseInt(bi) + (overflow ? 1 : 0)
    overflow = ci >= 10
    ci = overflow ? ci - 10 : ci
    sum = ci + sum 
  }
  sum = overflow ? '1' + sum : sum
  return sum
}
console.log(add("11111111101234567","77777777707654321"))
console.log(add("911111111101234567","77777777707654321"))

15位加速版:

const add = (a, b) => {
  const maxLength = Math.max(a.length, b.length)
  let overflow = false
  let sum = ''
  for(let i = 0; i < maxLength; i+=15){
    const ai = a.substring(a.length-i -15, a.length-i) || '0'
    const bi = b.substring(b.length-i -15, b.length-i) || '0'
    let ci = parseInt(ai) + parseInt(bi)
    overflow = ci > 999999999999999 // 15 个 9
    ci = overflow ? ci - (999999999999999+1) : ci
    sum = ci + sum 
  }
  sum = overflow ? '1' + sum : sum
  return sum
}
console.log(add("11111111101234567","77777777707654321"))
console.log(add("911111111101234567","77777777707654321"))

其他思路:

  1. 转为数组,然后倒序,遍历
  2. 使用队列,使用 while 循环

可以自行搜索。

两数之和

题目

const numbers = [2,7,11,15]
const target = 9
const twoSum = (numbers, target) => {
  // ...
}
console.log(twoSum(numbers, target))
// [0, 1] 或 [1, 0]
// 出题者保证
// 1. numbers 中的数字不会重复
// 2. 只会存在一个有效答案

答案

const numbers = [2,7,11,15]
const target = 9
const twoSum = (numbers, target) => {
  const map = {}
  for(let i = 0; i < numbers.length; i++){
    const number = numbers[i]
    const number2 = target - number
    if(number2 in map){
      const number2Index = map[number2]
      return [i, number2Index]
    } else {
      map[number] = i
    }
  }
  return []
}
console.log(twoSum(numbers, target))

上面是给菜鸟看的,所以有多余的中间变量,可以删掉。

无重复最长子串的长度

题目

力扣

https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/

const lengthOfLongestSubstring = (str) => {
  //...
}
console.log(lengthOfLongestSubstring("abcabcbb"))
// 3

答案:滑动窗口法

我称之为「两根手指法」。

var lengthOfLongestSubstring = function(s){
  if(s.length <= 1) return s.length
  let max = 0
  let p1 = 0
  let p2 = 1
  while(p2 < s.length) {
    let sameIndex = -1
    for(let i = p1; i < p2; i++){
      if(s[i] === s[p2]){
        sameIndex = i
        break
      }
    }
    let tempMax 
    if( sameIndex >= 0){
      tempMax = p2 - p1
      p1 = sameIndex + 1
    }else{
      tempMax = p2 - p1 + 1
    }
    if(tempMax > max){
      max = tempMax
    }
    p2 += 1
  }
  return max
}

使用 map 加速:

var lengthOfLongestSubstring = function(s) {
  if (s.length <= 1)
    return s.length
  let max = 0
  let p1 = 0
  let p2 = 1
  const map = {}
  map[s[p1]] = 0
  while (p2 < s.length) {
    let hasSame = false
    if(s[p2] in map){
      hasSame = true
      if(map[s[p2]] >= p1){
          p1 = map[s[p2]] + 1
      }
    }
    map[s[p2]] = p2
    let tempMax = p2 - p1 + 1
    if(tempMax > max) max = tempMax
    p2 += 1
  }
  return max
};

你会发现,加速失败,可能是 JS 的问题。

改用 new Map() 试试:

var lengthOfLongestSubstring = function(s) {
  if (s.length <= 1)
    return s.length
  let max = 0
  let p1 = 0
  let p2 = 1
  const map = new Map()
  map.set(s[p1], 0)
  while (p2 < s.length) {
    let hasSame = false
    if(map.has(s[p2])){
      hasSame = true
      if(map.get(s[p2]) >= p1){
          p1 = map.get(s[p2]) + 1
      }
    }
    map.set(s[p2],p2)
    let tempMax = p2 - p1 + 1
    if(tempMax > max) max = tempMax
    p2 += 1
  }
  return max
};

你会发现,加速失败,这应该还是 JS 的问题。

相关文章
|
29天前
|
JavaScript 前端开发
不会还有人的批改网还是手写的把
不会还有人的批改网还是手写的把
|
8月前
01 # 手写 new 的原理
01 # 手写 new 的原理
29 0
|
1月前
|
索引
12 # 手写 findIndex 方法
12 # 手写 findIndex 方法
29 0
|
1月前
|
索引
10 # 手写 every 方法
10 # 手写 every 方法
25 0
|
1月前
|
索引
09 # 手写 some 方法
09 # 手写 some 方法
21 0
|
10月前
|
设计模式 Java Spring
用300行代码手写1个Spring框架,麻雀虽小五脏俱全
为了解析方便,我们用application.properties来代替application.xml文件,具体配置内容如下:
30 0
|
缓存 前端开发 API
手写中实现并学习ahooks——useRequest
最近业务没有之前紧张了,也是消失了一段时间,也总结了一些之前业务上的问题。 和同事沟通也是发现普通的async + await + 封装api在复杂业务场景下针对于请求的业务逻辑比较多,也是推荐我去学习一波ahooks,由于问题起源于请求,因此作者也是直接从 useRequest 开始看起。
123 1
|
监控 前端开发 JavaScript
常见手写前端面试题,看看自己能写出来多少
手写前端面试题,面试必备
34473 140
常见手写前端面试题,看看自己能写出来多少
|
11月前
|
前端开发 算法 安全
前端面试100道手写题(1)—— 手写Promise实现
今年的金三银四面试,遇到了很多新的面试八股文,其实心里对手写题或者算法题有一定抵触,因为实际工作中基本上就不会用到这些东西,但是正因为这些基础八股文,才能真正验证一个人对技术有多热爱的程度。也有可能近几年没有对这些基础知识进行巩固,所以干脆一狠心,先立个flag, 准备完成100道手写题。
216 0
|
前端开发 JavaScript
常见前端手写面试题
常见前端手写面试题
314 40

热门文章

最新文章