每日一道手写题

2,517 阅读21分钟

hello,大家好我是disguiseFish,最近发现自己的编程和算法能力相对是比较薄弱的,所以接下来打算开一个编程算法贴,每日一题,每日一更,希望在这样的过程中,自己能得到提升,同时把这个过程记录下来,感兴趣的小伙伴可以跟着一起做题提升自己

96. 判断一个数是否是整数【7月28日】

function isInter(number){
  return number%1 === 0
}

95. 输出题【7月27日】

for (let i=1; i<=5; i++) { 
  setTimeout(function timer() { 
      console.log(i);
  }, i*1000); 
}

94.ES5继承组合式继承 【7月26日】

function Parent(name) {
    this.name = name
}
  Parent.prototype.eat = function () {
    console.log(this.name + ' is eating')
  }
  
  function Child(name, age) {
    // 在子中调用父,将this指向自己
    Parent.call(this, name)
    this.age = age
  }
    // 在子级内原型和构造函数都重新指向一下, 子级原型为父级的原型,构造函数是自己
  Child.prototype = Object.create(Parent.prototype)
  Child.prototype.constructor = Child
  
  // 测试
  let xm = new Child('xiaoming', 12) 
  console.log(xm.name) // xiaoming
  console.log(xm.age) // 12
  xm.eat() // xiaoming is eating

93. 数组扁平化【7月25日】

const arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
    // 不停循环 找出数组中是数组的进行...
    while (arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}
console.log(flatten(arr)); 

92. 手写Promise.race【7月22日】

Promise.race = function(args){
    return new Promise((resolve, reject)=>{
        for (let i = 0; i < args.length; i++) {
            // 同步循环最先返回的最快先执行;Promise状态只能改变一次
            const element = args[i];
            element.then(resolve,reject)
        }
    })
}

91. 手写Object.create【7月21日】

// 以传入的对象为原型创造新对象
function create(obj) {
    // 初始化一个构造函数
    function F(){}
    // 将传入对象赋值给该构造函数的原型上
    F.prototype = obj
    // 返回该构造函数的实例
    return new F()
}

90. 实现一个冒泡排序【7月20日】

function bubbleSort(arr) {
  // 缓存数组长度
  const len = arr.length;
  // 外层循环用于控制从头到尾的比较+交换到底有多少轮
  for (let i = 0; i < len; i++) {
    // 内层循环用于完成每一轮遍历过程中的重复比较+交换
    for (let j = 0; j < len - 1; j++) {
      // 若相邻元素前面的数比后面的大
      if (arr[j] > arr[j + 1]) {
        // 交换两者
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  // 返回数组
  return arr;
}

89. 反转一个字符串【7月19日】

const str = '1234 5678'
const str1 = [...str].reverse().join('')

88.【复习】两数之和 【7月18日】

image.png

function findAnother(nums, target) {
    //思路:循环数组,罗列出当前值和对应值(目标值-当前值),在map中是否has对应值,有的话直接返回当前和对应值
    // 不存在就存进map中
    const map = new Map()
    // 依次循环传入的数组  寻找 是否存在  目标值-当前值 的值
    // i+=1 相当于 i++
    for (let i =0; i<nums.length;i++) {
      const n = nums[i]// 当前值
      const n2 = target - n// 对应值
      if(map.has(n2)) {
        return [map.get(n2), i]
      } else {
        map.set(n, i)
      }
    }
  }
  console.log(findAnother([2,3,4,5,6,7,8], 10))// [ 2, 4 ]

87. 【复习】手写 new【7月15日】

function mynew(fn, ...args){
  // 将构造函数的原型传入 创建一个新对象
  let obj = Object.create(fn.prototype)
  // 将this指向这个新对象且执行这个函数
  let res = fn.call(obj, ...args)
  // 判断类型,如果是对象或者函数直接返回这个函数,否则这个新对象
  if (res && typeof res == 'object' && typeof res === 'function') {
    return res
  }
  return obj
}

86. LRU 算法【7月14日】

题目描述:设计实现一个lru缓存机制,至少拥有get,put,功能

//  一个Map对象在迭代时会根据对象中元素的插入顺序来进行
// 新添加的元素会被插入到map的末尾,整个栈倒序查看

class LRUCache {
  constructor(capacity) {
    this.secretKey = new Map();
    this.capacity = capacity;
  }
  get(key) {
    // 获取方法,每次获取后把它重新push到栈末,然后删掉他原来的位置
    if (this.secretKey.has(key)) {
      let tempValue = this.secretKey.get(key);
      this.secretKey.delete(key);
      this.secretKey.set(key, tempValue);
      return tempValue;
    } else return -1;
  }
  put(key, value) {
    // 如果key存在,仅修改值,在原来的位置修改
    if (this.secretKey.has(key)) {
      this.secretKey.delete(key);
      this.secretKey.set(key, value);
    }
    // 如果key不存在,cache未满,如果不存在的话 直接设置
    else if (this.secretKey.size < this.capacity) {
      this.secretKey.set(key, value);
    }
    // 添加新key,栈已经超出最大长度的话,删除旧key
    else {
      this.secretKey.set(key, value);
      // 删除栈的第一个元素,即为最长未使用的
      this.secretKey.delete(this.secretKey.keys().next().value);
    }
  }
}
// let cache = new LRUCache(2);
// cache.put(1, 1);
// cache.put(2, 2);
// console.log("cache.get(1)", cache.get(1))// 返回  1
// cache.put(3, 3);// 该操作会使得密钥 2 作废
// console.log("cache.get(2)", cache.get(2))// 返回 -1 (未找到)
// cache.put(4, 4);// 该操作会使得密钥 1 作废
// console.log("cache.get(1)", cache.get(1))// 返回 -1 (未找到)
// console.log("cache.get(3)", cache.get(3))// 返回  3
// console.log("cache.get(4)", cache.get(4))// 返回  4

85. 【复习】快排序【7月13日】

function quickSort(arr) {
  // 如果数组只有0,1项就没必要排了 直接返回
  if (arr.length < 2) {
    return arr;
  }
  // 从最后一项(随机选最后一项)开始捋
  const cur = arr[arr.length - 1];
  // 把小于等于cur的 且 不是最后一项的(避免重复) 放入left
  const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1);
  // 大于cur的放入 right
  const right = arr.filter((v) => v > cur);
  // 左中右放进去,两边的数组继续递归
  return [...quickSort(left), cur, ...quickSort(right)];
}
// console.log(quickSort([3, 6, 4, 10, 2, 4, 1]));

84. 【复习】手写 instanceof【7月12日】

// 查找左边的原型链上是否和右边构造函数的prototype一致
function myInstanceof(left, right) {
  while (true) {
    if (left === null) {
      return false;
    }
    if (left.__proto__ === right.prototype) {
      return true;
    }
    left = left.__proto__;
  }
}


83.树形结构转成列表【7月11日】

 [
        {
            id: 1,
            text: '节点1',
            parentId: 0,
            children: [
                {
                    id:2,
                    text: '节点1_1',
                    parentId:1
                }
            ]
        }
    ]
    转成
    [
        {
            id: 1,
            text: '节点1',
            parentId: 0 //这里用0表示为顶级节点
        },
        {
            id: 2,
            text: '节点1_1',
            parentId: 1 //通过这个字段来确定子父级
        }
        ...
    ]
function treeTolist(list){
    let res = []
    const dfs = (tree)=>{
        // 循环传入的树形结构,如果有children直接把childrenpush进item中再删掉chidren
        tree.forEarch(item=>{
            if(item.children) {
                dfs(item.children)
                delete item.children
            }
            // 递归删处理完children后再push进去
            res.push(item)
        })
    }
    dfs(list)
    return res
}


82.大数相加【7月8日】

let a = "9007199254740991"; let b = "1234567899999999999";
function add(a ,b){
  //取两个数字的最大长度
  let maxLength = Math.max(a.length, b.length);
  //padStart,用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!==0){
     sum = '' + f + sum;
  }
  return sum;
}

81.【复习】写一个写到烂的ajax请求吧【7月7日】

const getJSON = function(url) {
  return new Promise((resolve,reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('get', url, false)
    xhr.setRequestHeader('content-type', 'application/json')
    xhr.onreadystatechange = function (){
      if(xhr.readyState == 4 && xhr.status == 200) {
        resolve()
      } else {
        reject()
      }
    }
    xhr.send()
  })
}

80.【复习】写一个方法:把一个 DOM 节点输出 JSON 的格式【7月6日】

<div>
  <span>
    <a></a>
  </span>
  <span>
    <a></a>
    <a></a>
  </span>
</div>

//把上诉dom结构转成下面的JSON格式

{
  tag: 'DIV',
  children: [
    {
      tag: 'SPAN',
      children: [
        { tag: 'A', children: [] }
      ]
    },
    {
      tag: 'SPAN',
      children: [
        { tag: 'A', children: [] },
        { tag: 'A', children: [] }
      ]
    }
  ]
}

方法:

function domToJson(domtree) {
  let obj = {};
  // 直接 从节点树上获取标签名
  obj.tag = domtree.tagName;
  obj.children = [];
  // 循环dom子节点合集  --- 对子节点的子节点进行递归再push进children中
  domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child)));
  return obj;
}

