前端技能栈V1.1

551 阅读9分钟

JS原理篇:

一、函数判断变量的数据类型

在JavaScript中,可以使用typeof运算符来确定变量的数据类型,例如:

// 示例数据
const str = 'Hello, world!';
const num = 42;
const bool = true;
const arr = [1, 2, 3];
const obj = { name: 'John', age: 30 };
const func = () => console.log('Hello');

// 使用 typeof 进行类型判断
console.log(typeof str);   // string
console.log(typeof num);   // number
console.log(typeof bool);  // boolean
console.log(typeof arr);   // object
console.log(typeof obj);   // object
console.log(typeof func);  // function

// 特殊案例:判断 null 和 undefined
console.log(typeof null);          // object
console.log(typeof undefined);     // undefined

typeof一般是用来判断变量是否存在,返回它的类型,其中基本数据类型null返回的是一个object,但是null不属于引用数据类型,typeof除了判断function函数会识别,其他的引用数据类型输出为object

instanceof同样也能判断变量的数据类型,例如:

// 示例数据
const str = 'Hello, world!';
const num = 42;
const arr = [1, 2, 3];
const obj = { name: 'John', age: 30 };
const func = () => console.log('Hello');

// 使用 instanceof 进行类型判断
console.log(str instanceof String);   // false
console.log(num instanceof Number);   // false
console.log(arr instanceof Array);    // true
console.log(obj instanceof Object);   // true
console.log(func instanceof Function);// true

// 特殊案例:判断 null 和 undefined
console.log(null instanceof Object);      // false
console.log(undefined instanceof Object); // false

instanceof 一般是用来判断引用数据类型,但不能正确判断基本数据类型,根据再原型链中查找判断当前数据的原型对象是否存在返回布尔类型

平时开发时要判断数据类型一般推荐使用objct.prototype.toString,统一返回格式[object Xxx]

const dev = (e) => {
  return Object.prototype.toString.call(e).replace(/^\[object (\S+)\]$/, '$1')
}
    console.log(dev('aw')) //String

总结:具体使用哪种方式,还是要看使用场景

二、用reduce统计字符出现频率

const reduces = (e) => {
  return e.split('').reduce((arr , chat) => {
    if(arr[chat]){
      arr[chat]++
    }else{
      arr[chat] = 1
    }
    return arr
  } , {})
}
console.log(reduces('wqqeqqqqqqe'));

使用split()将传入字符串拆分成一个列表,然后再通过reduce函数统计列表中元素数量,最终返回结果

三、借用原型链补充数组的高阶排序方法

原型链是 JavaScript 中的一个重要概念,可以用于共享属性和方法。下面是使用原型链来补充数组的高阶排序方法的示例:

// 定义一个排序方法的原型对象
var sortingMethods = {
  bubbleSort: function() {
    // 冒泡排序逻辑
 let n = this.myArray.length;
      for (let i = 0; i < n-1; i++) {
        for (let j = 0; j < n-i-1; j++) {
          if (this.myArray[j] > this.myArray[j+1]) {
            // 交换位置
            [this.myArray[j], this.myArray[j+1]] = [this.myArray[j+1], this.myArray[j]];
          }
        }
      }
    console.log("Bubble Sort");
  },
  
  quickSort: function() {
    // 快速排序逻辑
    // ...
    console.log("Quick Sort");
  },
  
  mergeSort: function() {
    // 归并排序逻辑
    // ...
    console.log("Merge Sort");
  }
};

// 创建一个数组对象
let myArray = [5, 2, 8, 1, 9];

// 将原型对象的方法添加到数组的原型链上
Object.setPrototypeOf(myArray, sortingMethods);

// 调用数组的排序方法
myArray.bubbleSort(); // 输出: Bubble Sort
myArray.quickSort(); // 输出: Quick Sort
myArray.mergeSort(); // 输出: Merge Sort

