日常函数方法记录

1、实现数组扁平化

写一个 JS 函数,实现数组扁平化,只减少一级嵌套
如输入 [1,[2,[3]],4],输出 [1,2,[3],4]

思路:

定义空数组 arr =[]。遍历当前数组
如果 item 非数组,则累加到 arr
如果 item 是数组,则遍历之后累加到 arr

export function flatten1(arr: any[]): any[] {
    let res: any[] = []

    arr.forEach(item=>{
      if(Array.isArray(item)){
        item.forEach(ele=>res.push(ele))
      }else{
        res.push(item)
      }
    })
    return res
}

export function flatten2(arr: any[]): any[] {
    let res: any[] = []

    arr.forEach(item=>{
      res = res.concat(item)
    })
    return res
}

2、获取类型

手写一个 getType 函数,传入任意变量,可准确获取类型
如 number string boolean 等值类型
还有 object array map regexp 等引用类型

常见类型判断

  • typeof - 只能判断值类型,其他就是function和object
  • instanceof- 需要两个参数来判断,而不是获取类型
  • Object.prototype.toString.call(x)
export function getType(x:any):string{
    const originType = Object.prototype.toString.call(x)//'[Object String]'
    const spaceIndex = originType.indexOf(' ')// 5
    const type = originType.slice(spaceIndex+1,-1)//'String'
    return type.toLowerCase()//string
}

console.log(getType('test'))

3、new一个对象发生了什么?

  • 创建一个空对象,继承构造函数的原型
  • 执行构造函数(将obj作为this)
  • 返回obj
{}和Object.create()有什么区别

在这里插入图片描述

4.遍历DOM树

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>requestAnimationFrame</title>
</head>
<body>
  <p>template</p>
  <div id="box">
    <p>hello<b>world</b></p>
    <img src="https://www.baidu.com/img/flexible/logo/pc/result.png" alt="">
    <!-- 注释 -->
    <ul>
      <li>a</li>
      <li>b</li>
    </ul>
  </div>
  <script>
      function visitNode(n){
        if(n instanceof Comment){
          // 注释
          console.info('Comment node---',n.textContent)
        }
        if(n instanceof Text){
          // 文本
          var t = n.textContent?.trim()
          if(t){
            console.info('Text node---',t)
          }
          
        }

        if(n instanceof HTMLElement){
          // element
          console.info('Element node---',`<${n.tagName.toLowerCase()}>`)
        }
      }
      // 深度优先遍历
      function depthFirstTraverse(root){
        visitNode(root)
        const childNodes = root.childNodes//.children和.childNodes不一样
        if(childNodes.length){
          childNodes.forEach(child => {
            depthFirstTraverse(child)
          })
        }
      }

      // 广度优先遍历 采用队列方式,先进先出,层级遍历
      function breadthFristTraverse(root){
        const queue = []
        // 根节点入队列
        queue.unshift(root)
        while(queue.length>0){
          const curNode = queue.pop()
          if(curNode===null) break

          visitNode(curNode)
          // 子节点入列
          const childNodes = curNode.childNodes
          if(childNodes.length){
            childNodes.forEach(child=>{
              queue.unshift(child)
            })
          }
        }
      }
      const box = document.getElementById('box')
      if(box===null) throw new Error('box is null')
      breadthFristTraverse(box)
  </script>
</body>

</html>
  • 深度优先 ,递归 贪心
  • 广度优先,使用队列(数组vs链表)
  • children 和 childNodes 不同

5.手写LazyMan

在这里插入图片描述

class LazyMan{
    private name:string
    private tasks:Function []=[]//任务列表 
    constructor(name:string){
        this.name = name
        setTimeout(()=>{
    
            this.next()
        })
    }

    private next(){

        const task = this.tasks.shift()//取出当前tasks的第一个任务
        if(task) task()
    }

    eat(food:string){
        const task = ()=>{
            console.info(`${this.name} eat${food}`)
            // 立刻执行下一个
            this.next()
        }
        this.tasks.push(task)
        return this//链式调用
    }

    sleep(second:number){

        const task=()=>{
            console.info(`${this.name}开始睡觉`)
            setTimeout(()=>{
                console.info(`${this.name}已经睡完了${second}s,可以开始执行下一个任务`)
                this.next()
            },second*1000)
        }
        this.tasks.push(task)
        return this//链式调用
    }

}

const me = new LazyMan('曲奇')

me.eat('西瓜').eat('橙子').eat('李子').sleep(2).eat('红烧鸡')

6.柯里化函数

export function curry(fn:Function){
    const fnArgsLength = fn.length//传入函数的参数长度
    let args:any[] = []

    // ts中,独立的函数,需要声明类型
    function calc(this:any,...newArgs:any[]){
        // 积累参数
        args = [
            ...args,
            ...newArgs
        ]
        if(args.length<fnArgsLength){
            // 参数不够,返回函数
            return calc
        }else{
            // 参数够了,返回执行结果
            return fn.apply(this,args.slice(0,fnArgsLength))
        }
        
    }
    return calc
}