79.【复习】 写版本号排序的方法【7月5日】

有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。

现在需要对其进行排序,排序的结果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']

arr.sort((a, b)=>{
  let i = 0
  const arr1 = a.split('.')
  const arr2 = b.split('.')
  while(true) {
    const s1 = arr1[i]
    const s2 = arr2[i]
    i++
    // 有一边跑完了
    if (s1 === undefined || s2 === undefined) {
      return arr2.length - arr1.length
    } 
    if (s1 === s2) continue
    return s2-s1
  }
})

78.【复习】考到烂的节流【7月4日】

function throttle(fn, delay){
  // 初始flag为true
    let flag = true
    return ()=>{
      // flag为false/开启过定时器 的话直接退出、delay时间内只执行一次
      if(!flag) return
      // 开启定时器前false它
      flag = false
      // 开启定时器
      timer = setTimeout(()=>{
        // 执行函数 flag设为true
        fn()
        flag = true
      },delay)
    }
}

77.【复习】考到烂的防抖【7月1日】

function debounce(fn, delay = 300) {
  let timer
  return function(){
    const args = arguments
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay);
  }
}

76.【复习】手写 new【6月30日】

function mynew (fn, ...args){
  // 以传入的构造函数 创建新对象
  let obj = Object.create(fn.prototype)
  //  将this指向新对象,且执行该对象
  let res = fn.call(obj, ...args)
  // 根据类型判断返回值
  if(res && (typeof res === 'object' || typeof res === 'function')) {
    return res
  }
  return obj
}

75.寄生组合继承【6月29日】

又是想摆烂的一天

// 创建一个父
function Parent(name){
  this.name = name
  this.say = () => {
    console.log('function say')
  }
}
// 给 父的原型挂载方法
Parent.prototype.play = () =>{
  console.log('function play')
}
// 子元素 内部调用父元素
function Child(name){
  Parent.call(this)
  this.name = name
}
// 用Object.create以父原型为原型创建对象赋值给子原型
Child.prototype = Object.create(Parent.prototype)
// 将子赋值给自己原型上的构造函数  -- 形成一个完整继承
Child.prototype.constructor = Child

74.爬楼梯【6月28日】

image.png

image.png

73.翻转一个字符串【6月27日】

let str="hello word";
let result = [...str].reverse().join('')

72.实现对象扁平化【6月24日】

// 平时考察的是数组扁平化,这里考察的是对象扁平化
function flat(obj, key="", res = {}, isArray = false){
  // Object.entries返回一个给定对象自身可枚举属性的键值对数组
  // 依次循环键值对数组,拿到键和值
  for(let [k, v] of Object.entries(obj)){
    // 判断值是不是数组,对象,或者简单数据类型, 对三种不同的数据类型进行不同的处理
    if(Array.isArray(v)) {
      // 是否是数组,是的话再根据索引拼接成[值],否则拼接成  tmp+值
      let tmp = isArray ? key + '['+k+']' : key + k
      // 递归flat,传入相应参数
      flat(v, tmp, res, true)
    } else if (typeof v === 'object') {
      // 对象也是拼接成需要的格式,然后递归, 对象的话就比数组多一个.
      let tmp = isArray ? key + '[' + k +'].' : key + k + '.'
      flat(v, tmp, res)
    } else {
      // 普通数据类型判断如果是数组的话直接拼接[],否则直接 tmp+值
      let tmp = isArray ? key + "[" + k + "]" : key + k
      // 直接给tmp赋值
      res[tmp] = v
    }
  }
  // 返回结果!
  return res
}
const obj = {a:1, b:{c:{d:2, e:[3,4,5]}}}
console.log(flat(obj))//{ a: 1, 'b.c.d': 2, 'b.c.e[0]': 3, 'b.c.e[1]': 4, 'b.c.e[2]': 5 } 

71.请实现一个 find 函数,功能等同于 document.getElementById(阿里题)【6月23日】

请实现一个 find 函数,功能等同于 document.getElementById

考察点:对DOM的一些原生操作的熟练度

// 获取所有节点
const nodes = document.body
// 写一个获取id的函数
function getId(node, nodeIdMap) {
  // 如果有node/例子中是传入所有节点
  if(node) {
    // 将节点赋值给 map中对应节点的id键
    if(node.id)nodeIdMap[node.id] = node
    // 把子集赋值给children
    const children  = node.children
    // 循环children,递归该方法赋值,最后返回结果
    for(let i = 0;i<children.length;i++){
      getId(children[i], nodeMap)
    }
  }
  return nodeIdMap
}
// 获取所有的id
const ids = getId(nodes,{})
// 从所有id中获取特定id
const findId = (id) => ids[id]
// 用法:
findId('sepcialId')

70.实现字符串的repeat方法【6月22日】

输入字符串s,以及其重复的次数,输出重复的结果,例如输入abc,2,输出abcabc。

function repeat(s,n) {
  // new Array()创建了一个对象,新建的对象a.__proto__ == Array.prototype。这是标准的一个由Class到实例的创建步骤。
  // new Array(n+1)是创建n+1个有预留字段但没值的array,
  // 通过join往空数组里的每一项填入值  实现repeat!
  return (new Array(n+1)).join(s)
}
console.log(repeat('shelly', 2))//'shellyshelly'

69.实现maxRequest,成功后resolve结果【6月21日】

实现maxRequest,成功后resolve结果,失败后重试,尝试超过一定次数才真正的reject

function maxrequest(fn, max){
  // 返回一个promix
  return new Promise((resolve,reject)=>{
    // 写一个方法,接收最大请求次数
    function help(index){
      Promise.resolve(fn().then(value=>{
        // 成功的话直接resolve
        resolve(value)
      }).catch(err=>{
        // 失败就直接给index-1 再递归
        if(index-1>0) {
          help(index-1)
        } else {
          // 如果最后一项了就reject返回
          reject(err)
        }
      }))
    }
    help(max)
  })
}

68.lru算法【6月20日】

class LRU{
  constructor(max){
    this.max = max
    this.cache = new Map()
  }
  // 从缓存中拿到值,再删掉对应的值,再把该值存进去
  // 这样保证近期取过的值在最外
  get(key) {
    const {cache} = this
    const value = cache.get(key)
    cache.delete(key)
    cache.set(key, value)
    return value
  }
  // 设定值的时候,如果缓存中存在该值先删再存
  // 如果不存在,但cache中超过最大长度的话则删掉cache中最后的那个值
  set(key, value) {
    const {cache, max} = this
    if(cache.has(key)) {
      cache.delete(key)
    }
    if(cache.size === max) {
      cache.delete(cache.keys().next().value)
    }
    cache.set(key, value)
  }
}

67.数组去重【6月17日】

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8]; 
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]

66.数组的扁平化【6月16日】

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
  while(arr.some(item => Array.isArray(item))) {
    arr = [].concat(...arr)
  }
  return arr
}
console.log(flatten(arr)); //  [1, 2, 3, 4,5]

