前端知识点汇总=(我的面试错题本,学习备忘录)=>{...}

目录

【写在最前】

介绍

最近更新内容

【Vue】

Vue传值有几种方式?

父子相互传值

祖父传值

访问实例传值

使用Event Bus传值

使用vuex传值

Vue是怎么实现数据绑定的?

Vue2生命周期

Vue3生命周期

Vue2新增一个新属性(或字段)还是响应式吗?怎么变成响应式?

computed和watch的区别和应用场景

vue-router的params和query的区别?

有父子组件的情况下vue生命周期是怎样的?

【Vuex】

【原生JS】

JS作用域相关问题

块级作用域

变量提升与暂时性死区

JS垃圾回收机制

内存泄漏相关问题

闭包

ES6新增了什么?

继承与原型链

原型与继承的关系

继承的方式 / 构造函数 constructor

类 class

js获取dom元素的几种方式

call,aplly,bind的区别

深浅拷贝

微任务宏任务

JS事件循环

数组原生API

【网络】

axios和传统ajax的区别

axios怎么做全局身份验证?

【CSS相关】

CSS的4种文档流

BFC机制

弹性布局Flex

选择器优先级

三角形

【算法】

记忆函数

斐波那契(黄金分割数列)

最长公共子串

防抖节流

排序

快排

冒泡

希尔

对字符串中的数字排序


【写在最前】

介绍

        本文章是我个人的学习笔记,为了便于在线阅读而作。本文并不全面,因为去掉了部分基础简单且容易查到的知识点,主要是为了把一些我本人常忘记的知识做一个梳理。既然拿出来分享,我尽可能的严谨,也欢迎大家补充。此贴会常常更新。

        文章内容较大,各位结合目录服用为佳。

最近更新内容

【7.26】垃圾回收机制、字符串中的数字排序算法、最长公共字符串算法


【Vue】

Vue传值有几种方式?

  • 父子相互传值

    1. 子传父使用$emit

      <!-- 子组件 -->   
      <template>    
          <button @click="notifyParent">Notify Parent</button>   
      </template>     
      <script>   
          export default {
              methods: {
                  notifyParent() {
                      this.$emit('childEvent', 'Hello from Child');                   
                  }    
              }   
          }   
      </script>     
      <!-- 父组件 -->   
      <template>    
          <ChildComponent @childEvent="handleChildEvent" />   
      </template>     
      <script>   
          import ChildComponent from './ChildComponent.vue';
          export default {
              components: {      
                  ChildComponent    
              },    
              methods: {      
                  handleChildEvent(message) {                                         
                      console.log(message); // 输出 'Hello from Child'     
                  }    
              }   
          }   
      </script>
    2. 父传子使用props

      <!-- 父组件 -->
      <template>  
        <div>  
          <!-- 调用子组件,并通过v-bind或简写:将data传递给子组件的msg属性 -->  
          <ChildComponent :parentName="componentName" />  
        </div>  
      </template>  
        
      <script>  
      import ChildComponent from './ChildComponent.vue';  
      export default {  
          components: {  
              ChildComponent  
            },  
          data() {  
              return {  
                componentName: 'xxx'  
              }  
          }  
      }  
      </script>
      
      <!-- 子组件 -->
      <template>  
        <div>  
          <p>{{ parentName }}</p>  
        </div>  
      </template>  
        
      <script>  
      export default {  
        props: {   
          parentName: {  
            type: String,  
            default: 'No message from parent'  
          }  
        }  
      }  
      </script>
  • 祖父传值

    1. 使用provide/inject。允许祖先组件向其所有子孙组件提供一个依赖(不论组件层次有多深)。
      // 祖先组件   
      <script>  
      export default {    
          provide() {      
              return {        
                  foo: 'foo'      
              }    
          }   
      }     
      // 子孙组件   
      export default {    
          inject: ['foo'],    
          mounted() {      
              console.log(this.foo); // 输出 'foo'    
          }   
      }
      </script>
  • 访问实例传值

    1. 使用ref访问组件的实例

      <child-component ref="child"></child-component>
      this.$refs.child.someMethod('someValue');
    2. 使用使用一个新的vue实例(eventBus)
      // 两个组件都引入新建的 EventBus js文件
      import { EventBus } from '@/event-bus'; 
      ​
      // 传递值或方法给 EventBus
      EventBus.$emit('my-event', { message: 'Hello from ComponentA!' });
      ​
      // 从 EventBus接收值或方法
      EventBus.$on('my-event', (payload) => {  
          this.receivedMessage = payload.message;  
      });
           
  • 使用vuex传值