function add(a:number,b:number,c:number):number{
    
    return a+b+c
}

const curryAdd = curry(add)
const res = curryAdd(10)(20)(30)
console.info(res)

7.instanceof 原理是什么?

例如:f instanceof Foo
顺着f._proto_向上查找(原型链)
看能否找到Foo.prototype
在这里插入图片描述

/**
 * 自定义instanceof
 * @param instance instance
 * @param origin class or function
*/
export function myInstanceof(instance:any,origin:any):boolean{
    if(instance===null) return false //null 

    const type = typeof instance
    if(type!=='object'&&type!=='function'){
        // 值类型
        return false
    }

    let tempInstance = instance//防止修改instance
    while(tempInstance){
        if(tempInstance.__proto__=== origin.prototype){
            return true//匹配上了

        }
        tempInstance = tempInstance.__proto__//顺原型往上找
    }
    return false
}

// 功能测试
console.info(myInstanceof({},Object))//true
console.info(myInstanceof([],Object))//true
console.info(myInstanceof([],Array))//true

8.手写bind

bind的应用

返回一个新函数,但不执行
绑定 this 和部分参数
如是箭头函数,无法改变 this,只能改变参数

// @ts-ignore
 Function.prototype.customBind = function (context:any,...bindArgs:any[]){
    // context 是bind传入的this
    // bindArgs 是bind传入的各个参数
    const self = this//当前函数本身

    return function(...args:any[]){
        // 拼接参数
        const newArgs = bindArgs.concat(args)
        return self.apply(context,newArgs)
    }
 }

//  功能测试
function fn(this:any,a:any,b:any,c:any){
    console.log(this,a,b,c)
}
// @ts-ignore
const fn1 = fn.customBind({x:23},123,23,45)

fn1()

如何实现call、apply

分析:如何在函数执行时绑定this

  • 如const obj = {x:100,fn(){this.x}}
  • 执行obj.fn,此时fn内部的this指向obj
  • 借此来实现绑定this
Function.prototype.customCall = function (context: any, ...args: any[]) {
    if (context === null) context = globalThis//相当于window
    if (typeof context !== 'object') context = new Object(context)//值类型,变对象

    const fnKey = Symbol()//不会出现属性名称的污染
    context[fnKey] = this//this就是当前的函数
    const res = context[fnKey](...args)//绑定了this
    delete context[fnKey]//清理掉fnkey,防止污染
    return res
}



function fn(this:any,a:any,b:any,c:any){
    console.info(this,a,b,c)
}
// @ts-ignore
fn.customCall({s:"城市"},1,3,4,5)

// @ts-ignore
Function.prototype.customApply = function (context: any, args: any[]) {
    if (context === null) context = globalThis//相当于window
    if (typeof context !== 'object') context = new Object(context)//值类型,变对象

    const fnKey = Symbol()//不会出现属性名称的污染
    context[fnKey] = this//this就是当前的函数
    const res = context[fnKey](...args)//绑定了this
    delete context[fnKey]//清理掉fnkey,防止污染
    return res
}

9.手写EventBus自定义事件(事件总线)

  • on和once注册函数,存储起来
  • emit时找到对应函数,执行
  • off找到对应函数,从对象中删除
class EventBus{
  private events:{
   [key:string]:Array<{fn:Function;isOnce:Boolean}>
  }
  constructor(){
   this.events = {}
  }
  on(type:string,fn:Function,isOnce:Boolean=false){
       const events = this.events
       if(events[type]===null){
           events[type] = []//初始化key的fn数组
       }
       events[type].push({fn,isOnce:isOnce})
  }
  once(type:string,fn:Function){
      this.on(type,fn,true)
  }
  off(type:string,fn?:Function){
   if(!fn){
       // 解绑所有type的函数
       this.events[type] = []
   }else{
       // 解绑单个事件
       const fnList = this.events[type]
       if(fnList){
           this.events[type] = fnList.filter(ele=>ele.fn!==fn)
       }
   }

  }
  emit(type:string,...args:any[]){
       const fnList = this.events[type]
       if(fnList===null) return
       // 注意
       this.events[type] = fnList.filter(item=>{
           const {fn,isOnce} = item
           fn(...args)
           // once执行一次就要被过滤掉
           return !isOnce
       })
  }
}

10.用js实现LRU缓存

什么是LRU缓存
  • LRU–Least Recently Used 最近使用
  • 如果内存优先,只缓存最近使用的,删除“沉水”数据
  • 核心API两个:get set
    用哈希表存储数据,这样get set 才够快O(1)
    必须是有序的,常用数据放在前面,“沉水”数据放在后面
    哈希+有序,就是Map—其他都不行
class LRUCache{
    private length:number
    private data: Map<any,any> = new Map()
    constructor(length:number){
        if(length<1) throw new Error('invalid length')
        this.length = length
    }