直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:arr.flat([depth])

65.实现arr=[1,2,3,[[4,5],6],7,8,9],求和【6月15日】

arr=[1,2,3,[[4,5],6],7,8,9]
const sum = arr.toString().split(',').reduce((sum, next)=> sum += Number(next), 0)
console.log(sum)

64.实现arr=[1,2,3,4,5,6,7,8,9,10],求和【6月14日】

const arr = [1,2,3,4,5,6,7,8,9,10]
const sum = arr.reduce((pre, i) => pre += i, 0)
console.log(sum)

63.实现数组的乱序输出【6月13日】

var arr = [1,2,3,4,5,6,7,8,9,10];
for (let i = 0; i < arr.length; i++) {
  // Math.round四舍五入,如果是负数会朝正数进
  // Math.random 取[0,1)之间的随机数
  // arr.length-1-i 数组长度-第i项  再加 i  = 随机索引(保证唯一性)
  const randomindex = Math.round(Math.random()*(arr.length-1-i)) + i;
  [arr[i],arr[randomindex]] = [arr[randomindex], arr[i]]
}
console.log(arr)

62.交换a,b的值,不能用临时变量【6月10日】

巧妙的利用两个数的和、差:

a = a + b 
b = a - b 
a = a - b

61.实现深拷贝【6月9日】

  1. JSON.stringify() 存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。
let obj1 = {  a: 0,
              b: {
                 c: 0
                 }
            };
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}

  1. lodash的_.cloneDeep方法

  2. 手写实现深拷贝函数

// 不考虑symbol的版本
function deepcopy(object){
  // 是null或者不是对象就直接return 
  if (!object || typeof object !== 'object') return 
  // 初始化
  let newObject = Array.isArray(object) ? [] : {}
  for(let key in object) {
    // 在object中是否存在非原型链上的key值, 存在则根据该项是否是对象给newObject赋值,如果还是对象的话则递归深拷贝
    if(object.hasOwnProperty(key)) {
      newObject[key] = typeof object[key] === 'object' ? deepcopy(object[key]) : object[key]
    }
  }
  return newObject
}

60.实现浅拷贝【6月8日】

  1. Object.assign() 因为null 和 undefined 不能转化为对象,所以第一个参数不能为null或 undefined,会报错。

  2. Object.assign()

  3. 扩展运算符

let obj1 = {a:1,b:{c:1}}
let obj2 = {...obj1};
console.log(obj2); //{a:1,b:{c:1}}
  1. 数组方法
// Array.prototype.slice
let arr = [1,2,3,4];
console.log(arr.slice()); // [1,2,3,4]
console.log(arr.slice() === arr); //false

Array.prototype.concat
let arr = [1,2,3,4];
console.log(arr.concat()); // [1,2,3,4]
console.log(arr.concat() === arr); //false

等等.......

59.柯里化的实现【6月7日】

柯里化指的是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

function curry(fn, args){
  let length = fn.length
  args = args || []
  return function(){
    let subArgs = args.slice(0)
    for(let i = 0;i<arguments.length;i++){
      // 把参数挨个push进subArgs去
      subArgs.push(arguments[i])
    }
    // 若subArgs长度和函数所需长度一样则执行函数
    if(subArgs.length >= length) {
      return fn.apply(this, subArgs)
    } else {
      // 否则递归
      return curry.call(this, fn, subArgs)
    }
  }
}

// es6写法:
function curry(fn,...args){
  // 如果所需长度小于等于args长度则 执行函数 ,否则
  return fn.length <= args.length ? fn(...args) : curry.bind(null,fn,...args)
}

58.手写类型判断函数【6月6日】

function getType(value) {
  // 如果是null 直接返回 字符串null
  if (value === null) {
    return value + ""
  }
  // typeof可以检测出简单数据类型,对对象和null都会检测得object,所以先判断是不是null,然后再处理是不是object
  if (typeof value === 'object') {
    // 直接通过对象上原型的方法拿到类型,会得到一个字符串,再通过‘ ’分割成一个数组取后面一项
    let valueClass = Object.prototype.toString.call(value)// [object Object]
    type = valueClass.split(" ")[1].split("")//['O', 'b', 'j','e', 'c', 't',']']
    type.pop()// 去掉 ]
    return type.join("").toLowerCase()// 返回 小写字符串
  } else {
    // 如果是简单数据类型 直接用typeof判断
    return typeof value
  }
}
console.log(getType({a:1}))

57.手写 Promise.then【6月2日】

then 方法返回一个新的 promise 实例,为了在 promise 状态发生变化时(resolve / reject 被调用时)再执行 then 里的函数,我们使用一个 callbacks 数组先把传给then的函数暂存起来,等状态改变时再调用。

function then(onFullfilled, onReject){
  // 保存当前this
    const self  = this
    return new MyPromise((resolve, reject)=>{
      // 封装前一个成功的执行函数
       let fulfilled = () =>{
         try{
          const result = onFullfilled(self.value)
          return result instanceof MyPromise ? result.then(resolve,reject) : resolve(result)
         } catch(err) {
          reject(err)
         }
       }
      //  封装前一个失败时执行的函数
       let rejected = () => {
         try {
          const result = onReject(self.reason)
          return result instanceof MyPromise ? result.then(resolve,reject) : reject(result)
         } catch(err){
          reject(err)
         }
       }
      //  多个 then 里的回调方法是同步注册的,但注册到了不同的 callbacks 数组中
      // 根据不同的状态执行对应的方法
       switch(self.status) {
        //  pending情况 把成功的执行函数都注册到了不同的 callbacks 数组中
         case PENDING:
           self.onFullfilledCallbacks.push(fulfilled);
           self.onRejectedCallbacks.push(fulfilled);
           break;
        //  如果成功的话就执行成功的方法
         case FULFILLED:
           fulfilled()
           break
        //  如果失败的话就执行失败的方法
         case REJECT:
           rejected()
           break
       }
    })
}

56.手写 Object.create【6月1日】

// Object.create()是将传入的对象作为原型来实现创建对象
function mycreate(obj){
  function F(){}
  F.prototype = obj
  return new F()
}

55.settimeout 模拟实现 setinterval【5月31日】

function mySettimeout(fn, t) {
    let timer = null;
    function interval() {
      fn();
      // 开启一个定时器,t后执行interval,在interval里又是t之后执行interval
      // 如此依赖就形成了一个setinterval功能
      timer = setTimeout(interval, t);
    }
    interval();
    // 返回具有清除定时器的方法
    return {
      cancel:()=>{
        clearTimeout(timer)
      }
    }
  }


54. [复习]二分查找【5月30日】

题目描述:如何确定一个数在一个有序数组中的位置

function search(arr, target, start, end) {
  // 初始化一个目标index 以及 获取一个中间值
  let targetIndex = -1;
  let mid = Math.floor((start + end) / 2);
  // 然后分三个情况  
  // 如果目标值等于中间值 则确定中间index 返回目标index
  if (arr[mid] === target) {
    targetIndex = mid;
    return targetIndex;
  }
  // 如果开始大于结束值 则不存在 返回-1
  if (start >= end) {
    return targetIndex;
  }
 //  如果目标值大于/小于中间值 则继续回调
  if (arr[mid] < target) {
    return search(arr, target, mid + 1, end);
  } else {
    return search(arr, target, start, mid - 1);
  }
}
// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// const position = search(dataArr, 6, 0, dataArr.length - 1);
// if (position !== -1) {
//   console.log(`目标元素在数组中的位置:${position}`);
// } else {
//   console.log("目标元素不在数组中");
// }

53. 【3】无重复字符的最长子串【5月27日】

image.png 解题思路:

  1. 先找出所有不包含重复字符的字符串
  2. 再找出最长的那个!
  3. 冲冲冲