Vue是怎么实现数据绑定的?

  • Vue 2.x 的 Object.defineProperty

    • 发布订阅模式

    • 当一个 Vue 实例被创建时,Vue 会遍历其 data 对象中的每个属性,并使用 Object.defineProperty 将它们转化为 getter/setter,从而能够在这些属性被访问或修改时执行一些自定义的逻辑。

    • Vue 内部使用了一个叫做 Dep(Dependency)的类来管理依赖。每个属性都有一个与之关联的 Dep 实例,用于存储所有依赖于该属性的 Watcher 实例。

    • Watcher 则是用于监听某个属性变化的实例,它通常与一个模板中的表达式或计算属性相关联。当该属性变化时,Watcher 会重新计算表达式或计算属性的值,并更新相关的 DOM。

    • 当一个属性被访问时(通过 getter),Vue 会检查当前是否存在与该属性关联的 Watcher。如果存在,则将 Watcher 添加到该属性的 Dep 实例中。

    • 当一个属性被修改时(通过 setter),Vue 会触发该属性的 Dep 实例中的所有 Watcher,从而更新相关的 DOM。

  • Vue 3.x 的 Proxy

    • 在 Vue 3.x 中,Vue 使用了 ES6 的 Proxy 来替代 Object.defineProperty 来实现数据的劫持和监听。Proxy 相比 Object.defineProperty 提供了更强大和灵活的数据劫持能力,能够处理数组和对象属性的添加、删除等操作。

Vue2生命周期

  • beforeCreate:实例在内存中被创建出来,但尚未初始化data和methods属性。

  • created:实例创建完成,此时datamethodscomputedwatch 都已经被配置好,但模板还没有被编译。

  • beforeMount:在挂载开始之前被调用,模板被编译成虚拟 DOM,但还没有被渲染成真实的 DOM 元素。

  • mounted:组件的模板被渲染成真实的 DOM 元素,并被挂载到页面上。可以通过 this.$el 访问到组件的根 DOM 元素

  • beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前。

  • updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。

  • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。

  • destroyed:Vue实例销毁后调用。调用后,Vue实例指示的所有东西都会解绑,所有的事件监听器都会被移除,所有的子实例也都会被销毁。

Vue3生命周期

  • setup:组件实例创建之初的钩子,此时组件实例已经被创建,但dataprops等属性还未初始化。你可以在setup函数中进行一些数据初始化操作,或者返回一些需要在模板中使用的响应式状态或方法。

  • beforeMount:组件即将被挂载到DOM树中时的钩子,此时组件的模板已经编译完成,但尚未挂载到页面上。

  • onMounted:组件已经成功挂载到DOM树中后的钩子,此时可以对DOM进行操作,如获取DOM元素的引用或调用第三方库初始化函数。

  • onBeforeUpdate:组件的数据即将更新,重新渲染前调用的钩子。在这个阶段,你可以访问到更新前的DOM,但组件的状态还未更新。

  • onUpdated:组件的数据已经更新完成,DOM已经重新渲染后的钩子。此时你可以获取到更新后的DOM和组件状态。

  • beforeUnmount:组件即将被卸载之前的钩子,可以在这里执行一些清理工作,比如取消定时器、解绑事件等。

  • onUnmounted:组件已经被卸载后的钩子,此时组件的实例已经被销毁,所有的事件监听器和子组件也都被移除。你可以在这里执行一些最后的清理工作。

  • onErrorCaptured:当捕获到来自子孙组件的错误时调用的钩子。你可以在这里处理或记录错误。

Vue2新增一个新属性(或字段)还是响应式吗?怎么变成响应式?

// 对于对象/属性,使用set方法
Vue.set(向data中的哪个属性,添加名为什么的值,值本身)
this.$set(向data中的哪个属性,添加名为什么的值,值本身)
// 对与数组可使用js自带的方法
// push\pop\shift\unshift\splice\slice\sort\reverse
// 这些方法被vue重写了,可实现相关功能

computed和watch的区别和应用场景

  • 区别:

    • computed:

      1、 支持缓存,只有依赖数据发生改变,才会重新进行计算;

      2、 不支持异步,当computed内有异步操作时无效,无法监听数据的变化;

      3、 computed是计算属性,也就是依赖某个值或者props通过计算得来的数据;

      4、 computed的值是在getter执行之后进行缓存的,只有在它依赖的数据发生变化(依赖的数据可以是单个,也可以是多个)时,会重新调用getter来计算;

    • watch:

      1、不支持缓存,数据变,直接会触发相应的操作;

      2、支持异步操作;

      3、watch是监听器,可以监听某一个数据,然后执行相应的操作;

      4、监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;

  • 应用场景:

    • computed:简单的数据处理、过滤。购物车

    • watch:监听用户输入后做处理