    set(key:any,value:any){
        const data = this.data

        if(data.has(key)){//map删除后,重新赋值,会到最新的排位
            data.delete(key)
        }
        data.set(key,value)

        if(data.size>this.length){
            // 如果超出了容量,则删除Map最老的元素
            const delKey = data.keys().next().value
            data.delete(delKey)
        }
    }
    get(key:any):any{
        const data = this.data
        if(!data.has(key)) return null
        
        const value = data.get(key)

        data.delete(key)
        data.set(key,value)

        return value
    }
}

使用双向链表实现

双向链表:
在这里插入图片描述

interface IListNode {
    value: any,
    key: string//存储key,方便删除(否则删除时,需要遍历对象),
    prev?: IListNode,
    next?: IListNode
}

export default class LRUCache {
    private length: number
    private data: { [key: string]: IListNode } = {}
    private dataLength: number = 0
    private listHead: IListNode | null = null
    private listTail: IListNode | null = null
    constructor(length: number) {
        if (length < 1) throw new Error('invalid length')
        this.length = length
    }

    private moveToTail(curNode: IListNode) {
        const tail = this.listTail 
        if(tail===curNode) return
        // ---------- 1、让preNode nextNode断绝与curNode的关系 -----------
        const preNode = curNode.prev
        const nextNode = curNode.next
        if(preNode){
            if(nextNode){
                preNode.next = nextNode
            }else{
                delete preNode.next
            }
        }

        if(nextNode){
            if(preNode){
                nextNode.prev = preNode
            }else{
                delete nextNode.prev
            }

            if(this.listHead === curNode)this.listHead = nextNode
        }
        // ---------- 2、让curNode 断绝与pre next  的关系-----------
        delete curNode.prev
        delete curNode.next
        // ---------- 3、在list末尾重新建立新的关系 -----------
        if(tail){
            tail.next = curNode
            curNode.prev = tail
        }
        this.listTail = curNode
    }

    private tryClear(){
       while(this.dataLength>this.length){
        const head = this.listHead
        if(head==null) throw new Error('head is null')
        const headNext = head.next
        if(headNext==null) throw new Error('headNext is null')

        // 1.断绝head和next的关系
        delete headNext.prev
        delete head.next 
        // 2.重新赋值listhead
        this.listHead = headNext
        // 3.清理data
        delete this.data[head.key]
        // 4.重新计数
        this.dataLength--
       }
    }
    get(key: string): any {
        const data = this.data
        const curNode = data[key]

        if (curNode == null) return null
        if (this.listTail === curNode) {
            // 本身就是在末尾(最新鲜的位置),直接返回value
            return curNode.value
        }

        // 移动到末尾
        this.moveToTail(curNode)

        return curNode.value
    }

    set(key: string, value: any) {
        const data = this.data
        const curNode = data[key]

        if (curNode == null) {
            // 新增数据
            const newNode:IListNode = {
                value,key
            }
            // 移动到末尾
            this.moveToTail(newNode)
            data[key] = newNode
            this.dataLength++

            if(this.dataLength===1)this.listHead = newNode
        } else {
            // 修改现有数据
            curNode.value = value
            // 移动到末尾
            this.moveToTail(curNode)
        }

        // 尝试清理
        this.tryClear()
    }
}

var lruCache = new LRUCache(4)
lruCache.set('1',1)
lruCache.set('2',2)
lruCache.set('3',3)
console.info(lruCache.get('2'),lruCache)

在这里插入图片描述

11.手写一个深拷贝、考虑Map,set,循环引用

使用JSON.stringify和parse
  • 无法转换函数
  • 无法转换Map、set
  • 无法转换循环引用
使用Object.assign
  • 不是深拷贝
    在这里插入图片描述
普通深拷贝
  • 只考虑Object Array
  • 无法转换Map Set和循环引用
/**
 * 深拷贝
 * @param obj obj
 * @param map weakmap为了避免循环引用
 */
export function cloneDeep(obj: any, map = new WeakMap()): any {
    if (typeof obj !== 'object' || obj == null) return obj

    // 避免循环引用
    const objFromMap = map.get(obj)
    if (objFromMap) return objFromMap

    let target: any = {}
    map.set(obj, target)

    // Map
    if (obj instanceof Map) {
        target = new Map()
        obj.forEach((v, k) => {
            const v1 = cloneDeep(v, map)
            const k1 = cloneDeep(k, map)
            target.set(k1, v1)
        })
    }
    // Set

    if (obj instanceof Set) {
        target = new Set()
        obj.forEach(v => {
            const v1 = cloneDeep(v, map)
            target.add(v1)
        })
    }
    // Array
    if (obj instanceof Array) {
        target = obj.map(item => cloneDeep(item, map))
    }
    // Object 
    for (const key in obj) {
        const val = obj[key]
        const val1 = cloneDeep(val, map)
        target[key] = val1
    }

    return target
}

const obj ={
    a:function(){
        return 'text'
    },
    map:new Map([['x',12],['y',20]]),
    set:new Set([90,80,79]),
    x:{w:100}
}
var obj1 = cloneDeep(obj)
obj.x.w = 200
console.info(obj,obj1)

在这里插入图片描述

  • 26
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值