var findStr = function (s){
  let flag = 0
  let res = 0
  const map = new Map() // 判断是否重复
  for(let r = 0;r<s.length;r+=1) {
    let current = s[r]
    // 如果在map中存在当前值 且 当前值的index大于等于 flag索引
    // 说明 重复了 则可以给flag移位一下
    // 把flag往后移到当前项的后1位
    if (map.has(current) && map.get(current)>=flag){
      flag = map.get(current) + 1
    }
    // r- flag + 1:当前index-flag位置+1
    // 比较一下res和这次循环不重复的长度  把大的赋值给res
    res = Math.max(res, r- flag + 1)
    // 在map中存入当前值和对应index
    map.set(current, r)
  }
  return res
}
console.log(findStr('abcabcbb'))// 3
console.log(findStr('bbbbbbb'))// 1
console.log(findStr('pwwkew'))// 3

52. [复习]有效括号【5月26日】

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。

image.png

function isValidate(str){
  if(str.length % 2 === 1) {return false}
  const stack = []
  const map = new Map()
  map.set('(',')')
  map.set('[',']')
  map.set('{','}')
  for(let i = 0;i<str.length;i++){
    const c = str[i]
    if (map.has(c)){
      // 把左括号依次存进去
      stack.push(c)
    } else {
      // 右括号
      // 用左括号栈中最后一位去map中寻找对应的右括号
      // 如果该右括号和当前项一样 则把它推出栈
      if (map.get(stack[stack.length-1]) === c) {
        stack.pop()
      } else {
        return false
      }
    }
  }
  //  如果栈被清空则 true
  return stack.length === 0
}

console.log(isValidate('()'))
console.log(isValidate('[]'))
console.log(isValidate('()[]{}'))
console.log(isValidate('[])'))
console.log(isValidate('([)]'))

51. 两数之和【5月25日】

image.png

function findAnother(nums, target) {
  // 初始化一个对象
  const map = new Map()
  // 依次循环传入的数组  寻找 是否存在  目标值-当前值 的值
  // i+=1 相当于 i++
  for (let i =0; i<nums.length;i+=1) {
    const n = nums[i]// 当前值
    const n2 = target - n// 寻找的补齐值
    if(map.has(n2)) {
      // 如果能找到 直接返回 补齐Index和当前Index
      return [map.get(n2), i]
    } else {
      // 如果找不到就把 当前值和index 存进map中
      map.set(n, i)
    }
  }
}
console.log(findAnother([2,3,4,5,6,7,8], 10))// [ 2, 4 ]


50. 数组去重【5月24日】

function unique (arr) {
  arr = arr.sort(); // 排序之后的数组
  let pointer = 0;
  while (arr[pointer]) {
    if (arr[pointer] != arr[pointer + 1]) { // 若这一项和下一项不相等则指针往下移
      pointer++;
    } else { // 否则删除下一项
      arr.splice(pointer + 1, 1);
    }
  }
  return arr;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(unique(arr))


49. 数组去重【5月23日】

function unique (origin) {
  let arr = [].concat(origin);
  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);
        j--;
      }
    }
  }
  return arr;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(unique(arr))

48. Promise.all【5月20日】

hhhh今天继续promise,promise三件套!

Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个[Promise]实例,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且reject的是第一个抛出的错误信息。如果全部都resolve会按顺序返回结果数组

// 依次循环执行传入的promise回调,resolve后count++ 且存到arr中
// 当都执行完了resolve抛出
Promise.MyAll = function (promises) {
  let arr = [],
  count = 0
  return new Promise((resolve, reject) => {
    promises.forEach((item, i) => {
      Promise.resolve(item).then(res => {
        arr[i] = res
        count += 1
        if (count === promises.length) resolve(arr)
        // 有一个reject就直接reject
      }, reject)
    })
  })
}

47. Promise.race【5月19日】

没错儿,,今天还是promise方法!(有点偷懒了)race的译文为赛跑,即采用最快的那一个,race方法如果其中一个完成了,其他的还是会执行的,只是并没有采用它的结果

Promise.race = function (values) {
 return new Promise((resolve, reject) => {
 // 循环传入的values,如果有值且是个promise(说明成功)直接返回
   for (let i = 0; i < values.length; i++) {
     const current = values[i]
     if (isPromise(current)) {
       current.then(resolve, reject)//一旦成功就直接停止
    } else {
       resolve(current)
    }
  }
})
}


46. Promise.allSettled【5月18日】

这个方法是一个静态方法,当无论接口返回是成功还是失败的时候都想拿到对应顺序的结果时,就可以使用这个方法!

Promise.allSeleted = function(promises) {
    let count = 0
    let result = []
    //还是直接返回一个promise,内部循环传入的回调,
    return new Promise((resolve, reject) => {
        promises.forEach((promise, index) => {
        // 依次执行,无论成功还是失败在对应的位置放上对应的结果
            Promise.resolve(promise).then(res => {
                result[index] = {
                    value: res,
                    reason: null,
                }
            }, err => {
                result[index] = {
                    value: null,
                    reason: err,
                }
                // 最后长度等于累加数后/ 也就是都执行完后 返回结果
            }).finally(() => {
                count++
                if (count === promises.length) {
                    resolve(result)
                }
            })
        })
    })
}

46. 防抖【5月17日】

今天踩了个坑,在项目里引进lodash使用防抖的时候,发现报错,报错信息说找不到lodash,,,然后我删了安装包又重新安装依赖项。。。还是没用。。

找报错原因最后找到因为开启了预编译(mfsu)和webpack5.。更底层原因有没有小伙伴来说说?

关掉后,不报错了, 但是使用lodash的debounce没报错也没起作用,,然后自己手写了一个防抖也一样的结果,最后找到原因:react不能像vue那样直接使用,react会重复执行这个debounce,存在闭包问题。。得找找hook适用的防抖方式,,最后找到两种适用的,,,

// 默认防抖
function debounce(fn, delay = 300) {
    //默认300毫秒
    let timer;
    return function () {
      const args = arguments;
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(() => {
        fn.apply(this, args); // 改变this指向为调用debounce所指的对象
      }, delay);
    };
  }
  window.addEventListener(
    "scroll",
    debounce(() => {
      console.log(111);
    }, 1000)
  );

hook版

//
// util.ts
export const useDebounce(msg) {
    const [postMsg, setPostMsg] = useStat(msg)
    useEffect(()=>setPostMsg(msg), [msg])
    return postMsg
}

// 组件.tsx
import {useDebounce} from '../util'
...
const [msg, setMsg] = useState('')
const postMsg = useDebounce(msg)
useEffect(()=>{
    执行postMsg网络请求
}, [postMsg])
...
return <div>
    <input type="text" onInput={(e)=>setMsg(e.target.value)} />
    <p>你输入了{msg}</p >
</div>
// 这个版本比较简单,,,就是套了一层useCallback,,button调接口版
// 要注意的是,hook中useXXX都得写在函数内调用,不能在函数内的函数内调用 这样会报错的!
// 比如下面的debounceF得const debounceF = useXXX,不能写成function debounceF (){....}
const fn = () => {
    console.log("执行了");
  };

  function debounce(callback, delay) {
    let timer = null;
    return function (...args) {
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout((...args) => {
        callback.apply(this, args, delay);
      }, delay);
    };
  }

  const debounceF = useCallback(debounce(fn, 1000), []);

  return (
    <div className="App">
      <button onClick={() => debounceF() }>点我触发防抖</button>
    </div>
  );

45. promiseAll的实现【5月16日】

当然不用实现promise了啦~~ promiseAll还是比较简单的~

不过要注意啦!! promiseAll 是一个静态方法! 不是实例方法哦!

特点就是等所有回调都成功了才会按顺序返回结果!吐出来一个数组!一个失败全部失败!

Promise.all = function (promises) {
  const values  = []
  let count = 0// 计时器,如果计时器等于传入请求总数 则说明请求都执行成功了 可以返回出去啦!
  return new Promise((resolve, reject)=>{
    promises.forEach((promise, index)=>{
      Promise.resolve(promise).then(res=>{
        count++
        values[index] = res
        if (count === promises.length){
          return resolve(values)
        }
      },err=>{
        reject(err)
      })
    })
  })
}

44. 反转链表【5月13日】

image.png 先分析链表中两个节点如何翻转:只用将n+1的next指针指向n

想反转整个链表也是可以通过反转两个节点的方法来处理