vue-router的params和query的区别?

  • 定义路由时params需要占位符

  • 在使用 <router-link> 传递参数,和.push时。query可以使用name和path而params只能使用name

有父子组件的情况下vue生命周期是怎样的?

  • 顺序:

    父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted -> 
    ​
    父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated -> 
    ​
    父beforeDestroy -> 子beforeDestroy -> 子destroyed-> 父destroyed
    

【Vuex】


【原生JS】

JS作用域相关问题

        作用域(Scope)是指程序中定义变量的区域,它决定了变量在何处可访问和不可访问。

  • 块级作用域

        1、指的是变量在定义它的代码块{}内有效,一旦离开这个代码块,变量就不再可用。

        2、块级作用域通过letconst关键字实现。

        3、在ES6之前,只有var。当时的作用域只有两种:全局作用域和函数作用域。

  • 变量提升与暂时性死区

    • 未定义先使用。在ES6前变量==undefind。ES6后变量报错。
      // ES6前
      console.log(a)
      var a = 10
      // 以上代码相当于
      var a;
      console.log(a)
      a = 10
      // 所以
      var name = "mango"
      function showName(){
        console.log(name);
        if(0){
         var myname = "极客邦"
        }
        console.log(name);
      }
      var name = "mango"
      showName(); // undefind undefind

JS垃圾回收机制

目前的主流浏览器都采用“标记-清除(Mark-and-Sweep)”算法。工作步骤:

  1. 标记阶段(Marking Phase)

    • 从根对象(如全局对象、当前执行上下文中的局部变量)出发,递归遍历所有可达对象,并标记这些对象为活动对象。
    • 任何没有被标记的对象都被视为不可达对象,可以进行回收。
  2. 清除阶段(Sweeping Phase)

    • 扫描内存中的所有对象,回收所有未标记为活动的对象,释放它们所占用的内存。

内存泄漏相关问题

尽管有垃圾回收机制,以下情况仍可能导致内存泄漏:

  1. 未解除的事件监听器:注册的事件监听器在不被需要时没有清楚,将无法被回收。

  2. 闭包:闭包可能会持有超出其生命周期的外部变量引用,导致这些变量无法被回收。

  3. 全局变量:全局变量在程序运行期间一直存在,无法被回收。

  4. 定时器和回调函数setInterval没正常清除 / 回调函数可能会引用对象。

  5. DOM引用:JS对DOM元素进行了引用,那么该变量不可被垃圾回收。

闭包

  • 概念:是一个函数以及其捆绑的周边环境状态的引用组合。闭包让开发者可以从内部函数访问外部函数的作用域

  • 形成条件:当 一个函数fn_a 被定义在 另一个函数fn_b 的内部,并且在其内部引用了 外部函数fn_b 的变量时,就形成了闭包

  • 适用场景:

    • 需要封装私有变量

    • 需要实现模块化开发时。模块之间相互独立,使得变量互不干扰

    • 创建函数工厂/动态函数。可以一定的条件 创建多个函数。

    • 处理异步编程

    • 实现回调函数和高阶函数

ES6新增了什么?

  • 新声明方式:let、const

  • 箭头函数:箭头函数的this是定义时的上下文。普通函数的this是调用时的。

  • 结构赋值:...

  • 函数可默认参数

  • 类和继承: class\extends

  • 模板字符串:‘’

  • Map\Set

  • promise

