当前位置: 首页 > article >正文

万字深度剖析——JS数据结构(上)

数组本质是对象,键就是索引,值就是元素。

push  /unshift       在数组最后/最前添加

pop  /shift       把数组最后/最前的元素删除,返回的是被删除的元素

splice(0,2,5)从第0给位置开始删除2个元素,并添加一个元素

数组自带的排序方法

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

arr.sort((y,x)=>x-y)x-y>0交换顺序,不>0就不会交换顺序

最后结果时[2,3,4,5,6]顺序

arr.sort(x,y)

    迭代方法 every some filter map foreach reduce 

    arr.every((item)=>item>12)//如果每一项都满足条件,结果才是true

     arr.some((item)=>item>12)//只要有其中一项满足条件,结果就是true

map映射,简单来说,就像是一个“翻译器”或者“转换器”。它的主要工作就是将一种东西(比如数字、文字、对象等)按照一定的规则转换成另一种东西。

举个例子,假设你有一个列表,里面装着一些数字:[1, 2, 3, 4, 5]。现在你想把每个数字都变成它的两倍。那么,你可以使用map映射来实现这个目标。map映射会遍历这个列表,把每个数字都拿去乘以2,然后返回一个新的列表:[2, 4, 6, 8, 10]。

再比如,你有一个装着名字的列表:[“张三”, “李四”, “王五”]。你想把每个名字都变成“你好,XXX”的格式。使用map映射,你可以很容易地得到一个新的列表:[“你好,张三”, “你好,李四”, “你好,王五”]。

所以,map映射就是一个非常方便的工具,可以帮助我们快速地对一组数据进行统一的转换或处理。

forEach

     arr.forEach((item,index)=>
    console.log(item,index)
    )//返回数组里每个元素和索引值

reduce

第一个元素是上一次执行的返回值

    let res=arr.reduce((item1,item2)=item1+item2)//将每一个元素累加

迭代器对象

每一个数组都有一个迭代器

    let ite=arr[Symbol.iterator]()

如上拿到迭代器接口

只要符合迭代器对象就能使用for of、循环遍历对象

    for(let i of arr){
      console.log(i); 
    }
  console.log(arr.entries())

结果是迭代器对象

就能拿到键值对

  for(let i of arr.entries()){
    console.log(i);
  }

拿到键

  for(let i of arr.keys()){
    console.log(i);
  }

拿到元素的值

 for(let i of arr.values()){
    console.log(i);
  }

Arry.from将一个类似于数组的对象转化为真的数组结构

  function test(){
    console.log(Array.from(arguments));
    
  }
test(1,2,3)  

虽然test函数没有形参,但是用arguments可以找到,但是arguments是一个对象用from可以转化成对象

搜索 indexof  lastIndexof find findIndex findLast findLastIndex includes

indexof 

如果包含就返回正确的索引值,如果不包含就返回-1

let arr=[12,22,23]
arr.indexOf(15)
//结果是-1

includes

包含就返回true,不包含返回false

arr.includes(15)

find能返回最先满足条件的元素

findLast从后面开始查找

findIndex返回索引值

findLastIndex找出数组里最后一个大于10的元素

let res2=arr.findLastIndex(item=>item>10)

栈结构的封装

定义一个类,初始化一个元素。

  class stack{
    constructor(){
         this.item=[]
      }
  }
  let stack =new stack()