两个指针遍历链表,也就是双指针遍历链表,然后再重复上面的两个节点反转的操作

单指针遍历链表:1->2->3->4,1的指针指向2,2的指针指向3,3的指针指向4

双指针链表:一个指针指向2,一个指针指向1,指向2的这个指针下一步指向3,指向1的指针下一步指向了2;直到靠前的指针走到了链表的尽头;所以只需要在遍历的过程中两个节点这么反转就能就能把整个链表反转完了

function reverseList(list){
  let p1 = list
  let p2 = null
  while(p1){
    // 把链表 前一个的next存下来 之后交换给上一个
    const tmp = p1.next
    // 把下一个存给上一个的next
    p1.next = p2
    // 前后两个交换
    p2 = p1
    // 把暂存的 给上一个
    p1 = tmp
  }
  return p2
}

// 使用
let list = {val: 1, next:{
  val:2,next:{
    val:3, next:{
      val:4,next:{
        val:5,next:null
      }
    }
  }
}}
console.log(reverseList(list))

44. 有效括号【5月12日】

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。

image.png

var isValid = function(s){
    // 如果奇数长度直接false
    if(s.length % 2 === 1) {return false}
    const stack = []
    const map = new Map()
    map.set('(',')')
    map.set('{','}')
    map.set('[',']')
    console.log(map)//  { '(' => ')', '{' => '}', '[' => ']' }
    for(let i =0;i < s.length;i+=1){
      const c = s[i]
      if(map.has(c)){
        stack.push(c)
      } else {
        // 用栈中最后一项去查map查出的右括号  等于  当前项(右括号)  那么把最后一项出栈
        const t = stack[stack.length-1]
        if(map.get(t) === c) {
          stack.pop()
        } else {
          // 如果不等于 那括号就不对了
          return false
        }
      }
    }
    // 如果栈为空则代表true
    return stack.length === 0
}
console.log(isValid('()'))
console.log(isValid('[]'))
console.log(isValid('()[]{}'))
console.log(isValid('[])'))
console.log(isValid('([)]'))

43. 实现一个快排【5月11日】

function quick(arr) {
  if(arr.length<2){
    return arr
  }
  const cur = arr[arr.length - 1]
  // 过滤掉cur 且不是最后一项
  const left = arr.filter((v, i) => v <= cur && i !== arr.length -1)
  const right = arr.filter((v)=>v>cur)
  // 根据位置进行排序
  return [...quick(left),cur,...quick(right)]
}
console.log(quick([2,3,4,5,6,1]))

42. (复习)实现一个插入排序【5月10日】

// 双层循环

// 前后依次比较,如果当前项大于上一项的话则交换位置 再-- 继续比较
function insertSort(arr){
  for (let i = 0; i < arr.length; i++) {
    let j = i
    let target = arr[i]
    while(j>0 && arr[j-1] > target) {
      arr[j] = arr[j-1]
      j--
    }
    arr[j]=target
    
  }
  return arr
}

console.log(insertSort([3, 6, 2, 4, 1, 99]));

41. (复习)求数组中第二大的数【5月9日】

最近太忙了

手写选了一些曾经的题复习

function second(arr){
  return arr.sort((a,b)=>b-a)[1]
}

40. (复习)冒泡【5月7日】

function bubbleSort(arr){ 
// 拿到数组长度 
const length = arr.length 
// 冒泡是双层for循环 
// 外层是从头到尾的比较,交换多少轮 
for(let i = 0;i<length;i++){ 
// 内层是每一轮遍历过程中的重复比较和交换位置 
    for (let j = 0; j < length-1; j++) { 
        if(arr[j] >arr[j+1]){ 
            [arr[j],arr[j+1]] = [arr[j+1],arr[j]] 
         } 
      } 
    } 
    return arr 
} 

console.log(bubbleSort([3, 6, 2, 4, 1]));

39. (复习)实现一个单例模式【5月6日】

function Single(){

}
Single.getInstance = (function(){
    let instance = null
    return function(){
        if (!instance) {
            instance = new Single()
        }
        return instance
    }
})()

38. (复习)实现模板字符串解析功能【5月5日】

// \w是匹配字母、数字、下划线 
// +就是匹配一个以上 
// 一个\ 后面 则代表 转义\后面的这个 
function render(template, data) {
    let reg = /\{\{(\w+)\}\}/g
    let computed = template.replace(reg, function (match, key) {
        console.log(match, key) // {{name}} name     {{age}} age     {{sex}} sex
      return data[key];
    });
    return computed;
}

// 使用
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
console.log(render(template, data)); // 我是姓名,年龄18,性别undefined

37. 分片思想解决大数据量渲染问题【4月29日】

题目描述:渲染百万条结构简单的大数据时 怎么使用分片思想优化渲染

let ul = document.getElementById("container");
// 十万条数据 一页20条
let total = 100000;
let size = 20;
let page = total / size;
//每条记录的索引
let index = 0;
//主要处理方法
function loop(curTotal, curIndex) {
  // 1 0页的话直接退出
  if (curTotal <= 0) return false;
  //2 每页展示多少条-- 看展示一页20条还是如果剩余展示条数不到20的话展示剩余条数  所以拿这两个作比较 取较小值
  let pageCount = Math.min(curTotal, size);
  window.requestAnimationFrame(function () {
    // 3 使用RAF优化加载 循环要展示条数  依次把内容appendChild进去
    for (let i = 0; i < pageCount; i++) {
      let li = document.createElement("li");
      // ~~ 可以把字符串转成数字 and  可以把数字的小数点去掉
      li.innerText = curIndex + i + " : " + ~~(Math.random() * total);
      ul.appendChild(li);
    }
    // 4 接着要去渲染下一页 -- 递归 --> loop(当前剩余总数-当前页展示数,当前数总Index值+当前页展示数(此处只是为了填充text文本,没多大用))
    loop(curTotal - pageCount, curIndex + pageCount);
  });
}
loop(total, index);

该方法可以用于渲染简单数据渲染,如果是复杂数据表格的话推荐使用虚拟列表实现

36. (复习)输入整数数组arr,找出其中最小的k个数,输出的位置保持不变【4月28日】

function findItem(arr, k) {
 // 1.先深拷贝 再正序排序 接着截取前k位
  let sortedK = arr.slice(0).sort((a, b)=>(a-b)).slice(0,k)
 // 2.循环原数组,利用includes ,如果在原数组中有sortedK的值则push进目标数组
  let resArr = []
  arr.forEach(item => {
    if(sortedK.includes(item)){
      resArr.push(item)
    }
  })
  return resArr
}
console.log(findItem([11,2,3,1,10,4], 3))

35.(复习)版本号排序【4月27日】

题目描述:有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。现在需要对其进行排序,排序的结果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']

let arr = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.5.5', '4.3.4.5']
function resortArr(arr){
  arr.sort((a, b) => {
    // a, b -> next, current  a-b 正序排序(越来越大)
    let i = 0;
    // 1. 把传入的每一项 切成数组比对
    const nextArr = a.split(".");
    const curretArr = b.split(".");
    while (true) {
      // 2. 先比对第一项[0]  依次循环往后比对 如果有undefined说明有一项没值了直接,这把是倒叙 所以( b-a )-> (cur - next)
      const nextItem = nextArr[i];
      const curItem = curretArr[i];
      i++;
      
      if (nextItem === undefined || curItem === undefined) {
        return curretArr.length - nextArr.length;
      }
       //   3. 如果下一个和现在的 一样 则不排序  继续往后移动指针比对
      if (nextItem === curItem) continue;
       //   4. 如果都有值,且不一样的话 直接比对  因为是倒叙 所以b-a
      return curItem - nextItem;
    }
  });
  return arr
}
console.log(resortArr(arr))

34.二分查找【4月26日】