继承与原型链

  • 概念:在继承中js只有一种结构:对象。js为原型继承模式,没有类的概念,或者说js的class并不同于其他语言的class,他可实现的功能ES5都可以做到,class没有带来新的继承方式,可看作的相关功能的语法糖。每个对象(object)都有一个私有属性指向另一个名为原型(prototype)的对象。原型对象也有一个自己的原型,层层向上直到原型为 null

  • 原型与继承的关系

    • someObject.[[Prototype]] == someObject 的原型。 [[Prototype]] 可以通过 Object.getPrototypeOf()Object.setPrototypeOf() 函数来访问。等同于someObject.__proto__(非标准,不推荐)

    • 继承的子对象可直接使用父对象的值和方法。

      const child = {
        __proto__: parent,
      };
      console.log(child.parentMethod(),child.parentData);
  • 继承的方式 / 构造函数 constructor

    • 形式:function 大驼峰方法名

      // 在构造函数内和在原型内定义方法或变量,构造函数会在每次调用时给实例添加东西。区别:构造优先于原型、构造函数内消耗大
      function Animal(sound){
          this.sound = sound;
          this.makeSound = function(){
              return this.makeSound;
          }
      }
      Animal.prototype.makeSoundAgain = function(){
          return this.sound;
      }
      let cat = new Animal('喵');
      console.log(cat.makeSound()); // 喵
      ​
      // 实例的原型 === 构造函数的原型 Object.getPrototypeOf(new Animal()) === Animal.prototype
      // 构造函数原型上会有一个constructor自有属性,它指向构造函数本身。这使得任何实例都能访问原始构造函数。
      ​
      Animal.prototype.makeSound = function(){
          return this.sound;
      }
    • 继承:在ES5中的继承基本都是对 构造函数原型 的引用、复制、改造

      function Parent(val){
          this.arr = [1,2,3]
          this.val = val
      }
      Parent.prototype.makeSound = function(){
          return this.sound;
      }
      ​
      // 1、原型链继承:将父类的实例用作子类的原型。
      function Child(){}
      Child.prototype = new Parent()
      // 缺点:多个子类实例对引用类型的操作会相互影响
      let child1 = new Child()
      child1.arr.push('xxxx')
      let child2 = new Child()
      console.log(child2.arr) // [1,2,3,'xxxx']
      ​
      //================================================================================
      ​
      // 2、借用构造函数继承:子构造函数调用父构造函数,并改变this指向。
      function Child(){
          Parent.call(this)
      }
      // 解决了上述原型链继承的问题
      // 缺点:因为相当于直接使用了父构造函数,所以不能继承原型中的属性和方法。每个子实例都会有父构造函数中的东西,会影响性能
      ​
      //================================================================================
      ​
      // 3、组合继承:原型链和借用构造函数的结合。
      function Child(val,childName){
          // 借助父构造函数,让子实例拥有父构造函数中的变量与方法
          Parent.call(this,val)
          this.childName = childName
      }
      // 并且使用原型链继承,让子实例拥有父构造函数原型的方法或变量
      Child.prototype = new Parent()
      Child.prototype.constructor = Child
      ​
      // 解决了借用构造函数继承的 不能继承原型中的属性/方法的问题
      // 缺点:虽然解决了问题,但是由于既使用了父构造函数 又将原型设为父实例,这导致会有重复的变量/方法(来自于父构造函数中的变量或方法)
      ​
      //================================================================================
      ​
      // 4、原型式继承:将创建构造函数(空的)、设置原型、实例化 封装成一个方法
      function createObj(obj){
          function F(){}
          F.prototype = obj
          return new F();
      }
      let parent = {
          name: 'parent',
          arr:[1,2,3]
      }
      let Child1 = createObj(parent)
      console.log(child1.arr) // 1,2,3
      Child1.arr.push('xxx')
      let child2 = createObj(parent)
      console.log(child2.arr) // 1,2,3,'xxx'
      ​
      // 缺点:构造函数不能传参。而且与原型链继承一样,多个子类实例对引用类型的操作会相互影响。另外,ES5中存在Object.create()的方法,能够代替上面的方法。
      ​
      //================================================================================
      ​
      // 5、寄生式继承:在原型式继承的基础上再套一层函数,以便在这个对象被返回之前做一些升级
      function createCat(Obj){
          let cat = createObj(obj);
          cat.makeSound = function(){
              return '喵'
          }
          return cat
      }
      // 缺点:与原型链/原型式一样的缺点依旧存在
      ​
      //================================================================================
      ​
      // 6、寄生组合式继承:寄生式和组合式的结合。是最成熟的方法!
      function inheritPrototype(Child, Parent){
          var prototype = Object.create(Parent.prototype); // 创建父类原型的一个副本。即让子继承了父的原型,又不会出现像 原型式/原型链那样的 引用类型问题
          prototype.constructor = Child; // 修改constructor指向
          Child.prototype = prototype; // 指定对象,将新创建的对象赋值给子类的原型
      }
      // 创建父类部分
      function Parent(name){
          this.parentName = name;
          this.arr = [1,2,3];
      }
      Parent.prototype.parentName = function(){
          return this.parentName;
      }
      // 子类部分:借用构造函数。避免了因引用类型而互相影响,并支持传参。
      function Child(name, parentName){
          Parent.call(this, parentName);
          this.childName = name
      }
      // 调用方法。将一个父类原型的副本指向子类。此方法只是改造了构造函数的原型而已
      inheritPrototype(Child, Parent);
      ​
      /*
      * 解决了:
      *   引用类型的问题(存在于:原型链、原型式、寄生式)
      *   子实例和子原型重复的问题(存在于:原型链与借用的组合式)
      *   父构造函数不能传参的问题(存在于:原型式继承)
      *   
      */
  • class

    • 是构造函数的语法糖。它被设计为更接近面向对象方式的写法

      class Parent {
          constructor(name){
              this.name = name;
          }
          sayName(){
              return this.name;
          }
      }
    • 继承:extendssuper 关键字可以实现对父类的继承

      class Parent {
          constructor(name, arg) {
              this.name = name;
              this.arg = arg
          }
          get info() {
              return this.name + this.arg
          }
      }
      // 子类通过extends确定继承自哪个父类,再通过super调用父类的构造函数
      class Child extends Parent {
          constructor(name, arg, hobby) {
              super(name, arg);
              this.hobby = hobby
          }
          selfIntroduction() {
              return 'My name is ' + this.info + ' years old' + '. i love ' + this.hobby
          }
      }
      let child = new Child('zwj', 24, 'playComputerGame')
      console.log(child.selfIntroduction()); // My name is zwj24 years old. i love playComputerGame