现在可以直接跳过constructor

  class stack{
    items=[]
  }
  • pop() 方法

    • pop() 方法应该是从栈中移除并返回最后一个元素,而不是添加元素。因此,pop() 方法不需要参数,并且应该调用 this.items.pop() 来移除数组中的最后一个元素。
  • peek() 方法

    • peek() 方法用于查看栈顶元素,但不移除它。因此,peek() 方法也不需要参数,并且应该返回 this.items[this.items.length - 1],而不是调用 this.items.peek()。因为 this.items 是一个数组,而数组没有 peek() 方法。
    • push(data) 方法
    • 接受一个参数 data,并将其添加到栈顶。
    • 返回undefined
    •     class stack {
              items = []
              pop(){
             this.items.push()
              }
              push(){
                this.items.push(data)
              }
              peek(data){
                return this.items[this.items.length-1]
              }
                isEmpty(){
                  // return this.items.length===0
                  return this.items.at(-1)
                }
                size(){
                  return this.items.length
                }
                clear(){
                  return this.items=[]
                }
            Tostring(){
            return this.items.join(',')
            }
            }
        let stack =new stack()
      

      由于我们可以随便用比如this.item.splice(0,2,9)的方式破坏栈,可以加下划线

              _items = []
      

      表示栈的私有性,来保护栈里的元素

    • 直至发布es13后才支持自带的属性用在前面加井号的方式

    •         #items = []
      

      每次访问stack.#items.push()用stack里面的方法才能改变栈的元素。直接stack.item会报错.

    • 用栈方法应用——解决十进制转换

    • 每一次把一个十进制的整数除以2的余数用push方法把余数push到栈里越往后的余数月堆到栈顶,再用pop方法把每次余数取出来,也就是从最后的余数取出

        let stack =new stack()
        function Dec(Decnumber){
          let remStack=new stack()
          let number=Decnumber
          let string=""
          while(number>0){
            remStack.push(number)
            number=Math.floor(number%2)
          }
          while(!(remStack.isEmpty())){
            string+=remStack.pop()
          }
          return string
        }
        Dec(50)
      

      限制进制数如:8

    • 进制数如果是16,但是没有定义用ABCDEF表示10-16就需数组或者字符串定义

    •   function ECD(edcnumber){
          let string=''
          let rank=new stack()
          let number=edcnumber
          while(number>0){
            rank.push(number%2)
           number=Math.floor(number / 2) 
          }
          while(!(rank.isEmpty())){
            string+=rank.pop()
          }
          return string
        }
      EDC(500,16)//让500按照16进制转换
      

      队列

    • 先进先出:像日常做核酸,排在列队头的人做完出去,队尾可以随时加人

    • 队列和栈差不多,不同的在于

    • dequeue:返回队头被删除的元素,方法里面用shift来删除第一个元素

    • enqueue:表示队尾添加

    • front:返回队头

      class stack{
       #item=[]
      dequeue(){
      return this.#item.shift()
       }
       enqueue(data){
        this.#item.push(data)
       }
       front(){
        return this.#item.push.at(0)
       }
       isEmpty(){
        return this.#item.length===0
       }
       size(){
        return this.#item.length
       }
       clear(){
      this.#item=[]
       }
       Tostring(){
        return this.#item.join(" ")
       }
      
      }
      

      但是:用shift的缺点是删除第一个元素就使后面的元素都往后移动一个。若数据太多对渲染效果有害,而delet方法使删除的元素位置上是empty。

    • class Queue{
        #items={}
        #lowCout=0//记队头的索引值
        #count=0//为每次改变往后移记录索引值
        dequeue() {
          if(this.isEmpty()){
            return undefined
          }//防止出现size负数
      
          let res=this.#items[this.#lowCout]
          delete this.#items[this.#lowCout]
          this.#lowCout++
          return res
        }
        enqueue(data) {
          this.#items[this.#count]=data
         this. #count++
        }
        front() {
          return this.#items[this.#lowCout]
        }
        isEmpty() {
          return this.size()===0
        }
        size() {
          return this.#count-this.#lowCout
        }
        clear() {
          this.#items={}
              this.#lowCout = 0
          this.#count = 0
      
        }
        Tostring() {
        }
      
      }
      

    • 最应该采用对象obj的方式,来模拟数组

      obj={0:1,1:2,3:3}
      

      那么数据结构就会改变,所以之前的队列就不能用了。

    • 队列应用-——击鼓传花

    • 首先由几个玩家,规定一轮击鼓数为7.转为编程思想就是由队头被删并加在队尾,队列轮回后到谁时第七个结束谁出局。首先定义几个玩家,和击鼓传花数

    • game(['kerwin','xiaoming','tiechui','gangaer','guludunzi'],7)
      

      封装game函数

    • 1,new一个队列

    • let queue=new Queue()
      

    • 2,邀请玩家入场!

    • for(let i=0;i<list.length;i++){
        queue.enqueue(list[i])
      }
      

    • 3,开始游戏!

    • while(queue.size>1){
        for(let i=0;i<num;i++){
         queue.enqueue( queue.dequeue())
        }
      

      4,获胜者出列颁奖!

    • return queue.dequeue()
      

      完整代码

    • function game(list,num){
      let queue=new Queue()
      for(let i=0;i<list.length;i++){
        queue.enqueue(list[i])
      }
      while(queue.size>1){
        for(let i=0;i<num;i++){
         queue.enqueue( queue.dequeue())
        }
        console.log(queue.dequeue(),"淘汰了");
      }
      return queue.dequeue()
      }
      game(['kerwin','xiaoming','tiechui','gangaer','guludunzi'],7)
      

      双端队列

    • 可以从队头出,也可以从队头进,可以从队尾出,也可以从队尾进。例如,日常排队从队头插队,也可以看队太长了从队尾直接离开

    • 把方法改为从头加addFront

    • 分为三个情况:

    • lowcount(开头索引值)=0;把数组中后面的元素向后移动一位,把第一个位置空出来。然后在第一个位置上添加数据data。

    •         else{
                //lowcount从0开始
                for(let i=this.#count;i>0;i--){
                  this.#items[i]=this.#items[i-1]
                }
                this.#items[0]=data //0的位置被移出来,就可以添加数据
                this.#count++
              }
      

    • lowcount(开头索引值)>0;把lowcount减一,留出0的位置。添加data

    •         if(this.#lowCout>0){
                this.#lowCout--
                this.#items[this.#lowCout]=data
      

    • 数组为空就可以直接添加复用addback方法。

    •       //如果为空
            if(this.isEmpty()){
              this.addBack(data)
            }
      

      还有新颖的是在队尾删除

    • 因为count是从0开始计数,最后一个元素队尾元素的索引是 #count - 1,因为数组索引从0开始。因此,#count 本身并不直接指向队尾元素,而是指向队尾元素之后的下一个位置(即队列的长度)。

        removeBack(){
          if(this.isEmpty()){
            return
          }
          this.#count--
          let res=this.#items[this.#count]
          delete this.#items[this.#count]
          return res
        }
      

      其他和单队列差不多

    • class Queue{
        #items
        #count=0
        #lowcount
        removeFont(){
          let res=this.#items[this.#lowcount]
          delete this.#items[this.#lowcount]
          return res
        }
        addBack(){
          this.#items.this.#count=[data]
          this.count++
        }
        addFront(){
          if(this.isEmpty()){
            this.#items.addBack()
          }else{
            if(this.#lowcount>0){
              this.#lowcount--
              this.#items[this.#lowcount]=[data]
            }
            else {
              for(let i=0;i<this.#count;i++)
              this.#items[i+1]=this.#items[i]
            for(let i=this.#count;i>0;i--)
            this.#items[i]=this.#items[i-1]
            }
          }
        }
        removeBack(){
          if(this.isEmpty()){
            return
          }
          this.#count--
          let res=this.#items[this.#count]
          delete this.#items[this.#count]
          return res
        }
        isEmpty(){
          return this.size()===0
        }
        size(){
          return this.#count-this.#lowcount
        }
        peekFront(){
          return this.#items[this.#lowcount]
        }
        peekBack(){
          if(this.isEmpty()){
            return 
          }
          return this.#items[this.#count-1]
        }
        toString(){
          let str=""
          for(let i=0;i<this.#count;i++){
            str=`${this.#items[i]}`
          }
          return str
        }
      }
      let dequeue=new Queue()
      function test(str){
      const lowstr=str.toLocalLowerCase().split("").join
      let queue=new Queue()
      for(let i=0;i<lowstr.length;i++){
        queue.addBack(lowstr[i])
      }
      console.log(dequeue);
      
      }
      

      应用:回文检查如dad

    • :split方法会自动缩进空格

    • function test(str){
      const lowstr=str.toLocalLowerCase().split("").join
      let queue=new Queue()
      for(let i=0;i<lowstr.length;i++){
        queue.addBack(lowstr[i])
      }
      while(dequeue.size()>1){
        if(dequeue.removeBack!==dequeue.removeFont){
      isEqual=false
      
      break;
        }
      }
      }
      test('DA     D')
      

      链表

    • 模拟栈和队列,也有自己独特的魅力

    • 在构造函数中设置nextnull可以确保每个新创建的节点都默认没有指向下一个节点,合链表节点的初始状态。this.element = element;这行代码的作用是将传递给构造函数的element参数的值赋给新创建的节点实例的element属性。

    •   class Node {
        constructor(element) {
          this.element = element; // 节点存储的数据
          this.next = null; // 指向下一个节点的链接
        }
      }
    • 在linkedList中再定义记录节点位置和头节点

    • 1,在constructor构造函数器中用定义并初始化两个变量,cout用来记录链表中的节点,应为要从链头到链尾一点点找,还要初始化一个链头head

    • 2,push从后面插入

    •       push(){
             const node = new Node(element)
              if(head==null){
              this.head=Node  
            }
            else{
              let current=this.head
              while(current.next!==null){
                current =current.next
              }
              current.next=node
            }
            this.count++
         }
      

    • 3,删除分为两种:删除特定位置,删除特点元素数据

    • 删除特定位置

    •    //指定位置删除
         removeAt(index){
            if(index>=0&&index<this.count){
        let current=this.head
              if(index===0){
                this.head=this.head
              }
              else{
                let previous
                for(let i=0;i<index;i++){
                  previous=current
                  current=current.next
                }
                previous.next=current.next
              }
              this.current--
              return current.element
      
            }
         }
      

    • 参数检查

    • 为了保护head不被破坏复制给current找节点头
      • 首先检查传入的索引 index 是否有效,即是否在链表的范围内(0 到 this.count - 1)。
    • 删除头节点

      • 如果 index 为 0,表示要删除的是头节点。在这种情况下,只需要将头节点指向下一个节点即可。
    • 删除非头节点

      • 如果 index 大于 0,则需要遍历链表找到要删除的节点及其前一个节点。
      • 使用两个指针 previous 和 current,其中 current 初始指向头节点。
      • 遍历链表,直到 current 指向要删除的节点。
      • 然后,将 previous 的 next 指针指向 current 的 next,从而跳过 current 节点,实现删除。
    • 更新计数

      • 删除节点后,应该减少链表的节点计数 this.count。但在您提供的代码中,这一步被遗漏了。
      • 你可能有这样的疑惑
      • 为什么 current 是正确的节点

      • 初始化时,current 指向头节点,这是已知的。
      • 循环确保了 current 指针从头节点开始,逐个节点移动,直到到达指定的索引位置。
      • 循环结束后,current 指针所指向的节点就是我们要删除的节点,因为我们已经根据 index 的值移动了 current 指针相应的次数。
      • 因此,不需要将 current 复制为 index,因为 current 的位置是通过遍历链表来确定的,而不是通过直接设置索引值。循环的结构和 current 指针的移动确保了在循环结束时,current 指向的是正确的节点。

      • 实验效果,为简化方法,封装一个函数来接节点

      •    getNodeAt(index){
            if(index>=0&&index.this.count){
              let node=this.head
              for(let i=0;i<index;i++){
                node=node.next
              }
              return node
            }
           }
        

        在removeAt函数中precious调用函数接收节点

      •    removeAt(index){
              if(index>=0&&index<this.count){
                          let current = this.head
        
                if(index===0){
                  this.head=this.head
                }
                else{
                  let previous=this.getNodeAt(index-1)
                  previous.next=current
                  previous.next=current.next
                }
                this.current--
                return current.element
              }
           }
        

        上面是根据节点位置删除,如果你想删除一个数据就需要下面的方法

      • 规定两个方法,第一个用于比对节点中你想找到的数值,并返回索引值

      • indexOf(element){
          let current =this.head
          for(let i=0;i<this.count;i++){
            if(方法判断(element,current.element)){
              return i
            }
            current=current.next
          }
        return -1
        }
        

        在这个indexOf函数中,如果方法判断(element, current.element)返回true,那么return i;会被执行,函数会返回索引i,并且不会再执行current = current.next;或者任何后续的循环迭代。整个函数调用会结束,控制权会返回到调用indexOf函数的地方。

      • 第二个用于接收索引值,调上一个删除方法

      • 例如这样一个方法

      •    equalFn(a,b){
            return a===b
           }
        

        但是如果是一个对象,就不能这样简单对比,因为引用数据类型在栈中存放地址,每个对象地址不一样指向堆内存中实际对象的地址。

      • 可以直接暴力的转换成字符串

      •    equalFn(a,b){
            return JSON.stringify(a)===JSON.stringify(b)
           }
        

        但是如果把两个对象交换位置,就不能判断了

      • 可以直接用第三方库imumutable

      • 双向链表

      • 每个节点即能指向前一个(prev),有能指向后一个(next)

      • 插入值

      • 如果想在头部插入

      • insert(element,index){
            if(index>=0&&index<=this.count){
            const node=new Node(element)
            if(index===0){
              const current =this.head
              node.next=current
              this.head=node
        }
        }
        

      • if(index === 0){ // 如果插入位置是链表头部

        这行代码检查是否要在链表的头部插入新节点。index 是插入位置的索引,如果它是0,意味着新节点应该成为新的头节点。

      • node.next = this.head; // 新节点的下一个节点设置为当前头节点

        这行代码将新节点(node)的 next 属性设置为当前的头节点(this.head)。这样,新节点就指向了原来的头节点,保持了链表的连续性。

        • node:新创建的节点,即将插入链表。
        • this.head:当前链表的头节点。
        • node.next = this.head:将新节点的 next 指针指向当前的头节点。
        • 如果想插入的位置不是头部

        • 思想:
        • 示例

          假设链表当前状态如下:

          复制

          Head -> A -> B -> C
          

          其中,A 是头节点,B 是第二个节点,C 是第三个节点。现在,我们想要在B和C之间插入一个新节点X:

        • previous = this.getNodeAt(1); // 获取B的引用
        • current = previous.next; // 获取C的引用
        • node.next = current; // 设置X的next指向C
        • previous.next = node; // 设置B的next指向X
        • 插入后的链表状态:

          复制

          Head -> A -> B -> X -> C
          

          总结

        • 当在链表的特定位置插入新节点时,需要找到该位置的前一个节点和当前节点。
        • 将新节点的next指向当前节点。
        • 将前一个节点的next更新为新节点。
        • 具体代码
        •     else{
                const previous=this.getNodeAt(index-1)//获取前一个节点
                const current =previous.next//让前一个节点指向新节点引用的位置
                node.next=current//把新建的节点放进去
                previous.next=node
              }
          

          完整插入代码

        • insert(element,index){
            if(index>=0&&index<=this.count){
              const node=new Node(element)
              if(index===0){
                const current=this.head//保护head
                node.next=current//当前头节点作为新节点的下一个
                this.head=node//把新节点作为头节点
                
              }else{
                const previous=getNodeAt(index-1)
                const current=previous.next
                node.next=current
                previous.next=node
              }
              this.count++
              return true
            }
            return false
          }
          

          剩下几个简单的方法

        • isEmpty(){
            return this.size===0
          }
          size(){
            return this.count
          }
          gethead(){
            return this.head
          }
          

          双端链表

        • 还可以继承单链表的属性

        • class DoubleNode extends Node {
            constructor(element) {
              super(element); // 调用父类Node的构造函数
              this.prev = null; // 初始化DoubleNode特有的pre属性
            }
          }

        • super(element)调用Node类的构造函数,并将element参数传递给父类构造函数。这样,DoubleNode实例就继承了Node类的element属性

        • 之前封装的单向链表中很多方法需要重新封装

        • push

        •     push(){
                const node=new DoublyNode()
                //prev next
          if(this.head===null){
            this.head=node
            this.tail=node
          }else{
            this.tail.next=node
            node.prev=node
            this.tail=node
          }
          

          如果头部为空,就直接加

        • 头部不为空

        • 让链表尾部next与新节点连接,新节点prev指向尾部,再让尾部彻底等于新节点

        • insert

        • 不说了,思想全在图里
        •     insert(element,index){
                const node=DoublyLinked(element)
                let current=this.head
                if(index>=0&&index<=this.count){
                  if(index===0){
                    if(this.head===null)
                    this.head=node
                    this.tail=node
                  }
                  else{
                    node.next=this.head
                    this.head.prev=node
                    this.head=node
                  }          
                }
                else if(index===this.count){
                  current=this.tail
                  current.next=node
                  node.prev=current
                  this.tail=node
                }
                else{
                  const previous=getNodeAt(index-1)
                  current=previous.next
                  node.next=current
                  current.prev=node
                  previous.next=node
                  node.prev=previous
                }
                    this.count++
          
                    return true
              }
              
          

          removeAt删除

        • 注意:index要小于this.count,因为count从0开始,最后的count值没有节点(和insert不同)

        • 按照索引值删除和insert思想差不多,需要注意的是如果让head的next指向下一个会出问题,因为曾经的尾部被head占了,所以之前让head直接赋值为head的next—— this.head=current.next

        •     removeAt(index){
                if(index>=0&&index<this.count){
                  let current=this.head
                  if(index===0){
                    this.head=current.next
                    if(this.count===1){
                      this.tail===null
                    }else{
                      this.head.prev=undefined
                    }        
                  }
                  if(index===this.size()-1){
                    let current =this.tail
                    current.prev=this.tail
                    this.tail.next=undefined
                  }
                  else{
                    let previous=this.getNodeAt(index-1)
                     current=previous.next
                    previous.next=current.next
                    current.next.prev=previous
                  }
                  this.count--
                  return current.element
          
                }
          
              }
          

          remove

        • 按照数值删除和单链表方法一样

        • 循环链表

        • 就是让最后节点的next指向head,循环起来

        • insert插入

        • insert(element,index){
            let node=new DoublyLinked(element)
            let current=this.head
            if(index>=0&&index<=count){
              if(index===0){
                if(head===0){
                  this.head=node
                  node.next=this.head
                }
                else{
                  node.next=current
                  current = this.getNodeAt(this.size() - 1)//不能保证后面的节点指向head,下面解决,获得尾部元素
                  this.head=node//
                  current.next=this.head//让尾部元素next指向head,保证循环
                }
              }else{
                const previous=this.getNodeAt(index-1)
                 current=previous.next
                previous.next=node
                node.next=current
              }
              this.count++
          return true
            }
            return false
          }
          

          removedAt——按索引删除

        • removeAt(index){
          if(index>=0&&index<count){
            let current=this.head
            if(index===0){
              if(this.size()===1){
                let last=this.getNodeAt(this.size()-1)
               this. head=this.head.next
                last.next=head
              }
            }
            else{
            let previous=this.getNodeAt(index-1)
            current=previous.next
            previous.next=current.next//不用怕删除后没有循环,因为被删的current的next就指向head
            }
            this.count--
            return current.element
          }
          }
          

          集合set

        • 无序且唯一的项组成

        • 因为对象的属性不能重复适合作为集合,下面我们用对象模拟

        • 属性和属性名都是一个内容,存100就是100:100所以想要操作某个元素直接this.items[element]就可以

        • 建一个class封装增删查改

        •     class kerwin{
                // constructor(){
                //   this.items={}
                // }
                items={}
                add(element){
                  if(!this.has(element)){
                    this.items[element]=element
                    return true
                  }
                  return false
          
                }
                delete(element){
          if(this.has(element)){
            delete this.items[element]
            return true
          }
                }
                has(element){
          return element in this.items
                }
                clear(){
          return this.items={}
                }
                size(){
          return Object.keys(this.items).length
                }
                values(){
          return Object.values(this.items)
                }
              }
          

          其中size方法用Object.keys()方法会返回一个包含对象所有自有属性(不包括原型链上的属性)的键(key)的数组。

        • 应用:检查数组中是否有重复元素

        •     var arr=[1,2,3,3,3,4]
              arr.forEach(items=>{
                myList.add(items)
              })
          

          Es6中的set属性

        • 迭代器

        • 符合迭代器的元素可以用多个next遍历,也可以用for...of遍历,也可以用展开运算符变为普通数组

        • 与上面我们自己封装的set不同在于,Es6的size是一个属性,我们是方法

        • 应用

        • 交集,并集,差集——从后端接收数据时候使用

        •     var mySetA=new Set([1,2,3])
              var mySetB=new Set([2,3,4])
              // 并集
              var mySetAnd=new Set([...mySetA],[...mySetB])
            // 交集——只有数组才有filter方法
            var mySetJiao=new Set([...mySetA].filter(item=>mySetB.has(item)))
            //差集
            var mySetJiao=new Set([...mySetA].filter(item=>!mySetB.has(item)))
          

          字典

        • 和集合很相似,但集合是【值,值】,字典【键,值】,键可以是对象,也可以是任意数据类型

        •     class Dictionary{
                table={}
                //将键转化为字符串
                tostrFn(item){
                  if(item===null){
                    return 'NULL'
                  }
                  else if(item===undefined){
                    return 'UNDEFINDED'
                  }
                  else if(item==='string'||item instanceof String){
                    return item
                  }
                  return JSON.stringify(item)
                }
          }

          tostrFn 

        • :使用 tostrFn 方法将 key 转换为一个字符串。这是因为在 JavaScript 对象中,键总是被转换为字符串。tostrFn 方法的执行步骤如下:

          • 如果 item(在这里是 key)是 null,返回字符串 'NULL'
          • 如果 item 是 undefined,返回字符串 'UNDEFINED'
          • 如果 item 是字符串或 String 对象的实例,直接返回该字符串。
          • 否则,使用 JSON.stringify 将 item 转换为字符串。
        • 检查键存在性:使用转换后的键字符串在 this.table 中查找。this.table 是一个对象,用于存储字典的键值对。

        • 返回结果:如果找到对应的键(即 this.table 中存在该键),则 this.table[this.tostrFn(key)] 不会是 nullhaskey 方法返回 true。如果没有找到,或者对应的值是 null,则返回 false

        • 注意:如果haskey 方法中检查的是 !== null,这意味着如果键存在但对应的值是 undefined 或其他非 null 值,它也会返回 true。所以改为!=null就可以了
        • set

        • 检查是否有键
        • 第一种方法
                //检查是否含有键
                haskey(key) {
                  return this.table[this.tostrFn(key)] != null
                }
                set(key,value){
                  if(key!==null&&value!==null){
                    const tablekey=this.tostrFn(key)
                    this.table[tablekey]=value
                    return true
                  }
                  return false
                }
              }
          

          这种方法将键存放的被转化后的字符串键名,所以另起一个方法二将value中存键+值,保证获取到最原始的键

        • 方法二

        •       set(key,value){
                  if(key!==null&&value!==null){
                    const tablekey=this.tostrFn(key)
                    this.table[tablekey]=new ValuePair(key,value)
                    return true
                  }
                  return false
                }
              }
              var mysseet=Dictionary()
              class ValuePair{
                constructor(key,value){
                  this.key=key
                  this.value=value
                }
              }
          

          valuepair类创建一个实例对象保存一份原始的key,value

        • 其他方法

        •       get(key){
                  const ValuePair=this.table[this.tostrFn(key)]
                  return ValuePair ==null?undefined:ValuePair.value
                }
                remove(key){
                  if(this.haskey(key)){
                    delete this.table.tostrFn(key)
                    return true
                  }
                  return false
                }
                keys(){
                  return this.keyValues().map(item=>item.key)
                }
               values(){
                  return this.keyValues().map(item=>item.value)
                }
                keyValues(){
                  return Object.values(this.table)//Object.values() 只返回自有属性的值,不返回继承属性的值。
                }
                size(){
                  return Object.keys(this.table).length
                }
                isEmpty(){
                  return this.size===0
                }
                clear(){
                  return this.table=null
                }
                forEach(cb){
                  const ValuePair=this.keyValues()
                  for(let i=0;i<ValuePair.length;i++){
                    cb(ValuePair[i].key,ValuePair[i].value)
                  }
                }
                
          

          散列表

        • 有上字典可以看到,由于set每次查找键都需要转化为JSON字符串很麻烦,并且遍历上万条大数据很消耗性能,所以散列表可以把键通过散列函数转换为一个数组作为索引,再从散列表中查找

        • set方法(就相当于push)

        •       set (key,value){
                  if(key!=null&&value!=null){
                    const position=111
                    this.table[position]=ValuePair(key,value)
                    return true
                  }
                  return false
                }
          

          需要有一个方法能给table设置类似position=111这个索引数字

        • hash表就派上用场了,将table中的键转化为字符串,

        1. 如果直接是数字就更好,不用转换,

        2. 一旦是字符串就用charCodeAt方法转化为ASCALl码

        3. ,防止hash值过大用取余

        4.       hashCode(key){
                  const tablekey=this.tostrFn(key)
                  for(let i=0;i<tablekey.length;i++){
                    hash+=tablekey.charCodeAt(i)
          
                  }
                  return hash%37
                }
                //put
                set (key,value){
                  if(key!=null&&value!=null){
                    const position= hashCode(key)
                    this.table[position]=ValuePair(key,value)
                    return true
                  }
                  return false
                }
          

        • 其他方法——get remove

        •       get(key){
          const ValuePair=this.table[this.hashCode(key)]
          return ValuePair==null?undefined:ValuePair
                }
                remove(key){
                  const deletePair=this.table[hash]
                  const hash=this.hashCode(key)
                  if(deletePair!==null){
                    delete this.table[hash]
                    return true
                  }
                        return false
                }
          

        • ES6中的map

        • const obj = {q: 235};
          map.set(obj, 'aaaaa');
          

          在这段代码中,obj是一个常量,引用了一个对象。当你调用map.set(obj, 'aaaaa')时,你是将obj这个引用作为键设置到了Map中。

          如果你尝试直接使用字面量对象作为键,如下所示:

          map.set({q: 123}, 'bbbbb');
          

          这里的问题在于,每次你使用{q: 123}这样的对象字面量时,你实际上是在创建一个全新的对象。在JavaScript中,对象是通过引用来比较的,而不是通过值。因此,即使两个对象字面量看起来相同(例如{q: 123}和另一个{q: 123}),它们却是不同的引用,因此被视为不同的键。总结来说,不能直接把{q: 123}传入map作为键,是因为每次这样写都会创建一个全新的对象,而Map是通过引用来识别键的。

        • wakemap——对象只能是键

        • 有垃圾回收机制,和map不同,一旦object被null,objet就不会占内存,也就不会被遍历


http://www.kler.cn/a/578421.html

相关文章:

  • 【Linux学习笔记】Linux基本指令分析和权限的概念
  • Ubuntu通过局域网共享文件夹实现文件夹的连接
  • 13.C语言指针的易错点
  • Jmeter的脚本录制
  • DeepSpeek服务器繁忙?这几种替代方案帮你流畅使用!(附本地部署教程)
  • 【语料数据爬虫】Python实现将Json语料数据转换成Word文档
  • 从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(十二) socketio 消息处理
  • Ansible安装
  • 2025-3-9 树和森林的遍历
  • 2025.3.9总结
  • laravel中 添加公共/通用 方法/函数
  • Go语言实战,HTTP和gRPC多服务启动与关闭的最佳实践
  • 免费送源码:Java+springboot+MySQL 房屋租赁系统小程序的设计与实现 计算机毕业设计原创定制
  • 部署说明书
  • 网络空间安全(19)CSRF攻防
  • 计算机视觉算法实战——老虎个体识别(主页有源码)
  • Python—类class复习
  • 【jstack查询线程信息】1.对比下arthas的thread 和jvm指令
  • 循环神经网络(RNN):时序建模的核心引擎与演进之路
  • 【C++】vector(下):vector类的模拟实现(含迭代器失效问题)