在上述代码中,我们创建了一个排序方法的原型对象 sortingMethods,其中包含了几种排序算法的实现。然后,通过 Object.setPrototypeOf() 方法将 myArray 的原型链设置为 sortingMethods,这样 myArray 对象就可以共享原型对象中的方法。

通过这种方式,我们可以在数组对象上直接调用排序方法,例如 myArray.bubbleSort(),并且可以根据具体的需求在原型对象中添加更多的排序方法。

四、递归的实现原理,能做3个递归的常见题

递归是一种重要的编程概念,它指的是在函数的定义中调用函数本身。递归函数通过不断调用自身来解决问题,直到达到满足终止条件为止。

递归的实现原理通常包括以下几个关键步骤:

  1. 定义终止条件:在递归函数中,需要明确指定一个或多个终止条件,用于结束递归过程并返回结果。没有终止条件或终止条件不正确会导致无限递归。
  2. 分解原问题:将原问题拆解为更小的子问题,通常通过传递给递归函数的参数来实现。每一次递归调用都处理一个更小规模的子问题。
  3. 调用自身:在递归函数中,通过调用函数本身来解决子问题。递归函数会被多次调用,每次调用处理一个子问题。
  4. 整合结果:递归函数返回结果,并通常对子问题的结果进行处理和整合。这些结果会不断向上层递归函数传递,直到得到最终结果。
  5. 返回结果:最终,递归函数将返回最终结果给调用方。

递归的实现可以基于栈的思想,每次递归调用将会将当前函数压入栈中,直到达到终止条件,然后逐个弹出栈中的函数并返回结果。

需要注意的是,递归函数的性能开销较大,并且存在调用层级限制(函数调用栈溢出)。在使用递归时,需要谨慎考虑终止条件和递归的停止条件,以及保证递归可以在有限的时间和资源内完成。

总结来说,递归的实现原理包括定义终止条件、分解原问题、调用自身、整合结果和返回结果这几个步骤,通过这些步骤逐步解决子问题并最终获得最终结果。

TypeScript篇

五、ref reactive computed define props define emits 的类型注解

  1. refref是Vue 3中用于创建响应式数据的函数。类型注解可以使用泛型来指定数据的类型。例如:

    typescriptCopy code
    import { ref, Ref } from 'vue';
    
    const count: Ref<number> = ref(0);
    

    这里的count是一个ref函数创建的响应式数据,类型注解指定它是一个Ref<number>类型,表示它是一个number类型的响应式数据。

  2. reactivereactive是Vue 3中用于创建响应式对象的函数。类型注解可以使用泛型来指定对象的类型。例如:

    typescriptCopy code
    import { reactive, ReactiveFlags } from 'vue';
    
    interface User {
      name: string;
      age: number;
    }
    
    const user: User = reactive<User>({
      name: 'John',
      age: 30,
    });
    

    这里的user是一个通过reactive函数创建的响应式对象,类型注解指定它是一个User类型的对象。

  3. computedcomputed是Vue 3中用于创建计算属性的函数。类型注解可以使用泛型来指定计算属性的返回值类型。例如:

    typescriptCopy code
    import { computed, ComputedRef } from 'vue';
    
    const doubleCount: ComputedRef<number> = computed(() => count.value * 2);
    

    这里的doubleCount是一个计算属性,类型注解指定它是一个ComputedRef<number>类型,表示它是一个返回number类型的计算属性。

  4. definePropsdefineProps是Vue 3中用于定义组件接收的Props的函数。类型注解可以使用接口或类型别名来定义Props的类型。例如:

    typescriptCopy code
    import { defineProps } from 'vue';
    
    interface Props {
      message: string;
      count: number;
    }
    
    const props = defineProps<Props>();
    
    // 在组件中使用props.message和props.count
    

    这里的props使用defineProps函数定义,类型注解指定它符合Props接口的类型定义。组件在使用这些Props时,可以通过props.messageprops.count访问。

  5. defineEmitsdefineEmits是Vue 3中用于定义组件传递的事件的函数。类型注解可以使用对象字面量的方式定义事件名称及其处理函数的类型。例如:

    typescriptCopy code
    import { defineEmits } from 'vue';
    
    const emits = defineEmits({
      update: (value: string) => {
        return typeof value === 'string';
      },
      click: () => {
        return true;
      },
    });
    
    // 在组件中使用emits.update和emits.click
    

    这里的emits使用defineEmits函数定义,类型注解指定了事件名称及其处理函数的类型。在组件中使用emits.updateemits.click来传递对应的事件。