js获取dom元素的几种方式

  1. 通过id找 getElementById

  2. 通过类名找 getElementByClassName

  3. 通过元素名找 getElementByTagName

  4. 通过css选择器找 querySelector / querySelectorAll

call,aplly,bind的区别

主要是入参方式,和使用方式的区别

fn.call(this, arg1, arg2, ...) // 直接调用fn方法
fn.aplly(this, [argArr]) // 直接调用fn方法
let newFn = fn.bind(this, arg1, arg2,...) // 返回一个新的方法

深浅拷贝

  • 区别:

    • 深拷贝:是对变量本身的复制。对基本数据类型的复制。保存的是值

    • 浅拷贝:是对内存地址的复制。对应用数据类型的复制。相当与C的指针

  • 深拷贝的做法:

    function deepCopy(value, hash = new WeakMap()) {  
      // 处理原始值  
      if (value == null || typeof value !== 'object') {  
        return value;  
      }  
        
      // 检查是否已经拷贝过该对象  
      if (hash.has(value)) {  
        return hash.get(value);  
      } 
      
      // 处理日期对象  
      if (value instanceof Date) {  
        return new Date(value.getTime());  
      }  
      
      // 处理数组  
      if (Array.isArray(value)) {  
        let copy = [];  
        hash.set(value, copy);  
        for (let i = 0; i < value.length; i++) {  
          copy[i] = deepCopy(value[i], hash);  
        }  
        return copy;  
      }  
      
      // 处理普通对象  
      if (value.constructor) {  
        let copy = new value.constructor();  
        hash.set(value, copy);  
        for (let key in value) {  
          if (value.hasOwnProperty(key)) {  
            copy[key] = deepCopy(value[key], hash);  
          }  
        }  
        return copy;  
      }  
      
      // 处理其他对象(如RegExp、Error等),可能需要额外的逻辑  
      throw new Error('Unsupported type');  
    } 

微任务宏任务

  • 区别:

    • 宏任务:通常是由外部环境触发的,如setTimeoutsetIntervalsetImmediate(仅在Node.js中)、I/O操作、UI渲染、事件监听器的回调等。这些任务会被添加到宏任务队列中等待执行。

    • 微任务:通常是由程序本身产生的,如Promise的回调函数(包括.then().catch().finally())、MutationObserver的回调函数、process.nextTick(Node.js特有)等。这些任务会被添加到微任务队列中,等待当前执行栈清空后立即执行。

  • 执行时机:

    • 宏任务:在当前执行栈执行完毕后执行。当任务队列为空时,事件循环机制会从宏任务队列中取出一个宏任务执行。

    • 微任务:在当前宏任务执行完毕后立即执行。即,在一个宏任务执行完毕后,事件循环机制会检查微任务队列,并按照顺序执行其中的微任务。微任务的执行是连续的,直到任务队列清空才会继续执行下一个宏任务。

JS事件循环

数组原生API

  • 添加:

    • 在末尾添加 push()

    • 在头部添加 unshift()

  • 删除:

    • 删除并返回最后一个 pop()

    • 删除并返回第一个 shift()

  • 修改(增删改):

    • splice(从第几个开始,删除几个,插入1,插入2.... )

  • 拷贝:

    • 返回一个新数组。slice(从第几个开始,拷贝几个)

  • 合并:

    • concat(数组一,数组二)

  • 排序:

    • 升序排序:sort()

    • 倒序:reverse()

  • 遍历:

    • 以每个元素为参数执行方法,并返回新数组:map()

    • 以每个元素为参数执行方法,不返回新数组:forEach()

    • 筛选:filter(()=> 条件)

    • 当前元素与上一个元素合并处理:reduce((上一个元素,本次元素)=>{},初始元素)

  • 合并为字符串:

    • join(混入的字符)