题目描述:如何确定一个数在一个有序数组中的位置

  // 初始化一个目标index 以及 获取一个中间值
  let targetIndex = -1;
  let mid = Math.floor((start + end) / 2);
  // 然后分三个情况  
  // 如果目标值等于中间值 则确定中间index 返回目标index
  if (arr[mid] === target) {
    targetIndex = mid;
    return targetIndex;
  }
  // 如果开始大于结束值 则不存在 返回-1
  if (start >= end) {
    return targetIndex;
  }
 //  如果目标值大于/小于中间值 则继续回调
  if (arr[mid] < target) {
    return search(arr, target, mid + 1, end);
  } else {
    return search(arr, target, start, mid - 1);
  }
}

// const dataArr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// const position = search(dataArr, 6, 0, dataArr.length - 1);
// if (position !== -1) {
//   console.log(`目标元素在数组中的位置:${position}`);
// } else {
//   console.log("目标元素不在数组中");
// }

33.实现一个插入排序【4月25日】

对于少量元素的排序,它是一个有效的算法。实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。

function insertSort(arr){
  // 除去第0项  从第1项起
  for(let i = 1;i<arr.length;i++){
    let j = i
    let target = arr[i]
    // 如果j-1项大于第i项 --> 说明上一项大于本次项,本次的小
    while(j>0 && arr[j-1] > target){
      // 把小的放前面
      // 把大的给当前项 再j-- 接着把当前项的值给上一项,就实现了一个小值在前的排序
      arr[j] = arr[j-1]
      j--
    }
    arr[j] = target
  }
  return arr
}
console.log(insertSort([3, 6, 2, 4, 1, 99]));

32.实现一个选择排序【4月24日】

选择排序工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。

function selectSort(arr) {
    // 拿到总长度
    const len = arr.length;
    // 定义当前范围 的 最小值索引 minIndex
    let minIndex;
    // i 是当前排序区间的起点
    for (let i = 0; i < len - 1; i++) {
      // 初始化 minIndex 为当前区间第一个元素
      minIndex = i;
      // i是左边界,j是右边界
      for (let j = i; j < len; j++) {
        // 若 j 处更小,则更新最小值索引为 j
        if (arr[j] < arr[minIndex]) {
          minIndex = j;
        }
      }
      // 如果 minIndex 不是目前的头部元素,则交换两者
      if (minIndex !== i) {
        [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
      }
    }
    return arr;
  }
  console.log(selectSort([3, 6, 2, 4, 1]));

31.冒泡排序【4月22日】

冒泡排序:重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。

function bubbleSort(arr){
    // 拿到数组长度
    const length = arr.length
    // 冒泡是双层for循环
    // 外层是从头到尾的比较,交换多少轮
    for(let i = 0;i<length;i++){
        // 内层是每一轮遍历过程中的重复比较和交换位置
        for (let j = 0; j < length-1; j++) {
            if(arr[j] >arr[j+1]){
                [arr[j],arr[j+1]] = [arr[j+1],arr[j]]
            }
        }
    }
    return arr
}
console.log(bubbleSort([3, 6, 2, 4, 1]));

30.柯里化 【4月21日】

题目描述:柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数函数,依次处理剩余的参数。

function currying(fn,...args){
    let length = fn.length
    let allArgs = [...args]
    const res = (...newArgs) => {
        // 把每次传入的参数和之前的参数合并
        allArgs = [...allArgs,...newArgs] 
        // 如果总参数项等于函数的参数长度,最后一个函数的参数已经加进去了 然后调用函数
        if (allArgs.length === length) {
            return fn(...allArgs)
        } else {
            return res
        }
    }
    return res
}

// 用法如下: 
// const add = (a, b, c) => a + b + c;
// const a = currying(add, 1);
// console.log(a(2,3))

29. [0, 2, 3, 0, 5, 0, 0]将0全部移动到后面去【4月20日】

function test(nums){
    // 双指针循环 初始位置都是0
    let fast = 0;
    let slow = 0;
    while (fast < nums.length) { // 快指针循环至数组末位 则停止循环
        if (nums[fast] != 0) { // 如果快指针指向的值不是0 则快慢指针交换位置
            [nums[fast], nums[slow]] = [nums[slow], nums[fast]];
            slow++;
        }
        fast++;
    }
    return nums;
}
// console.log(test([0, 2, 3, 0, 5, 0, 0]))

28. 寄生组合继承【4月19日】

function father(){

}
father.prototype.play = () =>{

}
function child(){
    father.call(this)
}
child.prototype = Object.create(father.prototype)
child.prototype.constructor = child

27.数组去重【4月18日】

function unique(arr){
    return [...new Set(arr)]
}

26.数组扁平化【4月15日】

题目描述:实现一个方法使多维数组变成一维数组

    // 如果数组为空直接return
    if (!arr.length) return;
    // 如果arr中存在数组项 则...解出来给arr 
    while (arr.some((item) => Array.isArray(item))) {
        console.log('...',arr, ...arr)
        // 拍平最外一层后继续循环查看是否还存在数组,如果存在继续拍平最外一层
        arr = [].concat(...arr);
    }
    return arr;
}
console.log(flatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]))

25.复习14题--sleep函数【4月14日】

function sleep(time){ 
    return new Promise((resolve, reject)=>{ 
        setTimeout(resolve(),time*1000) }) 
     } 
    )}
     console.log('start') 
     sleep(3).then(res=>{ console.log('end') })

24.复习第八题 深拷贝!【4月13日】

function isObject(val) {
  return typeof val === "object" && val !== null;
}

function deepClone(obj, hash = new WeakMap()) {
  if (!isObject(obj)) return obj; // 不是对象直接返回
  if (hash.has(obj)) {
    return hash.get(obj);// 如果hash中有值的话,从hash中获取值,防止循环引用
  }
  let target = Array.isArray(obj) ? [] : {};// 根据传入的数据类型设置目标初始值
  hash.set(obj, target);// 在hash中设置值,防止循环引用
   // 映射 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组
  Reflect.ownKeys(obj).forEach((item) => {
    if (isObject(obj[item])) { // 如果对应键的值是对象的话递归
      target[item] = deepClone(obj[item], hash);
    } else {// 是简单数据类型的话 直接赋值
      target[item] = obj[item];
    }
  });

  return target;
}

23.settimeout 模拟实现 setinterval【4月12日】

settimeout:固定时间后执行一次回调

setinterval:每固定时间间隔执行一次回调

function mySettimeout(fn, t) {
    let timer = null;
    function interval() {
      fn();
      timer = setTimeout(interval, t);
    }
    interval();
    // 返回具有清除定时器的方法
    return {
      cancel:()=>{
        clearTimeout(timer)
      }
    }
  }

22.实现一个函数【4月11日】

function fun( ? ) {
    return {a,b}
}
console.log(fun( )) // {a:1,b:2}
console.log(fun({a:3})) // {a:3,b:456}
console.log(fun({})) // {a:123,b:456}

答案:

function fun({a=123, b=456} = {a:1 ,b:2}) {
    return {a,b}
}
console.log(fun( )) // {a:1,b:2}
console.log(fun({a:3})) // {a:3,b:456}
console.log(fun({})) // {a:123,b:456}

21.Object.is 实现【4月8日】

Object.is不会转换被比较的两个值的类型,这点和===更为相似,他们之间也存在一些区别。 一般情况下和 === 的判断相同,但有以下不同

  1. NaN在===中是不相等的,而在Object.is中是相等的

  2. +0和-0在===中是相等的,而在Object.is中是不相等的

function Objectis(x, y){
    if (x === y) {
        // 当前情况下,只有一种情况是特殊的,即 +0 -0
        // 如果 x !== 0,则返回true
        // 如果 x === 0,则需要判断+0和-0,则可以直接使用 1/+0 === Infinity 和 1/-0 === -Infinity来进行判断
        return x !== 0 || 1 / x === 1 / y;
      }
    
      // x !== y 的情况下,只需要判断是否为NaN,如果x!==x,则说明x是NaN,同理y也一样
      // x和y同时为NaN时,返回true
      return x !== x && y !== y;
}

20.实现电话号码隔位显示(3 4 4)【4月7日】

let number = '18833334444'
let reg = /(?=(\d{4})+$)/g
console.log(number.replace(reg, ' '))

其中

(?= p):符合p之前的位置
d{4}+$: 以每4个数字结尾的