六、Interface和Type的区别

在 TypeScript 中,Interface(接口)和 Type(类型别名)都用于定义类型,但它们在语义和使用上有一些区别。

  1. Interface(接口):
  • 使用 interface 关键字定义。
  • 主要用于描述对象的形状,可以定义属性、方法、函数签名等。
  • 支持继承其他接口。
  • 可以被类实现(implement)。

示例:

typescriptCopy code
interface Person {
  name: string;
  age: number;
  greet(): void;
}
  1. Type(类型别名):
  • 使用 type 关键字定义。
  • 可以用于定义任何类型,包括基本类型、联合类型、交叉类型等。
  • 可以使用联合类型和交叉类型等高级类型操作符。
  • 不支持继承或被类实现。

示例:

typescriptCopy code
type Point = {
  x: number;
  y: number;
};

type Result = number | string;

type User = {
  name: string;
  age: number;
};

type Admin = User & {
  role: string;
};

总结一下,Interface 主要用于定义对象的形状和行为,而 Type 可以定义更广泛的类型。当你需要描述一个对象的结构时,可以选择使用 Interface;而当你需要定义复杂的类型,或者需要使用联合类型和交叉类型等高级类型操作时,可以选择使用 Type。在实际使用中,Interface 和 Type 可以根据具体场景和个人喜好来选择使用。

七、字面量和联合类型的使用

字面量和联合类型是在 TypeScript 中用于增强类型定义的特性。

  1. 字面量类型:
    字面量类型允许我们精确地指定一个值的类型。例如,我们可以使用字符串字面量类型来定义一个只能取特定字符串值的变量:

    let status: "success" | "error" | "pending";
    
    status = "success"; // 合法
    status = "error";   // 合法
    status = "pending"; // 合法
    status = "invalid"; // 错误,类型不匹配
    

    在上述示例中,status变量的类型被限定为只能取三个特定的字符串值:"success""error""pending"

  2. 联合类型:
    联合类型允许我们将多个类型中的至少一个作为变量的类型。使用 | 运算符来分隔不同的类型。例如:

    let result: number | string;
    
    result = 42;        // 合法
    result = "success"; // 合法
    result = true;      // 错误,类型不匹配
    

    在上述示例中,result变量的类型为 number 或 string,可以用来存储不同类型的值。

通过使用字面量类型和联合类型,我们可以更加精确地定义变量的类型,并限制其取值范围,增加代码的可读性和类型安全性。

八、Axios在TypeScript中的使用

当使用 TypeScript 来使用 Axios 这个 HTTP 客户端库时,首先需要安装 Axios 并导入相关的 TypeScript 类型定义。PS:安装/导入可自行查询

Axios 官方文档 - 官方文档提供了对 Axios 的详细介绍,包括安装、用法、请求配置、拦截器、错误处理等。

Axios具体用法示例:

axios.get('接口地址')
  .then((response: AxiosResponse) => {
    // 处理成功响应
    const data = response.data;
    console.log(data);
  })
  .catch((error: AxiosError) => {
    // 处理请求错误
    if (error.response) {
      // 请求发出,但服务端响应状态码不在 2xx 范围内
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else {
      // 请求未发出,例如网络错误
      console.log(error.message);
    }
  });

这就是在 TypeScript 中使用 Axios 的基本步骤。你可以根据需要使用不同的 Axios 方法(如 POST、PUT、DELETE 等)来发送不同类型的请求,然后处理响应和错误。