【网络】

axios和传统ajax的区别

  • ajax

    • 核心:基于对原生js的XMLHttpReques对象的封装

    • 功能:在更新数据时,无需重载整个网页,让页面可以局部刷新。

    • 原理:

    • 不足:

      1. Ajax请求并不改变页面URL,不能返回(ajax响应请求后直接操作元素),而浏览器的Back和History功能是基于URL的变化来工作的。

      2. 一些手机浏览器并不能很好的支持ajax技术

      3. 不支持node.js

      4. 不够安全。缺乏对一些攻击的防御。如CSRF(跨站请求伪造)

  • axios

    • 核心:基于promise的http库。本质上也是对XMLHttpReques的封装,但是promise

    • 优点:

      • 在请求的各个阶段可以让程序介入。如:请求拦截器、响应拦截器、取消请求

      • 支持node.js

      • axios可以使用 .then() / .catch() 等方法,更加简洁灵活

axios怎么做全局身份验证?

步骤:

  1. 创建axios实例。在做请求前的阶段(创建axios实例的阶段),设置请求拦截器和相应拦截器,留出做身份验证的位置,并返回这个axios实例。
  2. 设置token。在请求后阶段(如请求登录阶段),设置then处理请求返回结果,获取并保存token至本地。
  3. 编写身份验证逻辑。在请求拦截器器中将token放入到请求头里。在相应拦截器中将response直接返回,对error的返回码进行判断,若==401则为验证失败

编程:

  • 创建axios实例部分(创建拦截器部分)
    import axios from 'axios';
    
    // 创建一个 Axios 实例
    const axiosInstance = axios.create({
      baseURL: 'https://api.example.com', // 替换为你的 API 基础 URL
      timeout: 10000, // 请求超时时间
    });
    
    // 请求拦截器
    axiosInstance.interceptors.request.use(
      (config) => {
        // 获取并添加 token 到请求头
        const token = localStorage.getItem('token'); // 假设 token 存储在 localStorage 中
        if (token) {
          config.headers['Authorization'] = `Bearer ${token}`;
        }
        return config;
      },
      (error) => {
        // 处理请求错误
        return Promise.reject(error);
      }
    );
    
    // 响应拦截器
    axiosInstance.interceptors.response.use(
      (response) => {
        // 处理响应数据
        return response;
      },
      (error) => {
        // 处理响应错误
        if (error.response && error.response.status === 401) {
          // 身份验证失败,重定向到登录页面或其他处理
          console.error('身份验证失败,请重新登录');
          window.location.href = '/login'; // 假设登录页面路径为 /login
        }
        return Promise.reject(error);
      }
    );
    
    export default axiosInstance;
    
  • 请求部分(获取token部分)
    import axiosInstance from './path/to/axiosInstance';
    
    // 示例 GET 请求
    axiosInstance.get('/protected/resource')
      .then(response => {
        console.log(response.data);
      })
      .catch(error => {
        console.error('请求出错:', error);
      });
    
    // 示例 POST 请求
    axiosInstance.post('/login', {
      username: 'user',
      password: 'password',
    })
      .then(response => {
        console.log(response.data);
        // 假设登录成功后返回 token
        localStorage.setItem('token', response.data.token);
      })
      .catch(error => {
        console.error('登录出错:', error);
      });
    


【CSS相关】

CSS的3种文档流

文档流主要有3种:标准流、浮动流、定位流

  1. 标准流(正常流):默认排序方式。自上而下从左到右,元素会占据一定的空间,会互相影响。div / p / h 等元素独占一行,span / a / img 会在一行从左到右排列到占满换行。
  2. 浮动流:脱离正常流。使元素绕父元素内侧按某个方向排列。浮动元素因相对正常流有“悬浮”效果,正常流中的元素将无视浮动元素的体积计算。
  3. 定位流:脱离正常流。
    • absolute 使元素相对于最近的非 static 祖先元素定位。当这样的祖先元素不存在时,则相对根元素<html>定位。
    • fixed 是元素相对窗口进行定位。
    • sticky 基于用户滚动在 fixed 和 absolute 之间切换。