19.实现一个compose函数【4月6日】

compose函数的作用就是组合函数,依次组合传入的函数,

  1. 后一个函数作为前一个函数的参数
  2. 最后一个函数可以接受多个参数,前面的函数只能接受单个参数;后一个的返回值传给前一个
function compose(...fn) {
  // 写一个compose方法
}
// 用法如下:
function fn1(x) {
  return x + 1;
}
function fn2(x) {
  return x + 2;
}
function fn3(x) {
  return x + 3;
}
function fn4(x) {
  return x + 4;
}
// compose(fn1,fn2,fn3)(a)等价于fn1(fn2(fn3(a)))
const a = compose(fn1,fn2,fn3,fn4);
console.log(a(1), '??'); // 1+4+3+2+1=11
let b = fn1(fn2(fn3(fn4(1))))
console.log(b, 'bbb')// 1+4+3+2+1=11 

写法:

function compose() {
  const argFnList = [...arguments]
  return (num) => {
    return argFnList.reduce((pre, cur) => cur(pre), num)
  }
}

18.实现一个单例模式【4月2日】

function Single(){

}
Single.getInstance = (function(){
    let instance = null
    return function(){
        if (!instance) {
            instance = new Single()
        }
        return instance
    }
})()

17.实现一个maxRequest 成功后resolve结果,失败后重试n次才真正reject【4月1日】

function maxRequest(fn, maxNum = 3) {
  return new Promise((resolve, reject) => {
    function help(index) {
      Promise.resolve(fn())
        .then((value) => {
          console.log("ok");
          resolve(value);
        })
        .catch((err) => {
          console.log("重试剩余次数", index);
          if (index - 1 > 0) {
            help(index - 1);
          } else {
            reject(err);
          }
        });
    }
    help(maxNum);
  });
}

16.手写AJAX【3月31日】

function getJson(url) {
    return new Promise((resolve, reject)=>{
        const xhr = new XMLHttpRequest()
        xhr.open('get', url, true)
        xhr.setRequestHeader("Content-Type","application/json")
        xhr.onreadystatechange = function(){
            if (xhr.status === 200 && xhr.readyState === 4) {
                resolve(xhr.responseText)
            } else {
                reject(new Error(xhr.responseText)) 
            }
        }
        xhr.send()
    })
}

15.new 操作符【3月30日】

 function myNew(fn, ...args) {
    let obj = Object.create(fn.prototype);
    let res = fn.call(obj, ...args);
    if (res && (typeof res === "object" || typeof res === "function")) {
      return res;
    }
    return obj;
  }

14.sleep函数【3月29日】

sleep函数作用是让线程休眠,等到指定时间在重新唤起。

实现 先打印start, 3s后打印 end:

function sleep(time){
    return new Promise((resolve, reject)=>{
        setTimeout(resolve(),time*1000)
    })
}
console.log('start')
sleep(3).then(res=>{
    console.log('end')
})
/// 比较简单 就不写备注了

13.有效的括号【3月28日】

image.png

示例 1:

输入:s = "()" 输出:true

示例 2:

输入:s = "()[]{}" 输出:true

示例 3:

输入:s = "(]" 输出:false

示例 4:

输入:s = "([)]" 输出:false

示例 5:

输入:s = "{[]}" 输出:true

var isValid = function(s) {
    // 如长度为奇数 直接false
    if(s.length % 2 !== 0 )return false
    let obj = {
        "[" : ']',
        "{" : '}',
        "(" : ')',
    }
    // 左括号的栈
    let leftStack = []
    // for可以return 
    for(let i = 0;i<s.length;i++) {
        // 拿到字符串的第i项
        let item = s[i]
        // 如果是左括号放进左栈中
        if(item == '[' || item == '{' || item == '(') {
             leftStack.push(item) 
        } else {
            // 如果是右括号
            let c = leftStack[leftStack.length-1]
            // 左括号最后一项 等于 右括号 则pop出栈;不等于直接false
            if(obj[c] === item){
                leftStack.pop()
            } else {
                return false
            }
        }
    }
    return leftStack.length == 0
};

其中

判断是否奇偶数: s % 2 === 0 s除以2没有余数则是偶数

12.实现模板字符串解析功能【3月25日】

题目要求:

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
console.log(render(template, data)); // 我是姓名,年龄18,性别undefined

实现:

//   \w是匹配字母、数字、下划线
//   +就是匹配一个以上
// 一个\ 后面  则代表 转义\后面的这个

function render(template, data) {
    let reg = /\{\{(\w+)\}\}/g
    let computed = template.replace(reg, function (match, key) {
        console.log(match, key)// {{name}} name     {{age}} age     {{sex}} sex
      return data[key];
    });
    return computed;
}


11.实现一个math.Sqrt,开根向下求整数【3月24日】

题目要求:

  • 100开根向下取整->10
  • 94开根向下取整->9
  • 1222开根向下取整->34
var mySqrt1 = function(value){
    // 最左值默认取1 最右值vulue  --- 左小右大
    let left = 1
    let right = value
    // left永远小于right
    while(left<right) {
        // 左右相加向下 取中值
        let mid = left+right >> 1
        // 接下来找目标值是在中值的右边还是左边
        if(mid <= value/mid) {
            if (mid+1 > Math.floor(value/(mid+1))) {
                 // 如果mid的平方小于等于value 且 mid+1 的平方大于value 那么 mid就是这个值
                return mid
            }
            // 如果mid平方小于value 且 mid+1 的平方小于于value  说明目标范围在mid右侧  则把left定位到mid+1
            left = mid+1
        } else {
            // mid平方大于value 说明目标范围在mid左侧  直接把right定位到mid-1
            right = mid - 1
        }
    }
    return left
}
console.log(mySqrt1(1222))
// console.log(mySqrt1(94))
// console.log(mySqrt1(1222))

其中位运算

x >> 1 :x除以2向下取整,注意位运算优先级更高,必要时()

10.求数组中第二大的数【3月23日】

两个方法

function findSecondMaxNum(arr) {
    let max = arr[0], secondMax = Number.MIN_SAFE_INTEGER// 会默认给一个17位负值
    for (let i = 1; i < arr.length; ++i) {// 循环一遍
      if (arr[i] > max) { // 如果该项比max大则把max给第二大的值,再把该项给max
        secondMax = max
        max = arr[i]
      } else if (arr[i] > secondMax) { // 如果该项小于最大值,又大于第二项的话,把该项赋值给第二项
        secondMax = arr[i]
      }
    }
    return secondMax
}

console.log(findSecondMaxNum([5, 4, 2, 7, 4, 6, 1, 9])) // 输出7
function findSecondMaxNum1(arr) {
  return arr.sort((a, b) => b - a)[1]
}
console.log(findSecondMaxNum1([5, 4, 2, 7, 4, 6, 1, 9]))// 7

9.股票买卖最佳时机【3月23日】

image.png 这道题其实就是!!!!! 贪心算法,每次都要是最优解!!!冲鸭!!!

// 注意四个点:不能是同一天 买入价尽可能低 卖出价尽可能高  卖出在买入之后
function maxProfit(prices) {
    // 获取传入价格一共多少项,总共多少天
    const len = prices.length
    if (len < 2) return 0// 如果天数为1 或者 0 当天买卖的话 利润为0
    let min = prices[0] // 假设,拿到第一项设为最小值
    // 最大利润
    let maxP = 0 
    // 从第一项开始循环,最大长度为len
    for (let i = 1; i < len; i++) {
        // 如果第i项小于min,则赋值给min
        if (prices[i] < min) {
            min = prices[i]
        }
        // 每次循环计算 此次循环利润
        const thisP = prices[i] - min 
        // 如果此次循环利润大于最大利润 则赋值给最大利润,因为求的是最大利润,
        // 把每次循环拿到的利润和已知最大利润进行对比,就可以巧妙的实现了卖出在买入之后
        if (thisP > maxP) {
            maxP = thisP
        }
    }
    return maxP
};
console.log(maxProfit([7,1,5,3,7,4, 0, 2, 5]))


提供两个求最小值的方法

// 用Number.MAX_SAFE_INTEGER初始值去找最大值,第10题是这个点的实践
let min1 = Number.MAX_SAFE_INTEGER

// 直接用math自带的方法
let min = Math.min(...[7,1,5,3,7,4, 0, 2, 5])

8.深拷贝(考虑到复制 Symbol 类型)【3月22日】

function isObject(val) {
  return typeof val === "object" && val !== null;
}

function deepClone(obj, hash = new WeakMap()) {
  if (!isObject(obj)) return obj; // 不是对象直接返回
  if (hash.has(obj)) {
    return hash.get(obj);// 如果hash中有值的话,从hash中获取值,防止循环引用
  }
  let target = Array.isArray(obj) ? [] : {};// 根据传入的数据类型设置目标初始值
  hash.set(obj, target);// 在hash中设置值,防止循环引用
   // 映射 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组
  Reflect.ownKeys(obj).forEach((item) => {
    if (isObject(obj[item])) { // 如果对应键的值是对象的话递归
      target[item] = deepClone(obj[item], hash);
    } else {// 是简单数据类型的话 直接赋值
      target[item] = obj[item];
    }
  });

  return target;
}

// var obj1 = {
// a:1,
// b:{a:2}
// };
// var obj2 = deepClone(obj1);
// console.log(obj1);

7.防抖节流【3月21日】

// 防抖
function debounce(fn, delay = 300) {
    //默认300毫秒
    let timer;
    return function () {
      const args = arguments;
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(() => {
        fn.apply(this, args); // 改变this指向为调用debounce所指的对象
      }, delay);
    };
  }
  window.addEventListener(
    "scroll",
    debounce(() => {
      console.log(111);
    }, 1000)
  );
function throttle(fn, delay) {
    let flag = true;
    return () => {
      if (!flag) return;
      flag = false;
      timer = setTimeout(() => {
        fn();
        flag = true;
      }, delay);
    };
  }
window.addEventListener(
  "scroll",
  throttle(() => {
    console.log(111);
  }, 1000)
);

6.快排【3月21日】

要求:时间复杂度 nlogn~ n^2 之间

function quickSort(arr) {
    // 如果arrlength为 0 或者 1 的话 直接返回arr  不需要排列
    if (arr.length < 2) {
      return arr;
    }
    // 拿到最后一项
    const cur = arr[arr.length - 1];
    // 筛选出小于等于cur且不是最后一项的值
    const left = arr.filter((v, i) => v <= cur && i !== arr.length - 1);
    // 筛选出大于cur的值成一个数组
    const right = arr.filter((v) => v > cur);
    debugger
    // 根据左中右位置进行排序, 再对左右进行递归排序
    return [...quickSort(left), cur, ...quickSort(right)];
  }
  console.log(quickSort([3, 6, 2, 4, 1]));


断点解析图:

image.png

image.png

image.png

5.版本号排序【3月20日】

题目描述:有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。

现在需要对其进行排序,排序的结果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']

let arr = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.5.5', '4.3.4.5']
arr.sort((a, b) => {
    // a, b -> next, current
    // a-b 正序排序
    let i = 0;
    const nextArr = a.split(".");
    const curretArr = b.split(".");
    while (true) {
    // 拿到.分割开后的每一项
      const nextItem = nextArr[i];
      const curItem = curretArr[i];
      i++;
      if (nextItem === undefined || curItem === undefined) {
        //   如果判断走到末端项发现有undefined的话
        return curretArr.length - nextArr.length;
      }
       //   如果下一个和现在的 一样 则继续往后循环
      if (nextItem === curItem) continue;
       //   下一项和现在都不一样 且 末端没有undefined情况 则直接比较大小
      return curItem - nextItem;
    }
  });
 
  console.log(arr);
// ['4.3.5.5', '4.3.5','4.3.4.5', '4.2', '2.3.3', '0.302.1','0.1.1']

4. 输入整数数组arr,找出其中最小的k个数,输出的位置保持不变【3月19日】

image.png

方法二:

function getMinArr(arr, k){
    let result = []
    // 把index和每一项存下来放入数组中
    result = arr.map((element, index)=>{
        return { element,index }
    })
    // 排序
    result.sort((a, b)=>{
        return a.element-b.element
    })
    // 截取前 k 个
    result = result.slice(0, k)
    // 再根据最初的index进行排序
    result.sort((a,b)=>{
        a.index - b.index
    })
    // 过滤掉index 只取item
    result = result.map(element => element.element)
    return result

}

console.log(getMinArr([2,3,1,10,4], 2))

方法三:

function findMix(arr, k){
    let sorted = arr.slice(0).sort((a, b)=> (a - b))
    let minKArr = sorted.slice(0, k)
    let resArr = []
    arr.forEach(item=>{
        if (minKArr.includes(item)) {
            resArr.push(item)
        }
    })
    return resArr
}
console.log(findMix([4, 5, 1, 6, 2, 7, 3, 8], 4))

3. 输入整数数组arr,找出其中最小的k个数,输出的位置保持不变【3月18日】

image.png

较快的解决方式

方法一:

// 输入整数数组arr,找出其中最小的k个数,输出的位置保持不变
function getMinArr(arr, k) {
    // sort会改变原数组,深拷贝使原数组不变
    // let sorted = JSON.parse(JSON.stringify(arr)).sort((a, b) => a-b)
    let sorted = arr.slice(0).sort((a, b) => a-b)
    // 截取前k个值--也就是最小的k个数
    let minKArr = sorted.splice(0, k)
    // 如果只截取第1个数的话 不用考虑位置不变问题 直接返回数组
    if (k == 1) return minKArr
    // 拿到最大值, 并用count计数 默认值1开始
    let minKArrMax = minKArr[minKArr.length - 1],count = 1
    // 接下来找有多少个最大值,count为最大值的数量 初始为1  表示一开始有一个最大值
    // minKArr最后一个肯定是最大值了,从倒数第二个开始往前比对
    for(let i = minKArr.length - 2;i>= 0; --i) {
        // 如果minKArr的第i项等于最大值 则count++,如果不等于则break
        if (minKArr[i] === minKArrMax) count++
        else break
    }
    let res = []
    // 循环原数组,比最大值大则push进去,否则如果等于最大值且还有count则把该值push进去
    for(let i = 0; i< arr.length;++i) {
        if(arr[i] < minKArrMax) res.push(arr[i])
        else if(arr[i] === minKArrMax && count-->0) res.push(arr[i])
    }
    return res
}
console.log(getMinArr([2,3,1,10,4], 2))

2.请写个方法将“weilcome to China”输出为“EMOCLIEW to ANIHC”【3月17日】

具体要求:奇数段倒叙,偶数段不倒叙,倒叙要大写

function generate(str) {
    let arr = str.split(' ')
     res = ''
    for (let i = 0; i < arr.length; ++i) {
        const temparr = arr[i]
        res += ((i+1)%2==0 ? temparr : temparr.toUpperCase().split('').reverse().join('')) + ' '
    }
    return res.slice(0, -1)
  }
  
console.log(generate('weilcome to China'))

1.url截取【3月17日】

将 'sapms.com/?a=1&b=2&c=…'

输出为 { a: '1', b: '2', c: '3', d: 'xx', arr: [ '1', '2' ] }

function parse(url){
    // 把? 和 #的位置拿到
    const wenhao  = url.indexOf('?'),jinghao = url.lastIndexOf('#')
    // 根据两端位置截取出中间部分 再 根据&分割成数组
    params = url.slice(wenhao+1, jinghao).split('&')
    let res = {}
    for(let i = 0; i<params.length;i++){
        const [key, value] = params[i].split('=') // 把键值解构赋值出来
        if(key.indexOf('[]') !== -1) {// 如果有[]
            const arrKey = key.slice(0, key.length-2)// 把键中arr[]的[]去掉
            // 如果没这个键就[] 有的话concat
            res[arrKey] = (res[arrKey] || []).concat([value])
        } else res[key] = value
    }
    return res
}
console.log(parse('https://sapms.com/?a=1&b=2&c=3&d=xx&arr[]=1&arr[]=2#hash'))