BFC机制

  • 定义:BFC是一个独立的渲染区域,它规定了内部的块级元素如何布局,并且与外部的元素相互隔离,使得内外元素的定位不会相互影响。

  • 效果:

    • 隔离浮动影响:在BFC内的浮动元素不会影响到外部布局。换句话说,浮动元素会参与BFC的高度计算,解决了父元素高度塌陷的问题。

    • 计阻止外边距重叠:如果个父元素触发了BFC,那么它与其子元素之间 或者 它的所有子元素之间的 margin 不会重叠。

  • 触发条件:

    • display:table-* / inline-block / flex / grid

    • 块级元素添加overflow属性,且属性不能为visibility

    • position: absolute / fixed

弹性布局Flex

网格布局Grid

justify-* / align-*(当前轴 / 交叉轴方向对齐)

贴边、平分...

  • -items:gridbox中控制子元素对齐。

  • -content:flexbox中分配子元素周围的空间。

  • -self:在子元素中使用

选择器优先级

  • 内联样式:权重为1,0,0,0

  • ID选择器:权重为0,1,0,0

  • 类选择器、属性选择器、伪类:权重为0,0,1,0

  • 标签选择器和伪元素:权重为0,0,0,1

  • 通配符选择器、子选择器、相邻兄弟选择器等:权重为0,0,0,0

三角形

  • 使用边框的方式

    <body>
        <div class="sj"></div>
    </body>
    <style>
        .sj {
            /* 
            要点是设置宽高为0
            而方向为:不设置透明一边的反方向(其他方向设置为透明)
            */
            width: 0;
            height: 0;
            border-left: 30px solid gray;
            border-right: 90px solid green;
            border-bottom: 60px solid red;
        }
    </style>
  • 使用空间坐标系的方式

    <style>
        .sj {
            width: 0;
            height: 0;
            background: #000;
            clip-path: polygon(0 0, 0 100%, 100% 100%, 0 0);
        }
    </style>

【算法】

记忆函数

现给定一个任意的函数 fn ,返回该函数的一个 记忆化 版本。

一个 记忆化 的函数是一个函数,它不会被相同的输入调用两次。而是会返回一个缓存的值(如果两个输入值在 JavaScript 中使用 === 运算符比较时相等,则它们被视为相同)。

function memoize(fn) {
    const argIdMap = new Map();
    const argsMap = new Map();
    let id = 0;
    return function(...args) {
        let key = ""
        // 确保入参的唯一性
        for (let item of args) {
            if (!argIdMap.has(item)) {
                argIdMap.set(item, id++);
            }
            key += argIdMap.get(item);
        }
        if (argsMap.has(key)) {
            return argsMap.get(key);
        } else {
            const res = fn(...args);
            argsMap.set(key, res);
            return res;
        }
    }
}

斐波那契(黄金分割数列)

指的是每项都是前两项之和的数列(0,1,1,2,3,5,8,......)。使用三种方式实现,主要区别在于如何解决下面三个问题:从大到小计算?还是从小到大计算?如何记录每一次的计算数据?

// 递归法
// 思路:每一项都是前两项之和,通过if排除0,1后,由此逻辑,从大到小递归
function fibonacciRecursive(n) {
    if (n <= 1) {
        return n;
    }
    return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}

// 动态规划法
// 思路:每一项都是前两项之和,能够成立的最小数是2,2前面的两个数是[0,1],由此建立数组,从小到大计算并记录
function fibonacciDP(n) {
    if (n <= 1) return n;
    let dp = [0, 1];
    for (let i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}

// 迭代法
// 思路:与动态规划法一样,但直接使用三个值记录:sum(每一步的计算结果)、a(后一个值)、b(后两个值)
function fibonacciIterative(n) {
    if (n <= 1) return n;
    let a = 0, b = 1, sum = 0;
    for (let i = 2; i <= n; i++) {
        sum = a + b;
        a = b;
        b = sum;
    }
    return sum;
}

最长公共子串

需求:对一个字符串中的数字进行排序,但其他字符排序不变

function longestCommonSubstring(str1, str2) {
    const m = str1.length;
    const n = str2.length;
    // 创建一个二维数组来存储中间结果
    const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
    let maxLength = 0; // 记录最长公共子串的长度
    let endIndex = 0; // 记录最长公共子串的结束位置

    for (let i = 1; i <= m; i++) {
        for (let j = 1; j <= n; j++) {
            if (str1[i - 1] === str2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
                if (dp[i][j] > maxLength) {
                    maxLength = dp[i][j];
                    endIndex = i;
                }
            }
        }
    }
    // 提取最长公共子串
    const longestCommonSubstring = str1.slice(endIndex - maxLength, endIndex);
    return longestCommonSubstring;
}

防抖节流

  • 区别:

    • 防抖:将多次执行改为单次执行。或者说是在执行前加入等待

    • 节流:向多次执行加入时间间隔。

  • 基本思想:防抖节流函数会返回出一个被改造过后的方法。

  • /**
     * 防抖函数(回调函数,时间)
     * 作用:
     *      在每次方法执行前设置一个定时器,定时器执行完再执行方法
     * 思路:
     *      防抖 = 等待定时器 -> 执行方法
     */
    function debounce(func, delay = 1000) {
        let timeout; //定时器
        return function () {
            // 如果没有等待足够久,之前等待过的将被清除
            if (timeout) clearTimeout(timeout);
            // 方法被包裹在定时器中,执行方法前总是要等待足够久
            timeout = setTimeout(() => {
                func.call(this);
            }, delay);
        }
    }
  • /**
     * 节流函数(回调函数,时间)
     * 作用:
     *      在方法执行时,会设置一个(暂时关闭)会定时开启的“阀门”
     * 思路:
     *      节流 = 执行 -> 关闸门 -> 等待 -> 开阀门 -> 执行
     */
    function throttle(fn, delay){
    	let valid = true;
    	return function(){
    		if(valid) { //如果阀门已经打开,就继续往下
    			setTimeout(()=> {
    				fn.apply(this, arguments);//定时器结束后执行
    				valid = true;//执行完成后打开阀门
    			}, delay)
    			valid = false;//关闭阀门
    		}
    	}
    }

排序

快排

// 一:取出第一个数为key,留出一个位置n
// 二:设置 l=0 和 r=arr.length-1 两个指针,从左右两边向对方推进
// 三:使用 lr 与 key 相比,比key小或大的lr移动到n,并将lr原来的位置设置为n
// 四:如果三成立,那么两个指针换一个行动
function quickSort(arr, left = 0, right = arr.length - 1) {
    if (left < right) {
        // 调用分区函数,返回中间索引  
        let key = () => {
            // 选择最右侧的arr[right]元素作为基准值  
            let i = left;
            for (let j = left; j < right; j++) {
                // 如果当前元素小于或等于基准值  
                if (arr[j] <= arr[right]) {
                    // 交换  
                    [arr[i], arr[j]] = [arr[j], arr[i]];
                    // 移动索引  
                    i++;
                }
            }
            // 将基准值放到正确的位置  
            [arr[i], arr[right]] = [arr[right], arr[i]];
            return i;
        };
        // 递归排序分区左侧和右侧的子数组  
        quickSort(arr, left, key() - 1);
        quickSort(arr, key() + 1, right);
    }
    return arr;
}

冒泡

function bubbleSort(arr) {  
    let len = arr.length;  
    for (let i = 0; i < len - 1; i++) {  
        for (let j = 0; j < len - 1 - i; j++) {  
            if (arr[j] > arr[j + 1]) { // 如果前一个元素大于后一个元素,则交换它们  
                // 使用解构赋值来交换两个元素的位置  
                [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];  
            }  
        }  
    }  
    return arr;  
}

希尔

function shellSort(arr) {  
    let len = arr.length;  
    let gap = Math.floor(len / 2); // 初始增量  
  
    // 动态定义间隔序列  
    while (gap > 0) {  
        for (let i = gap; i < len; i++) {  
            let temp = arr[i];  
            let j = i;  
            // 插入排序  
            while (j >= gap && arr[j - gap] > temp) {  
                arr[j] = arr[j - gap];  
                j -= gap;  
            }  
            arr[j] = temp;  
        }  
        gap = Math.floor(gap / 2); // 缩小增量  
    }  
    return arr;  
}  

对字符串中的数字排序

需求:对一个字符串中的数字进行排序,但其他字符排序不变

let arrOfStrSort = (str) => {
    // 设置两个数组
    // 位置数组:一个数组装数字在字符串中的位置
    // 字符数组:一个数组装所有的字符
    let numIndexArr = [], numArr = [...str].filter((char, index) => {
        // 用正则过滤出所有是数字的字符装进字符数组,并将其位置push到位置数组
        if ((/\d+/.exec(char))) {
            numIndexArr.push(index);
            return char
        }
        // 至此,每个字符数组中的每个数字,都有位置数组作为其在字符串中的位置映射
    }).sort() // 排序
    // 遍历字符数组,将每个字符串中有数字的位置,替换为正确顺序的数组
    for (let str_j = 0; str_j < numArr.length; str_j++) {
        str = str.slice(0, numIndexArr[str_j]) + numArr[str_j] + str.slice(numIndexArr[str_j] + 1);
    }
    return str
}

我也是有底线的


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值