面试题-js篇

文章目录

  • 一、数据类型
    • [1. JavaScript有哪些数据类型,它们的区别?](#1. JavaScript有哪些数据类型,它们的区别?)
      • 1.1.null和undefined区别
      • [1.2. typeof null 的结果是什么,为什么?](#1.2. typeof null 的结果是什么,为什么?)
    • 2.NaN
      • [2.1.typeof NaN 的结果是什么?](#2.1.typeof NaN 的结果是什么?)
      • [2.2.isNaN 和 Number.isNaN 函数的区别?](#2.2.isNaN 和 Number.isNaN 函数的区别?)
    • 3.判断数据类型方式
      • [3.1.intanceof 操作符的实现原理及实现](#3.1.intanceof 操作符的实现原理及实现)
    • [4. 判断数组的方式有哪些](#4. 判断数组的方式有哪些)
    • [4. 为什么0.1+0.2 ! == 0.3,如何让其相等](#4. 为什么0.1+0.2 ! == 0.3,如何让其相等)
    • [5. 如何获取安全的 undefined 值?](#5. 如何获取安全的 undefined 值?)
    • 6.js类型转换规则
      • [6.1. `==` 操作符的强制类型转换规则?及与 `===`、`Object.is()`的区别?](#6.1. == 操作符的强制类型转换规则?及与 ===Object.is()的区别?)
    • [7. || 和 && 操作符的返回值?](#7. || 和 && 操作符的返回值?)
    • [9. 什么是 JavaScript 中的包装类型?](#9. 什么是 JavaScript 中的包装类型?)
    • [10. 为什么会有BigInt的提案?](#10. 为什么会有BigInt的提案?)
    • [11. object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别](#11. object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别)
    • [12. 如何判断一个对象是空对象](#12. 如何判断一个对象是空对象)
  • 二、ES6
    • [1. ES6常用新特性](#1. ES6常用新特性)
    • 1.let、const、var的区别
    • 2、箭头函数及和普通函数的区别
    • 3.模板字符串
    • [4.函数参数增强:默认参数 + 剩余参数](#4.函数参数增强:默认参数 + 剩余参数)
    • 5.对象、数组的解构
    • [6.数组 / 对象扩展:扩展运算符(...)(浅拷贝)](#6.数组 / 对象扩展:扩展运算符(...)(浅拷贝))
    • [7. 面向对象:Class](#7. 面向对象:Class)
    • [8. 模块化:import /export](#8. 模块化:import /export)
    • [9. 新数据结构:Set / Map](#9. 新数据结构:Set / Map)
    • [10. 数组、字符串方法](#10. 数组、字符串方法)
      • [10.1. 字符串](#10.1. 字符串)
      • [10.2. 数组](#10.2. 数组)
    • [11. Symbol](#11. Symbol)
    • 12、for...in(es5)、for...of(es6)
    • [13. Proxy 可以实现什么功能?](#13. Proxy 可以实现什么功能?)
    • [14. 如何提取高度嵌套的对象里的指定属性?](#14. 如何提取高度嵌套的对象里的指定属性?)
  • 三、异步编程
    • [1. 什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?](#1. 什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?)
    • 2、Promise(解决回调地狱)
    • 3.async/await(es8)
    • [4. async/await对比Promise的区别](#4. async/await对比Promise的区别)
    • [5、generator. ??](#5、generator. ??)
    • [6. 并发与并行的区别?](#6. 并发与并行的区别?)
    • [7. setTimeout、setInterval、requestAnimationFrame 各有什么特点?](#7. setTimeout、setInterval、requestAnimationFrame 各有什么特点?)
    • [8、事件循环 EventLoop ?](#8、事件循环 EventLoop ?)
  • 四、JavaScript基础
    • [1. new操作符的实现原理](#1. new操作符的实现原理)
    • [2. js有哪些内置对象](#2. js有哪些内置对象)
    • [3. 对JSON的理解](#3. 对JSON的理解)
    • [4. js脚本`<script>`延迟加载的方式有哪些?](#4. js脚本<script>延迟加载的方式有哪些?)
    • [5. js 类数组对象的定义?](#5. js 类数组对象的定义?)
    • [6. Unicode、UTF-8、UTF-16、UTF-32的区别?](#6. Unicode、UTF-8、UTF-16、UTF-32的区别?)
    • [7. 常见的位运算符有哪些?其计算规则是什么?](#7. 常见的位运算符有哪些?其计算规则是什么?)
    • [8. 什么是 DOM 和 BOM?](#8. 什么是 DOM 和 BOM?)
    • [9. escape、encodeURI、encodeURIComponent 的区别](#9. escape、encodeURI、encodeURIComponent 的区别)
    • [10. JavaScript为什么要进行变量提升,它导致了什么问题?](#10. JavaScript为什么要进行变量提升,它导致了什么问题?)
    • [11. 什么是尾调用,使用尾调用有什么好处?](#11. 什么是尾调用,使用尾调用有什么好处?)
    • [12. use strict是什么意思 ? 使用它区别是什么?](#12. use strict是什么意思 ? 使用它区别是什么?)
    • [13. 如何判断一个对象是否属于某个类?](#13. 如何判断一个对象是否属于某个类?)
    • [14. 对AJAX的理解,实现一个AJAX请求](#14. 对AJAX的理解,实现一个AJAX请求)
    • [15. ajax、axios、fetch的区别](#15. ajax、axios、fetch的区别)
  • [五、原型与原型链 ??](#五、原型与原型链 ??)
    • [41. 对原型、原型链的理解](#41. 对原型、原型链的理解)
    • [42. 原型修改、重写](#42. 原型修改、重写)
    • [43. 原型链指向](#43. 原型链指向)
    • [44. 原型链的终点是什么?如何打印出原型链的终点?](#44. 原型链的终点是什么?如何打印出原型链的终点?)
    • [45. 如何获得对象非原型链上的属性?](#45. 如何获得对象非原型链上的属性?)
  • 六、执行上下文/作用域链/闭包
    • [1. 对闭包的理解](#1. 对闭包的理解)
      • [1.1 .使用场景四、防抖与节流](#1.1 .使用场景四、防抖与节流)
    • [2. 对作用域、作用域链的理解](#2. 对作用域、作用域链的理解)
    • [3. 对执行上下文的理解](#3. 对执行上下文的理解)
  • 七、this/call/apply/bind
    • [1. 对this对象的理解](#1. 对this对象的理解)
    • [2. call() 和 apply() 的区别?](#2. call() 和 apply() 的区别?)
    • [3. 实现call、apply 及 bind 函数](#3. 实现call、apply 及 bind 函数)
    • [连续多个 bind,最后this指向是什么?](#连续多个 bind,最后this指向是什么?)
  • 八、面向对象
    • [1. 对象创建的方式有哪些?](#1. 对象创建的方式有哪些?)
    • [2. 对象继承的方式有哪些?](#2. 对象继承的方式有哪些?)
  • 九、垃圾回收与内存泄漏
    • [1. 浏览器的垃圾回收机制](#1. 浏览器的垃圾回收机制)
    • [2. 哪些情况会导致内存泄漏](#2. 哪些情况会导致内存泄漏)

一、数据类型

1. JavaScript有哪些数据类型,它们的区别?

总结: 7种基本数据类型 (Undefined、Null、Boolean、Number、String、Symbol、BigInt)+ 1种引用数据类型(Object)

区别:

  • 存储位置不同: 基本 (值本身存储在栈中);引用(地址存储在栈中,值存储在堆中)
  • 可变性: 基本 (不可变。修改,本质是创建了新的值,而非修改原有的值);引用(可变)
  • 比较方式: 基本 (按值比较);引用(按引用比较,比较地址)
  • 复制方式: 基本 (复制后会创建一个新的独立值);引用(复制的是地址引用,指向同一个对象)

其中Symbol 和 BigInt 是ES6 中新增的数据类型:

  • Symbol(ES6):唯一且不可变的标识符,用于对象属性名。
  • BigInt(ES2020):任意精度整数,后缀需加 n,如 9007199254740993n。

1.1.null和undefined区别

总结: 都是基本数据类型 ;undefined表示未定义 ,如声明了还没定义会返回它;null表示空对象 ,通常用来初始化,赋值给会返回对象的变量。

相同点: undefined 和 null 都是基本数据类型

不同点

  • undefined 代表的含义是未定义null 代表的含义是空对象
  • 变量声明了但还没有定义 的时候会返回 undefinednull主要用于赋值 给一些可能会返回对象的变量,作为初始化。
  • 使用 typeof 进行判断时,Null 类型化会返回 "object"

1.2. typeof null 的结果是什么,为什么?

总结: null使用typeof判断时,会返回object,因为对象类型的二进制标识符是000,而null被认为是一个空指针,即零地址(值全是0),所以会被错误的标识为object

结果是"object"

javascript的值是二进制数据 ,用低位表示数据类型
对象的类型 标识符 是000 ,null被认为是一个空指针,即零地址,值全是0 ,所以被错误的标识为object

javascript 复制代码
000: object   - 当前存储的数据指向一个对象。
  1: int      - 当前存储的数据是一个 31 位的有符号整数。
010: double   - 当前存储的数据指向一个双精度的浮点数。
100: string   - 当前存储的数据指向一个字符串。
110: boolean  - 当前存储的数据是布尔值。

2.NaN

NaN(Not-a-Number)是 js 中被视为数字类型的一个特殊值 ,表示无法计算出合法的数值
特点:

  • typeof NaN 仍然是 number,尽管它表示"不是数字"。
  • NaN任何值都不相等,包括它自身 ,即 NaN !== NaN。
    - isNaN(NaN) 返回 true

2.1.typeof NaN 的结果是什么?

总结: NaN意思不是一个数字 ,用来标识数学运算的结果非法 的或者未定义 的。在js中它被视为数字类型里 的一个特殊值 。所以typeof NaN 的结果是number

NaN 表示 "Not a Number",用于表示非法的或未定义的数学运算结果。

typeof NaN 的结果是 "number" ,因为 NaN 在 js 中被视为数字类型的一个特殊值

javascript 复制代码
typeof NaN; // "number"
//不用typeof判断,用isNaN判断
console.log(isNaN(123)) // 输出 false,因为 123 可以转换成数值
console.log(isNaN(NaN)) // 输出 true,因为 NaN 是 NaN

2.2.isNaN 和 Number.isNaN 函数的区别?

总结: 两者都是用来判断数据是否是NaN数据类型 的。区别在isNaN 判断之前会先将值转成数值 再去判断,这就导致一些不是NaN的值也被误判为NaN ;而Number.isNaN不转类型 ,只有参数本身是NaN 时才会返回 true。

  • isNaN(value)是 js 提供的全局方法。
    • 作用:将传入的值转换为数值,然后检查该数值是否为 NaN
    • 注意点:它会在检查前 尝试将参数转换为数值 ,导致一些非 NaN 值也被误判为 NaN
  • Number.isNaN(value)ES6 引入的方法,它不会进行类型转换 ,只有当参数本身是 NaN 时才会返回 true
javascript 复制代码
console.log(isNaN(NaN));        // true
console.log(isNaN('hello'));    // true ("hello" 无法转换成数字,最终变成 NaN)
console.log(isNaN('123'));      // false (字符串 "123" 可转换为数字 123)
console.log(isNaN(undefined));  // true (undefined 转换成 NaN)
console.log(isNaN(null));       // false (null 转换为 0)

//
console.log(Number.isNaN(NaN));        // true
console.log(Number.isNaN('hello'));    // false (不会进行类型转换)

3.判断数据类型方式

总结: 判断基本数据类型typeof;判断引用 数据类型用instanceof(判断数组还可以用Array.isArray);更复杂的用Object.prototype.toString.call()

  • typeof
    • 判断基本数据 类型(除了null),返回字符串
    • 注意点:数组、对象、null都会被判断为object
  • instanceof:只能判断引用数据 类型,而不能判断基本数据类型
  • Object.prototype.toString.call():Object 对象的原型方法 toString 来判断数据类型
  • constructor不能判断null,undefined。其它的都可以。由于类的constructor可以随意更改,此时会存在判断不准确的问题
  • Array.isArray():判断是否是数组
javascript 复制代码
console.log(typeof true);            // boolean

console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true

var a = Object.prototype.toString;
console.log(a.call(function(){}));
console.log(a.call(null));

console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true

3.1.intanceof 操作符的实现原理及实现

原理:通过检查对象的原型链 ,看对象的原型链上是否存在指定类型的原型
实现 :当你使用 object instanceof Type 时,js 引擎会沿着 object 的原型链向上查找 ,看是否能在原型链中找到 Type.prototype 。如果找到,就返回 true;否则返回 false。

javascript 复制代码
function myInstanceof(left, right) {
  // 获取对象的原型
  let proto = Object.getPrototypeOf(left)
  // 获取构造函数的 prototype 对象
  let prototype = right.prototype; 
 
  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto);
  }
}

4. 判断数组的方式有哪些

  • Object.prototype.toString.call()
  • 原型链
  • ES6的Array.isArray()(常用)
  • instanceof(常用)
  • Array.prototype.isPrototypeOf
javascript 复制代码
Object.prototype.toString.call(obj) === 'Array';
obj.__proto__ === Array.prototype;
Array.isArrray(obj);
obj instanceof Array
Array.prototype.isPrototypeOf(obj)

4. 为什么0.1+0.2 ! == 0.3,如何让其相等

总结: 浮点数问题的本质是二进制存储精度误差 ,像0.1这样的小数无法被二进制精确表示,所以计算时会出现误差。
解决方法: toFixed(控制误差范围);转成整数计算

javascript 复制代码
let n1 = 0.1, n2 = 0.2
console.log(n1 + n2)  // 0.30000000000000004

console.log(n1*10+n2*10)
//让其相等。toFixed(num)方法可把 Number 四舍五入为指定小数位数的数字
(n1 + n2).toFixed(2) // 注意,toFixed为四舍五入

5. 如何获取安全的 undefined 值?

总结: es5之前 它是个可写属性,可以修改它的值会出现问题,所以使用void标识 符,后面放任意表达式,永远返回undefined;es6之后,它被定义为不可写了。

es5 之前undefined是全局对象的一个可写属性 ,开发者可以修改它的值,会出现问题
es5 之后,全局的 undefined 被定义为不可写

void 运算符的特性

  • void 会执行其后的表达式 ,但无论表达式结果是什么永远返回 undefined
  • void 0 是最简洁的写法(0 是合法且最短的表达式)
javascript 复制代码
undefined = 123; // 在旧环境中可能成功
console.log(undefined); // 可能输出 123,而不是预期的 undefined

//void 
console.log(void 0);       // undefined
console.log(void "hello"); // undefined
console.log(void (1+1));   // undefined

6.js类型转换规则

总结: 分为显式和隐式类型转换显式 主要用到String()、Number()、Boolean();Boolean()主要是用运算符,如比较运算符、算术运算 符。其中><优先转数字;+有字符串转字符串,没转数字;其他-、*、/都转数字;==按优先级转。

  • 显式类型转换
    • 转为字符串: toString()String()
      • boolean->"true"/"false"
      • undefined、null->"undefined"/"null"
      • {}->"[object Object]"
      • [1,2,3]->"1,2,3"
    • 转为数字: Number()parseInt()(没Number严格,逐个解析字符,遇到不能转换的字符就停下来)、parseFloat()
      • string->空字符串0;仅数字的字符串数字;非纯数字字符串NaN
      • boolean->0/1
      • undefined、null->NaN/0
      • Object->仅包含单个数字的数组数字;其他NaN
    • 转为布尔: Boolean()
      • 转为false的情况:undefined、null、0、-0、""、NaN
      • 转为true的情况:{}、[]、其他非空值
  • 隐式类型转换
    • 比较运算(==、>、<)、ifwhile需要布尔值地方
      • >、<:优先转数字
      • if、while:自动转布尔值
      • ==:按优先级转换类型
    • 算术运算+、-、*、/、%
      • +: 有一边是string,就拼接;没有string,转number
      • 其他运算符:转成Number

显式类型转换

javascript 复制代码
//转为字符串
String(true) // "true"/"false"
String(undefined) // "undefined"
String(null) // "null"
//对象
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"

//转数字
Number('') // 0
Number('324') // 324
Number('324abc') // NaN
Number(true) // 1/0
Number(undefined) // NaN
Number(null) // 0
// 对象:通常转换成NaN(除了只包含单个数值的数组)
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5

Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true

隐式类型转换

javascript 复制代码
"3" + 4;        // "34"(数字转字符串)
[] + {};        // "[object Object]"([] 转 "",{} 转 "[object Object]")
true + 1;       // 2(true → 1)
null + 1;       // 1(null → 0)

"5" - 2;    // 3
"10" / "2"; // 5
"abc" - 1;  // NaN

"10" > 2;    // true("10" → 10)
"abc" > 1;   // false("abc" → NaN,比较返回 false)

if ("hello") { ... }  // true(非空字符串)

//对象先调用 valueOf()(默认返回对象自身),再调用 toString()
[] + 1;          // "1"([] → "" → 字符串拼接)
{} + [];         // 0({} 被解析为空代码块,实际计算 +[] → 0)

6.1. == 操作符的强制类型转换规则?及与 ===Object.is()的区别?

  • == :抽象相等,比较前先类型转换,再比值
  • ===:严格相等,比较两个值的数据和类型是否全相等
  • Object.is():行为与 === 基本一致,除**+0不等于-0;NaN等于自身**

== 比较规则

  • NaN不等于任何值,包括自己
  • null和undefined不等于其它,除了null和undefined
  • 字符串 vs 数字 → 字符串转数字
  • 布尔值 vs 其他类型 → 布尔值转数字
  • 对象 vs 原始类型 → 对象转原始值(先使用valueOf,再使用toString)

对象转原始值 (valueOf、toString)

调用 valueOf():尝试获取原始值。

  • 如果返回原始值,则使用它。
  • 如果返回对象 ,继续调用 toString():将对象转为字符串

内置对象使用valueof()返回

  • 对象: 返回 对象本身
  • 数组: 返回 数组本身
  • Date: 返回 时间戳(数字)
  • 包装对象: 返回 原始值
javascript 复制代码
//对象 valueOf() 返回对象本身
{} == "[object Object]";  // true
{} == 0;                  // "[object Object]" → NaN → false

//数组 valueOf() 返回数组本身
[] == 0;       // [] → "" → 0 → true
[1] == "1";    // [1] → "1" → true
[1, 2] == 0;   // [1,2] → "1,2" → NaN → false

//Date valueOf() 返回时间戳(数字)
const date = new Date();
date == date.toString();  // date → 时间戳 → 与字符串比较 → false
date == date.valueOf();    // 时间戳 == 时间戳 → true

//包装对象(Number、String、Boolean)valueOf() 返回原始值
new Number(1) == 1;    // true(Number对象 → 1)
new String("a") == "a";// true(String对象 → "a")

7. || 和 && 操作符的返回值?

  • ||:寻找第一个真值,常用于默认值。
  • &&:寻找第一个假值,常用于条件执行或安全访问
  • && 的优先级高于 ||

9. 什么是 JavaScript 中的包装类型?

总结:基本数据类型 (String、Number、Boolean)包装成有属性和方法的对象 。如string.length、number.toFixed等。js在内部自动包装 ,所以可以直接使用,就等同于new实例化

js中,原始值没有方法或属性 ,但为了能够 使用方法 和属性。

js提供了包装类型允许基本类型(如String、Number、Boolean)临时"包装"为对象,从而调用对象方法。,比如字符串和数字的 length 属性、toUpperCase() 方法等。

三种基本包装类型

  • String(字符串包装类型): 对原始字符串类型 string 的包装(如 toUpperCase()、toLowerCase() 等)
  • **Number(数字包装类型):**对原始 number 类型的包装,它为数字提供一些方法(如toFixed() 或 toPrecision())
  • **Boolean(布尔值包装类型):**原始布尔类型 boolean 的包装
javascript 复制代码
//js在内部自动包装为 String 对象
let greeting = "hello";
console.log(greeting.toUpperCase());  // 输出: "HELLO"
//等同于
let wrappedStr = new String("hello");
console.log(wrappedStr.toUpperCase());  // 输出: "HELLO"

console.log((3.213445).toFixed(2));  // 输出: "3.14"
//等同于
let wrappedNum = new Number(num);
console.log(wrappedNum.toFixed(2));  // 输出: "42.00"

let isActive = false;
if (new Boolean(isActive)) {
  console.log("This is active");
} else {
  console.log("This is not active");  // 这行代码会被执行
}

10. 为什么会有BigInt的提案?

总结: 因为js中number类型 有一个最大安全整数限制 ,在这个范围内,精度不会丢失;超过这个范围,计算就会不准确了。因此用BigInt处理大数计算时精度问题

BigInt的提案主要是由于js中Number类型在处理大数时的精度问题。‌

js中的Number类型 有一个最大安全整数限制 ,即Number.MAX_SAFE_INTEGER。

在这个范围内,计算结果不会出现精度丢失

但一旦超过这个范围,就会出现计算不准确的情况。

这在需要进行大数计算时,不得不依靠一些第三方库来解决,因此官方提出了BigInt来解决此问题‌。

11. object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别

  • 浅拷贝 ‌:
    • 只复制对象的第一层属性 ,嵌套对象是引用复制。修改嵌套对象的属性会影响原对象。
    • 实现方式: 扩展运算符...Object.assign()
  • 深拷贝 ‌:
    • 递归复制对象及其所有嵌套对象 ,确保每个层级的内容都被独立复制,不再共享引用地址。
    • 实现方式: JSON.parse(JSON.strigify())

JSON.parse(JSON.strigify())缺点

  • 无法 处理函数和undefined
  • 无法 处理循环饮用(对象某属性引用了对象本身)
  • 无法 处理一些特殊对象 ,如Error、Date,会被转为空对象
  • 对象的原型链上的属性会丢失(如果属性是继承下来的)

12. 如何判断一个对象是空对象

  • JSON.strigify(obj) == '{}'
  • Object.keys(obj).length === 0

二、ES6

1. ES6常用新特性

  • let、const(解决 var声明的变量存在变量提升、重复声明覆盖、块级无隔离三大问题)
  • 箭头函数(解决了「this 绑定混乱」的核心问题,简化写法)
  • 模板字符串(优化字符串)
  • 函数参数增强:默认参数 + 剩余参数(rest) ...rest(在键那边,用了存储数据)
  • 对象、数组的解构(简化变量赋值)
  • 数组 / 对象扩展:扩展运算符(...) ...[1,2] (在值那边,用来解构值)
  • 面向对象:Class
  • 模块化:import /export
  • 新数据结构:Set / Map
  • 异步编程:Promise(解决回调地狱)
  • 数组、字符串新方法
  • Symbol

1.let、const、var的区别

总结: 从5个方面来说,

1、作用域上 ,var有函数作用域(全局变量和局部变量);let、const有块级作用域({})。

2、变量提升 上,var可以变量提升,在声明前访问;let、const不行。

3、重复声明 ,var可以;let、const不可以

4、值的可变性 ,var、let可以,const不可以

5、全局对象属性,var全局变量会挂载到window对象上;而let、const没有

let 声明变量;const声明常量
区别

  • 作用域:
    • var:有函数作用域。有全局变量和函数级变量
    • let、const:有块级作用域,仅在声明的代码块(由 {} 界定)内有效
  • 变量提升‌:
    • var存在变量提升可以在声明之前被访问,其初始值为 undefined。‌
    • let、const不存在变量提升 。在声明之前访问会引发引用错误,此区域称为"暂时性死区"。
  • 重复声明‌:
    • var允许 在同一作用域内重复声明 同一个变量,后者会覆盖前者。‌
    • let、const不允许在 同一作用域内重复声明 同一个变量,否则会引发语法错误。
  • 值的可变性‌:
    • var、let:变量在初始化后,其值可以被重新赋值修改
    • const:声明常量时必须立即初始化 ,且基本类型的值不可更改引用类型 (如对象、数组),其指向的内存地址不可变 ,但对象属性或数组元素可以修改
  • 全局对象属性:
    • var全局作用域下声明变量时,该变量会挂载到window对象上
    • let、const:没有全局对象属性

2、箭头函数及和普通函数的区别

总结: 箭头函数简化 了普通函数的写法 ;箭头函数没有自己的this ,它的this来自外层包裹函数(声明时的this);没有arguments对象,也不能new调用。

简化了函数的声明语法,更解决了「this 绑定混乱」的核心问题

特性:(和普通函数的区别)

  • **语法简化:**省略function关键字,写法:()=>{}
  • this 绑定固定: 箭头函数没有自己的this ,它的this继承自「外层作用域的 this」 (即声明时的 this,而非调用时的 this);
  • **无 arguments 对象:**无法通过arguments获取参数列表,需用「剩余参数」替代
  • **不能作为构造函数:**不能用new关键字调用,否则报错
javascript 复制代码
const add = (a, b) => a + b;
console.log(add(3, 4)); // 7

const calculate = (a, b) => {
  const sum = a + b;
  const product = a * b;
  return sum + product; // 必须显式return
};
console.log(calculate(2, 3)); // 2+3 + 2*3 = 11

const user = {
  name: "张三",
  // 传统函数:this指向调用者(user)
  sayName1: function() {
    setTimeout(function() {
      console.log(this.name); // undefined(this指向window/global)
    }, 100);
  },
  // 箭头函数:this继承外层作用域(user)
  sayName2: function() {
    setTimeout(() => {
      console.log(this.name); // 张三(正确)
    }, 100);
  }
};
user.sayName1(); // undefined
user.sayName2(); // 张三

3.模板字符串

ES6 之前,拼接字符串需要用**+连接**,遇到多行字符串或变量插值时,代码冗长且易出错。

ES6之后,使用模板字符串 用 **反引号()** 包裹,用${}`来插入变量

特性:

  • 多行字符串: 直接换行即可,无需\n或+
  • 变量 / 表达式插值:${}包裹变量或表达式,自动解析并拼接
  • **标签模板:**可通过函数自定义模板字符串的解析逻辑
js 复制代码
// 1. 多行字符串:直接换行
const html = `
  <div class="user">
    <h1>用户信息</h1>
    <p>姓名:张三</p>
  </div>
`;
console.log(html); // 输出带换行的完整HTML字符串
 
// 2. 变量插值:${}包裹变量
const name = "张三";
const age = 25;
const info = `我叫${name},今年${age}岁`;
console.log(info); // 我叫张三,今年25岁
 
// 3. 表达式插值:${}内可写任意表达式
const a = 10;
const b = 20;
const calc = `${a} + ${b} = ${a + b}`;
console.log(calc); // 10 + 20 = 30
 
// 4. 标签模板(进阶):自定义解析逻辑
function format(strings, ...values) {
  // strings:模板字符串的静态部分数组
  // values:插值部分的数组
  return strings[0] + values[0].toUpperCase() + strings[1];
}
const result = format`Hello, ${name}!`;
console.log(result); // Hello, 张三!(若name是小写,会转为大写)

4.函数参数增强:默认参数 + 剩余参数

默认参数: 在函数声明时直接为参数设置默认值,当参数未传递或为undefined时生效
剩余参数: 用...rest表示,收集函数的「剩余参数」并组成一个数组,替代arguments(arguments是类数组,需转换为数组才能用数组方法)

javascript 复制代码
//默认参数
function add(a, b = 0) { // 正确:非默认参数在前
  return a + b;
}

//剩余参数
function printFirstAndRest(first, ...rest) {
  console.log("第一个参数:", first);  //1
  console.log("剩余参数:", rest); // [2,3,4]
}
printFirstAndRest(1, 2, 3, 4);

// 3. 对比arguments:剩余参数是数组,arguments是类数组
function compareArgs(...rest) {
  console.log(rest instanceof Array); // true(数组)
  console.log(arguments instanceof Array); // false(类数组)
  // arguments需转换为数组才能用reduce:Array.from(arguments).reduce(...)
}
compareArgs(1, 2, 3);

5.对象、数组的解构

总结: 对象按属性名匹配;数组按下标匹配

数组解构:按位置匹配 (根据数组的「索引位置 」匹配变量,支持默认值、嵌套解构)
对象解构:按属性名匹配 (根据对象的「属性名」匹配变量,支持别名、默认值、嵌套解构)

javascript 复制代码
// 1. 基本数组解构
const [a, b, c] = [10, 20, 30];
// 2. 跳过元素:用空逗号占位
const [x, , y] = [1, 2, 3];  //x=1;y=3
// 3. 默认值:当解构的值为undefined时使用默认值
const [m, n = 5] = [3]; // m=3;n=5
// 4. 嵌套数组解构
const [p, [q, r]] = [1, [2, 3]]; //p=1;q=2;r=3
// 5. 与扩展运算符结合:获取剩余元素
const [first, ...rest] = [1, 2, 3, 4];  //first=1;rest=[2,3,4]

6.数组 / 对象扩展:扩展运算符(...)(浅拷贝)

扩展运算符(...)可以将「可迭代对象 」(数组、Set、字符串等)或「对象」展开为单个元素 / 属性,常用于数组拷贝、 合并 ,对象拷贝、合并等场景

**数组扩展:**将数组展开为逗号分隔的元素列表,支持拷贝、合并、函数传参

**对象扩展:**将对象展开为键值对列表,支持对象拷贝、合并、添加新属性

javascript 复制代码
// 1. 数组拷贝:创建新数组(浅拷贝)
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // 等价于arr1.slice() 
arr2.push(4);
console.log(arr2); // [1, 2, 3, 4]
 
// 2. 数组合并:替代concat方法
const arr3 = [4, 5, 6];
const mergedArr = [...arr1, ...arr3]; // 合并arr1和arr3, 结果[1, 2, 3, 4, 5, 6]
 
// 3. 函数传参:将数组展开为函数参数
function add(a, b, c) {
  return a + b + c;
}
const nums = [1, 2, 3];
console.log(add(...nums)); // 6(等价于add(1, 2, 3))

//对象用法同数组
const obj1 = { a: 1, b: 2 };
const newObj = { ...obj1, c: 3, a: 10 }; // 新增c,修改a。结果{ a: 10, b: 2, c: 3 }

7. 面向对象:Class

总结: 使用class定义类 ,里面可以定义属性、方法、constructor(构造函数)。使用它的好处是,可以使用extends继承 这个类,还可以继承后再扩展 ,增加了代码的复用性。(子类使用父类的构造函数 需要用super调用

特性

  • 类定义 :用class关键字定义 ,内部包含constructor(构造函数)和方法
  • 继承 :用extends关键字实现类的继承super()调用父类构造函数
  • 静态方法 :用static关键字定义静态方法 ,只能通过类调用,不能通过实例调用;
  • 实例方法:类内部定义的方法,只能通过实例调用
javascript 复制代码
// 1. 定义基础类
class Person {
  // 构造函数:实例化时执行,初始化属性
  constructor(name, age) {
    this.name = name; // 实例属性
    this.age = age;
  }
  // 实例方法:通过实例调用
  sayHi() {
    console.log(`我叫${this.name},今年${this.age}岁`);
  }
  // 静态方法:通过类调用,this指向类本身
  static createPerson(name, age) {
    return new Person(name, age); // 工厂方法,创建Person实例
  }
}
 
// 实例化类
const person1 = new Person("张三", 25);
person1.sayHi(); // 我叫张三,今年25岁(调用实例方法)
 
// 调用静态方法
const person2 = Person.createPerson("李四", 30);
person2.sayHi(); // 我叫李四,今年30岁
 
// 2. 继承:定义子类Student继承自Person
class Student extends Person {
  // 子类构造函数:必须调用super()
  constructor(name, age, studentId) {
    super(name, age); // 调用父类构造函数,初始化name和age
    this.studentId = studentId; // 子类新增属性
  }
  // 子类重写父类方法
  sayHi() {
    console.log(`我是学生${this.name},学号${this.studentId}`);
  }
  // 子类新增方法
  study() {
    console.log(`${this.name}正在学习`);
  }
}
 
// 实例化子类
const student1 = new Student("王五", 20, "2024001");
student1.sayHi(); // 我是学生王五,学号2024001(重写后的方法)
student1.study(); // 王五正在学习(子类新增方法)
console.log(student1 instanceof Student); // true
console.log(student1 instanceof Person); // true(子类实例也是父类实例)

8. 模块化:import /export

核心概念:

  • 模块(Module):一个 JS 文件就是一个模块,内部变量 / 函数默认对外不可见;
  • 导出(Export) :将模块内的变量 / 函数暴露给外部,分为「命名导出」和「默认导出」;
  • 导入(Import) :在其他模块中引入已导出的变量 / 函数
javascript 复制代码
// 方式1:声明时直接导出
export const PI = 3.14159;
export function add(a, b) {
  return a + b;
}
// 导出时重命名:用"原名称 as 新名称"
export { add as addFn,funA,... };
// 默认导出
export default greet = (name) => `Hello, ${name}`;

//导入其他模块
import { PI, add, funA as  testFn } from './moduleA.js';
//导入所有
import * as moduleA from './moduleA.js';
console.log(moduleA.PI); 

9. 新数据结构:Set / Map

总结: 都是es6新增的数据类型,Set类似于数组 ,存值(且无重复,自动去重),可以用for...of遍历 ,常用方法add、delete、hasMap类似对象 ,它的键可以是任意类型可以遍历forEachfor...ofkeys(所有键名)、values ( 所有键值)等),常用方法get、set、delete、has

  • Set:无重复值的集合
    • 存储唯一值:自动去重,不允许重复元素
    • 可迭代:支持for...of遍历,可转换为数组
    • 常用方法add()(添加)、delete()(删除)、has()(判断是否存在)、size(获取长度)、clear()(清空)
  • Map:键值对集合(键可任意类型)
    • 键可以是任意类型:如 Number、String、Object、Symbol 等(对象的键只能是字符串 / Symbol);
    • 可迭代 :按插入顺序排列,遍历:forEachfor...ofkeys()(所有键名)、values() ( 所有键值)、entries()(所有键值对);
    • 常用方法set(key, value)(添加键值对)、get(key)(获取值)、delete(key)(删除)、has(key)(判断)、size(长度)、clear()(清空)

9.1.Map和Object的区别

总结: 从5个方面来说,

1、键的数据类型 :map是任意值 ;object必须是string或Symbol

2、键值对顺序 :map有序;object无序

3、方法 :map有增删改方法;object没有

4、遍历 :map可以遍历;object不行

5、性能:map在增删改场景下更好

区别:

  • 键的数据类型: Map的键可以是任意值,而Object的键必须是一个String或是Symbol。
  • 键值对的顺序: Map中的key是有序的,而Object的键是无序的。
  • 功能和方法: Map提供 set(), get(), has(), delete(), size 等方法,而Object没有内置的方法支持快速增删查改,需手动实现或用工具。
  • 迭代: Map可以直接被迭代,而Object不可以直接被迭代。
  • 性能: Map在频繁增删键值对的场景下表现更好,而Object的效率比较差。

9.2. weakSet和Set区别

  • WeakSet中只能存放对象类型
  • WeakSet对元素的引用是弱引用,若没有对某个对象进行引用,那么垃圾回收机制可以对该对象进行回收;
  • 常见方法: add、delete、has
  • WeakSet不能遍历

9.3.weakMap和Map区别

  • WeakMap的key只能使用对象
  • WeakMap的key对 对象的引用是弱引用,如果没有引用这个对象,那么垃圾回收机制可以回收该对象
  • 常见方法: set、get、delete、has
  • WeakSet不能遍历

10. 数组、字符串方法

  • 字符串方法总结(不修改原字符串)
    • ES5 是基础,重点掌握:indexOf、slice(截取)、split(切割)、trim、replace、toUpperCase/toLowerCase
    • ES6+ 是升级,优先使用:includes(替代 indexOf)、startsWith/endsWith、模板字符串
  • 数组方法总结
    • 修改原数组的方法: push(后加)、pop(后删)、unshift(前加)、shift(前删)、splice(增删改)、reverse(翻转)、sort(排序)、fill(填充)
    • 不改原数组的方法: includes、indexOf、lastIndexOf、slice(截取)、concat(拼接)
    • 遍历: forEach、map、filter、every、some、find、findIndex、reduce(累积)
    • 创建数组/类数组转数组: Array.of (创建)、Array.from(转)

10.1. 字符串

  • ES5 原生方法(所有浏览器兼容)‌
    • 字符 / 索引 查找类(获取字符 / 查找位置)
      • charAt(下标):获取指定下标的单个字符
      • charCodeAt(下标):获取指定下标 的单个字符的 Unicode 编码值
      • indexOf(指定字串, 起始下标):查找指定的子串 / 字符 ,返回首次出现的索引值
      • lastIndexOf(指定字串):末尾向前查找指定的子串 / 字符,返回最后一次出现的索引值
    • 字符串 截取 / 切割类(提取指定片段)
      • slice(起始下标, 截止下标)截取字符串中指定区间的内容,返回新字符串
      • substring(起始下标, 截止下标):同上
    • 大小写 转换类
      • toUpperCase() / toLowerCase():全部转为大写/小写,返回新字符串
    • 内容 替换 / 分割/拼接类
      • split(分隔符, 限制返回的长度):按照指定分隔符切割 ,转为一个数组返回
      • replace(替换前字串, 替换后字串):第一个匹配到 的 替换前字串 替换成 替换后字串,返回新字符串
      • concat(str1, str2, ...):拼接 字符串,返回拼接后的新字符串
      • trim():去除首尾的所有空白符(空格、换行符、制表符)(注:部分早期浏览器如 IE8 不支持,但通常视为 ES5 基础方法)‌
  • ES6 及之后新增方法‌
    • 包含判断类
      • includes(查找字串, 起始下标) :是否包含指定的子串 / 字符,返回 true / false
      • startsWith(查找字串, 起始下标):是否以指定的子串开头
      • endsWith(查找字串, 检测的长度):是否以指定的子串结尾
    • 重复拼接类
      • repeat(数字):将原字符串 重复拼接 n 次,返回拼接后的新字符串
    • 补全填充类(字符串补位,实用)
      • padStart(补全后长度, 填充字串):从字符串的开头 填充指定内容,直到字符串的长度达到设置的补全后长度,返回补全后的新字符串
      • padEnd(补全后长度, 填充字串):从字符串的末尾 填充指定内容,直到字符串的长度达到 设置的补全后长度,返回补全后的新字符串
    • 模板字符串(反引号 ${var}

10.2. 数组

  • ES5 原生方法(不会修改原数组)‌
    • slice(起始下标, 截止下标):数组截取(切片)
    • concat(arr1, arr2, ...):数组 合并
    • join(分隔符):数组转字符串
    • indexOf(查找元素,起始下标):查找元素索引(前--->后查找)
    • lastIndexOf(查找元素,起始下标):查找元素索引(后--->前查找)
    • includes (查找元素):判断数组是否包含指定元素
    • arr.flat(深度):数组扁平化,将「多维数组」转换为「一维数组」。arr.flat(1/Infinity)扁平化1层/所有层
    • flatMap (callback):先 map ,再 flat (1) 扁平化,相当于 arr.map().flat(1)
    • 数组遍历
      • forEach(callback):数组遍历。(无返回值)arr.forEach((item, index, arr) => { 执行逻辑 })
      • find(callback)/findIndex(callback) :→ 遍历数组,返回第一个满足条件的元素 / 索引
      • map(callback):数组(格式化、数据转换 ),对每个元素做处理 / 转换。(返回新数组)arr.map((item, index, arr) => { return 处理后的元素 })
      • filter(callback):数组过滤,根据条件筛选 出符合要求。(返回新数组)arr.filter((item, index, arr) => { return 筛选条件 })
      • some(callback):数组是否有元素 满足条件。arr.some((item, index, arr) => { return 判断条件 })
      • every(callback):数组所有元素是否 满足条件arr.every((item, index, arr) => { return 判断条件 })
      • reduce(callback, 初始值):数组累积(用于数组求和、求平均值、去重、扁平化、分组统计),每个元素的处理结果累积到一个变量中,返回这个累积值。`arr.reduce((prev, item) => prev + item, 0)
  • ES5 原生方法(会修改原数组)‌
    • push(...元素):末尾追加元素
    • pop():末尾删除元素(只删除一次最后一个元素)
    • unshift(...items):开头追加元素
    • shift():开头删除元素
    • sort(compareFunction):数组排序。(不传参:字符串排序;传参a-b升序、b-a降序)sort((a,b) => a - b)
    • reverse():数组反转
    • splice(起始下标, 删除的个数0, ...添加 / 替换的元素)‌:万能增删改。 splice(2,0,3,4)增34splice(2,1,99)替换下标为2的元素
    • fill(填充的值, 起始下标, 截止下标):数组填充。fill(0)全部填0;fill(99,1,3)下标1到下标3填99
  • ES6 及之后新增方法‌
    • Array.from(伪数组) : 伪数组转真数组
    • Array.of(...items/数字) :传单个数字:数组长度;传多个值:数组的参数。Array.of(3)长度为3的数组;Array.of(2,3)数组为[2,3]
javascript 复制代码
//reduce
// 场景1:求和(最基础)
const sum = arr.reduce((prev, item) => prev + item, 0)
// 场景2:求数组中最大值
const max = arr.reduce((prev, item) => prev > item ? prev : item, 0)
// 场景3:数组去重(进阶)
const uniqueArr = arr.reduce((prev, item) => {
  if(!prev.includes(item)) prev.push(item)
  return prev
}, [])

11. Symbol

总结: 主要特点是独一无二,适合用来做对象的唯一属性值,避免属性名冲突;也常用来定义一些不希望被遍历到的私有属性;或者是定义常量集合。

通过 Symbol() 函数创建,调用都会生成一个完全唯一的值 ,即使传入相同的描述文本,生成的 Symbol 也不相等

使用 Symbol.for() 创建全局可复用的 Symbol

使用场景

  • 作为对象的唯一属性键,避免属性名冲突
  • 定义对象的 "私有" 属性(模拟私有性): Symbol 键名不会出现在遍历 中,只能通过**Object.getOwnPropertySymbols() 访问**,起到隐藏属性的作用
  • 定义常量集合,避免魔法字符串:
javascript 复制代码
console.log(Symbol('foo') !== Symbol('foo')); // true

//对象的唯一属性键
const user = {};
const nameA = Symbol('name');
const nameB = Symbol('name');
user[nameA] = 'A';
user[nameB] = 'B';
console.log(user[nameA] !== user[nameB]) //true

//对象的 "私有" 属性
const secretKey = Symbol('secret');
const obj = {
  public: '公开信息',
  [secretKey]: '敏感信息' // 模拟私有属性
};

//常量集合
const STATUS = {
  PENDING: Symbol('pending'),
  SUCCESS: Symbol('success'),
  ERROR: Symbol('error')
};

12、for...in(es5)、for...of(es6)

总结

  • for...in 循环只遍历对象,获取键
  • for...of 循环可以用来遍历数组、 类数组对象, 字符串、 Set、 Map 以及 Generator 对象,获取值

详解
for...of概念 :允许遍历一个含有 迭代器 接口的数据结构 。(数组、对象等)并且返回各项的值
for...in 和 for...of 的区别

  • for...of 遍历获取 的是对象的键值 for (let key in obj) for...in 获取 的是对象的键名for (let val in arr)
  • for... in遍历对象整个原型链性能非常差 不推荐使用; 而for ... of 只遍历 当前对象 不会遍历 原型链
  • 遍历数组for...in 会返回数组中所有可枚举的属性 (包括原型链上 可枚举的属性); for...of返回数组的下标对应的属性值;

13. Proxy 可以实现什么功能?

总结: 意思是代理。用来拦截对象的操作,使用方法get、set、has等自定义逻辑处理

定义: Proxy 是 ES6 引入的一个强大功能,意思是"代理"。它可以用来拦截对象的操作 ,并在操作时进行自定义的逻辑处理new Proxy(target, handler)

常用的拦截操作(handler )

  • get ------ 拦截对象属性的读取
  • set ------ 拦截对象属性的 赋值
  • has ------ 拦截 in 操作符
  • deleteProperty ------ 拦截 delete 操作
  • apply ------ 拦截函数调用
  • construct ------ 拦截 new 操作
javascript 复制代码
const user = {name: "Tom",age: 18};
const proxy = new Proxy(user, {
	  get(target, prop) {
	    if (prop === "age") {
	      return target[prop] + " 岁"; 
	    }
	    return target[prop];
	  },
	  set(target, prop, value) {
	    if (prop === "age") {
	      if (typeof value !== "number") {
	        throw new TypeError("年龄必须是数字!");
	      }
	    }
	    target[prop] = value;
	    return true; // 必须返回 true 表示赋值成功
	  },
	  has(target, prop) {
	    if (prop === "password") {
	      return false; // 隐藏 password 属性
	    }
	    return prop in target;
	  },
	  deleteProperty(target, prop) {
	    if (prop === "name") {
	      console.log("name 属性不能被删除!");
	      return false;
	    }
	    return true;
	  }
 })
console.log("password" in proxy); // false
javascript 复制代码
//函数
function sum(a, b) {
  return a + b;
}
const proxy = new Proxy(sum, {
  apply(target, thisArg, args) {
    console.log("拦截函数调用,参数是:", args);
    return target(...args) * 2; // 返回值改为原函数结果的两倍
  }
});
console.log(proxy(1, 2)); // 6

// new
class Person {
  constructor(name) {
    this.name = name;
  }
}
const ProxyPerson = new Proxy(Person, {
  construct(target, args) {
    console.log("正在创建实例,参数:", args);
    return new target(...args);
  }
});
const p = new ProxyPerson("Tom"); // 正在创建实例,参数: [ 'Tom' ]

14. 如何提取高度嵌套的对象里的指定属性?

  • 已知结构:使用解构赋值
  • 结构不确定:使用可选链操作符?.
javascript 复制代码
const school = {
  classes: {
    stu: {
      name: 'Bob',
      age: 24
    }
  }
};

// 一行解构提取深层属性
const { classes: { stu: { name } } } = school;
console.log(name); // 'Bob'

//可选链操作符
const phone = school?.classes?.stu?.phone ?? '未提供';

三、异步编程

1. 什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?

概念: 回调函数(Callback)是指将一个函数作为参数传递给另一个函数 ,并在特定时刻执行它 。在 JavaScript 中,回调函数常用于处理异步操作,如读取文件、请求数据、 定时器 等。

javascript 复制代码
function fetchData(callback) {
  setTimeout(() => {
    callback("Data loaded");
  }, 1000);
}
fetchData((result) => {
  console.log(result); // 输出:Data loaded
});

回调地狱产生的原因: 当需要执行多个异步操作时,每个操作都依赖前一个操作的结果,这时就会出现"回调嵌套"的情况。导致代码层级过深,形成"金字塔"结构,称为回调地狱

javascript 复制代码
doTask1((result1) => {
  doTask2(result1, (result2) => {
    doTask3(result2, (result3) => {
      console.log("All tasks completed:", result3);
    });
  });
});

缺点:

  • 可读性差:嵌套层级过深,代码逻辑混乱。
  • 维护困难:修改或调试时容易出错。
  • 错误处理复杂:多层嵌套导致异常捕获困难。

解决方案:(异步编程的实现方式)

  • Promise
  • async/await
  • Generator 函数

2、Promise(解决回调地狱)

总结: promise解决了回调地狱问题,使用.then链条式调用,避免嵌套。有三个状态pending、fulfilled、rejected;Promise对象方法.all(全成功才返回)、.race(跑的快的决定结果);实例方法.then(成功)、.catch(失败)、.finally(无论成功还是失败)

优势:

  • 链式调用 :通过 .then() 避免嵌套。
  • 统一错误处理 :通过 .catch() 捕获异常。

核心概念

  • 三个状态状态的改变 是通过在异步操作结束后调用 resolve() 和 reject() 函数来实现的)
    • pending初始状态,既未成功也未失败;
    • fulfilled:操作成功,状态不可逆;
    • rejected:操作失败,状态不可逆。
  • Promise对象-静态方法
    • ‌Promise.resolve()‌:将一个普通的值 转换 成 Promise 类型的数据。
      • 非 Promise 对象,将返回一个 Promise 对象,状态为 fulfilled,结果为参数值
      • Promise 对象,将返回的 Promise 对象的状态和参数,与参数相同
    • Promise.reject()‌:始终返回一个失败的 Promise 对象,并且不受参数的影响。‌‌
    • Promise.all([promise1, promise2]):同时执行多个Promise,全部成功才返回,有一个失败则立即失败
    • Promise.race([promise1, promise2]):同时执行多个Promise,第一个改变状态的Promise决定结果
  • 实例方法
    • then:状态变为fulfilled时执行,返回新的 Promise,支持链式调用(resolve())
    • catch:状态变为rejected时执行,捕获错误(reject())
    • finally:无论成功或失败,都会执行

写法结构

javascript 复制代码
let p1 = Promise.resolve(123);
let p1 = Promise.reject(456);
//等同于
const promise = new Promise(function(resolve, reject) {
  if (/* 异步操作成功 */){
    resolve(123);
  } else {
    reject(456);
  }
});

//Promise.resolve方法 参数及结果
let p1 = Promise.resolve(123);
console.log(p1); // Promise {<fulfilled>: 123}
let p2 = Promise.resolve(new Promise((resolve, reject) => { resolve(456); }));
console.log(p2); // Promise {<fulfilled>: 456}
let p3 = Promise.resolve(new Promise((resolve, reject) => { reject("error"); }));
console.log(p3); // Promise {<rejected>: "error"}

详细使用

javascript 复制代码
// 1. 创建Promise
function fetchData() {
  return new Promise((resolve, reject) => {
    // 模拟异步操作(如AJAX请求)
    setTimeout(() => {
      const success = true; // 模拟成功/失败
      if (success) {
        const data = { name: "张三", age: 25 };
        resolve(data); // 成功:调用resolve,状态变为fulfilled
      } else {
        const error = new Error("数据获取失败");
        reject(error); // 失败:调用reject,状态变为rejected
      }
    }, 1000);
  });
}
 
// 2. 使用Promise:链式调用
fetchData()
  .then((data) => {
    console.log("获取数据成功:", data);
    // 返回新的Promise,继续链式调用
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(data.name); // 将name传递给下一个then
      }, 500);
    });
  })
  .then((name) => {
    console.log("处理后的数据:", name); // 张三
  })
  .catch((error) => {
    console.log("错误:", error.message); // 捕获所有前面的错误
  })
  .finally(() => {
    console.log("无论成功或失败,都会执行"); // 可选,用于清理操作(如关闭加载动画)
  });
 
// 3. Promise常用静态方法
// 3.1 Promise.all():同时执行多个Promise,全部成功才返回,有一个失败则立即失败
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
Promise.all([promise1, promise2])
  .then((values) => {
    console.log(values); // [1, 2](所有成功,返回结果数组)
  })
  .catch((error) => {
    console.log(error);
  });
 
// 3.2 Promise.race():同时执行多个Promise,第一个改变状态的Promise决定结果
const promise3 = new Promise((resolve) => setTimeout(resolve, 100, "快的Promise"));
const promise4 = new Promise((resolve) => setTimeout(resolve, 200, "慢的Promise"));
Promise.race([promise3, promise4])
  .then((value) => {
    console.log(value); // "快的Promise"(第一个成功的结果)
  });

3.async/await(es8)

总结: 用同步的方式写异步代码,
async声明函数是异步 的,返回值是Promise对象return的值 会被封装成Promise对象 ,根据值的类型自动改变状态 ;没有return值,返回undefined值的Promise对象)
await 是用来等待后面的表达式返回结果 ,若后面是Promise对象 ,会阻塞代码中断执行 ,若不想中断用try...catch包裹多个await Promise 捕获异常

概念: 是一种基于 Promise 实现的异步编程方式async/await 也是一种语法糖
优势: async/await 实现了用同步方式写异步代码 (promise是链式调用形式写异步代码)
缺点: 性能问题,await会阻塞代码

  • async : 用来声明一个function是异步的,返回Promise对象
    • 使用 return 语句返回的值 会被自动封装成 Promise 对象 , 该对象的状态 会根据 return 语句返回的值 类 型自动变为 resolved 或 rejected
    • 没有return ,则该函数返回一个 undefined 值的 Promise 对象
  • await在async 函数内部,它会暂停 async 函数的执行,等待 Promise 对象的状态改变,然后再继续执行 async 函数。
    • await不仅仅用于等Promise对象,可以等任意表达式的结果
    • await后面的 Promise 对象如果变为 reject 状态 ,则 reject 的参数 会被 catch 方法的回调函数接收到
    • 任何一个 await 语句后面的 Promise 对象变为 reject 状态 ,那么整个 async 函数都会中断执行 。(不想中断:try...catch

async代码写法

javascript 复制代码
// 成功
async function foo() {
	return 'Hello World!'
}
foo().then(value => console.log(value)) // 'Hello World!'

//失败
async function bar() {
	throw new Error('Something went wrong!')
}
bar().catch(error => console.error(error))  // 'Error: Something went wrong!'

//async 函数中没有 return 语句,则该函数返回一个 undefined 值的 Promise 对象
async function foo() {
	console.log('Hello World!')
	// 等同于 return Promise.resolve(undefined)
}
foo().then(value => console.log(value)) // 'Hello World!' // undefined

await代码写法

javascript 复制代码
async function foo() {
	await Promise.resolve('xx')
}
async function f() {
	await Promise.reject('出错了')
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))

// 有一个await就会中断函数
async function f() {
	await Promise.reject('出错了')
	await Promise.resolve('hello world') // 不会执行
}
// 不想中断
async function f() {
	try {
		await Promise.reject('出错了')
	} catch(e) {
	}
	return await Promise.resolve('hello world')
}
f().then(v => console.log(v)) // hello world

//多个await
async function main() {
	try {
		const val1 = await firstStep()
		const val2 = await secondStep(val1)
		const val3 = await thirdStep(val1, val2)
		console.log('Final: ', val3)
	} catch (err) {
		console.error(err)
	}
}

4. async/await对比Promise的区别

总结: 分为4个方面

1、语法差异: Promise 基于回调函数的异步处理方式 ;async/await是一种的语法糖,基于generator和promise (async声明异步,await等待结果)

2、可读性: Promise 链式调用 书写异步;async/await是同步 书写异步

3、异步操作的顺序: Promise 处理多个异步,要嵌套then的方式 ,或者all、race;async/await 同步代码

4、错误捕获和处理: Promise 用catch方法 ;async/await用try...catch

区别:

  • 语法差异
    • promise 是基于回调函数的异步处理方式,使用 then 和 catch 方法来处理异步操作的结果和错误。
    • async/await 是一种基于 生成器 函数(Generator)和 promise 的语法糖,通过在函数前面加上 async 关键字,将函数转换为返回 promise 对象的异步函数,并使用 await 关键字等待异步操作的结果。
  • 可读性
    • promise 以链式调用的形式书写异步代码。
    • async/await 以同步形式书写异步代码 。更加直观和易读,代码结构更加清晰。
  • 错误处理
    • promise 中,通常使用 catch 方法来捕获和处理错误
    • async/await 中,通常使用 try...catch 语句来捕获和处理错误
  • 异步操作的顺序
    • promise 中,如果有多个异步操作需要按照特定的顺序执行 ,我们需要通过嵌套的 then 方法或使用 Promise.all、Promise.race 等组合方法来实现。
    • async/await 中,可以使用同步的代码风格来编写异步操作的顺序,使代码更加简洁和可读。
  • 错误堆栈
    • promise 中,当发生错误时,错误 信息 中会包含详细的堆栈信息,可以追踪到错误发生的位置
    • async/await 中,由于使用了 try...catch 语句捕获错误堆栈信息可能会被截断,不够详细

5、generator. ??

6. 并发与并行的区别?

  • 并发宏观 上同一时间段内处理多个任务 ,这些任务交替执行 (强调任务在‌时间段‌上的重叠)。
  • 并行微观 上同一时刻执行多个任务 ,这些任务真正同时进行‌ (强调任务在‌时刻‌上的同步)。

7. setTimeout、setInterval、requestAnimationFrame 各有什么特点?

  • setTimeout:指定延迟后‌执行一次‌回调函数。适用场景‌:一次性延迟任务(如倒计时、防抖、延迟加载)
  • setInterval每隔固定时间间隔重复执行‌回调函数。适用场景‌:周期性轮询、定时更新(需手动取消以避免内存泄漏)
  • requestAnimationFrame专为动画优化‌,在浏览器下一次重绘前执行回调。适用场景‌:所有视觉动画(Canvas、CSS 变换、游戏、滚动效果)
javascript 复制代码
// setTimeout
setTimeout(() => console.log("1秒后执行"), 1000);
//setInterval
const id = setInterval(() => console.log("每秒执行"), 1000);
clearInterval(id); // 手动停止
//requestAnimationFrame
function animate(timestamp) {
  // 动画逻辑
  requestAnimationFrame(animate); // 递归调用保持循环
}
requestAnimationFrame(animate);

8、事件循环 EventLoop ?

四、JavaScript基础

1. new操作符的实现原理

  • 创建新对象: {}
  • 设置原型链,将对象的原型设置为constructor构造函数的 prototype 对象:
  • 让函数的 this 指向这个对象,执行构造函数的代码: 为这个新对象添加属性
  • 返回新对象
javascript 复制代码
function createObject(constructor, ...args) {
    const obj = {}; // 步骤1: 创建新对象
    obj.__proto__ = constructor.prototype; // 步骤2: 设置原型链
    const result = constructor.apply(obj, args); // 步骤3: 绑定this并执行构造函数
    return (typeof result === 'object' && result !== null) ? result : obj; // 步骤5: 返回结果
}

2. js有哪些内置对象

总结: 常用的有对象、数组、函数、字符串、Date、JSON、正则、Promise等;

  • Object‌:所有对象的基类,提供如 keys()、values()、assign() 等方法。
  • Function‌:函数构造器,支持 .call()、.apply()、.bind() 等方法。
  • Array‌:用于处理有序集合,提供 push()、pop()、map()、filter() 等丰富方法。
  • String‌:字符串操作,如 substring()、split()、trim()、includes()。
  • Number‌:数值处理,包括 toFixed()、toPrecision()、isInteger()、isNaN()。
  • JSON‌:用于 JSON 序列化与反序列化,如 stringify() 和 parse()。
  • Date‌:处理日期和时间,支持创建、格式化、时间戳计算等。
  • Map‌:键值对集合,键可为任意类型。
  • Set‌:存储唯一值的集合。
  • Error‌ 及其子类型:如 TypeError、ReferenceError、SyntaxError、RangeError 等,用于异常处理。
  • RegExp‌:用于模式匹配和文本搜索,支持字面量 /.../ 或构造函数方式。
  • Promise‌:异步操作处理(ES6 引入)。
  • Proxy‌ 和 ‌Reflect‌:用于元编程,拦截对象操作(ES6 引入)。

3. 对JSON的理解

JSON的数据结构 主要由两种形式组成:对象(Object)和数组(Array)对象 是一个无序的键值对集合 ,用花括号{}表示;数组 是一个有序的值的集合 ,用方括号[]表示。
两个原生方法实现:

  • JSON.stringify():将 js 对象或值转换为 JSON 字符串。
  • JSON.parse():将 JSON 字符串解析为 js 对象或值。
javascript 复制代码
const obj = { name: "Alice", age: 25 };
const jsonString = JSON.stringify(obj);
console.log(jsonString); // '{"name":"Alice","age":25}'
const obj = JSON.parse(jsonString);
console.log(obj.name); // "Alice"

4. js脚本<script>延迟加载的方式有哪些?

  • defer 属性: 脚本在‌文档解析完成后 、DOMContentLoaded 事件触发前‌按顺序执行<script defer src="script.js">(按顺序执行‌且操作 DOM )
  • async 属性: 脚本‌异步下载,下载完成后立即执行‌,可能中断 HTML 解析<script async src="script.js">(完全独立‌且需尽快执行)
  • 动态创建 DOM 方式: 通过 js 动态创建 script 元素并插入 DOM,实现‌按需加载‌。
  • 使用 setTimeout 延迟方法: 设置一个定时器来延迟加载js脚本文件
  • <script> 放在 HTML 底部‌:js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

5. js 类数组对象的定义?

类数组: 具有一个 length 属性和若干索引属性任意对象 。这样的对象可以用数字索引来访问其元素 ,但它们不是真正的数组。

javascript 复制代码
const arrayLike = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3
};

5.1.为什么函数的 arguments 参数是类数组而不是数组?

历史原因‌ :在早期js版本 中,arguments 被设计为类数组对象 ,主要是为了性能考虑减少内存占用和提高访问速度

兼容性‌早期的浏览器和js引擎不支持完整的数组方法 ,因此提供一个类似数组的对象可以保证基本的兼容性

5.2.如何转化为数组?

  • Array.prototype.slice
  • Array.from
  • 使用扩展运算符:仅适用于可迭代对象
  • Array.prototype.map:如果需要映射每个元素
javascript 复制代码
const trueArray = Array.prototype.slice.call(arrayLike);
const trueArray = Array.from(arrayLike);
const trueArray = [...arrayLike];
const trueArray = Array.prototype.map.call(arrayLike, (item, index) => item);

5.3.如何遍历类数组?

转成数组后再遍历

6. Unicode、UTF-8、UTF-16、UTF-32的区别?

Unicode 是字符集,定义了"有哪些字符";UTF-8、UTF-16、UTF-32 是编码方式,定义了"如何用字节存储这些字符"‌

假设:Unicode 是字典,UTF-8/16/32 是三种不同的"抄写方式"‌ ------ 你选哪种,取决于你用的是纸笔(UTF-8)、钢笔(UTF-16)还是金笔(UTF-32)。

7. 常见的位运算符有哪些?其计算规则是什么?

常见的位运算符共有 ‌6 种‌:按位与&、按位或|(表格写不出来)、按位异或^、按位取反~、左移<<和右移>>

运算符 符号 计算规则 示例(8位二进制) 结果(十进制)
按位与 & 两数对应位均为1时,结果为1,否则为0 12 & 10 → 1100 & 1010 8
按位或 两数对应位任一为1,结果为1 `12
按位异或 ^ 两数对应位不同时为1,相同时为0 12 ^ 10 → 1100 ^ 1010 6
按位取反 ~ 所有位取反(0变1,1变0) ~12 → ~00001100 -13(补码表示)
左移 << 所有位左移n位,右侧补0 12 << 2 → 1100 << 2 48
右移 >> 所有位右移n位,左侧补符号位(有符号数) 12 >> 2 → 1100 >> 2 3

8. 什么是 DOM 和 BOM?

  • DOM(文档对象模型) :由W3C制定标准 ,它将网页文档解析为树形结构(节点树) ,使js能够动态访问和修改页面中的元素、内容、结构和样式。‌
    • 顶层对象: document(隶属于window对象)。‌‌
    • 主要功能: 获取、修改、添加或删除页面元素;事件
  • BOM(浏览器对象模型)‌:由浏览器厂商提供。它通过一系列对象(核心是window对象)让js能够控制浏览器窗口、导航、屏幕信息等与网页内容无关的功能。‌‌
    • 顶层对象: window
    • 主要对象与功能 ‌:
      • location:控制URL跳转与刷新。‌‌
      • history:操作浏览器历史记录。‌‌
      • navigator:获取浏览器信息。‌‌
      • screen:获取屏幕尺寸。‌‌
      • alert/confirm:弹出对话框

9. escape、encodeURI、encodeURIComponent 的区别

escape是对字符串(string)进行编码

encodeURI、encodeURIComponent 对URL编码

10. JavaScript为什么要进行变量提升,它导致了什么问题?

总结
变量提升 是JavaScript在代码执行之前对变量和函数声明进行预处理的机制 。它提高了性能和容错性

但也可能导致在变量声明之前使用变量时出现undefined 的情况。

为避免潜在问题,建议在代码中始终先声明变量再使用 ,并使用ES6的let和const来避免变量提升带来的一些隐患。

11. 什么是尾调用,使用尾调用有什么好处?

尾调用 是指一个函数最后一步操作调用另一个函数 。它可以减少内存消耗,提高性能,并且可以优化 编译器

12. use strict是什么意思 ? 使用它区别是什么?

"use strict" :指定代码在严格条件下执行;
目的:

  • 消除 Javascript 语法的不合理、不严谨之处,减少怪异行为;
  • 消除代码运行的不安全之处,保证代码运行的安全;
  • 提高编译器效率,增加运行速度;
  • 为未来新版本的 Javascript 做好铺垫

区别

  • 禁止使用 with 语句。
  • 禁止 this 关键字指向全局对象。
  • 对象不能有重名的属性

13. 如何判断一个对象是否属于某个类?

总结: instanceof运算符、通过constructor属性Object.prototype.toString()instanceof和constructor属性提供了更准确的判断 ,而toString()主要用于获取对象类型,不适用于自定义类。

14. 对AJAX的理解,实现一个AJAX请求

AJAX是一种用于在网页上进行异步数据交互 的技术。它允许网页通过后台与服务器 进行数据交换 ,而不需要刷新整个页面。
Ajax的原理 简单来说通过XmlHttpRequest对象向服务器发异步请求 ,从服务器获得数据 ,然后用JavaScript来操作DOM而更新页面

实现一个基本的AJAX请求可以分为以下几个步骤

  • 创建一个XMLHttpRequest对象 :使用new XMLHttpRequest()创建一个新的XMLHttpRequest对象,该对象用于发送HTTP请求
  • 设置请求参数和回调函数 :使用open()方法设置请求的类型(GET或POST)和URL 。如果有需要,还可以设置其他的请求参数,例如请求头信息等。然后,使用onreadystatechange属性指定一个回调函数 ,该函数将在请求状态发生变化时被调用
  • 发送请求 :使用send()方法发送请求 。对于GET 请求,可以将请求参数附加到URL 中;对于POST 请求,可以将请求参数作为send()方法的参数传递。
  • 处理响应 :在回调函数中,通过检查readyState和status属性来确定请求的状态readyState表示请求的当前状态 ,其中4表示请求已完成并且响应已就绪,status表示服务器返回的HTTP状态码 ,200表示请求成功。可以使用responseText或responseXML属性获取服务器返回的数据。

readyState表示请求的当前状态,包括0(未初始化)、1(已打开)、2(已发送)、3(接收中)和4(已完成)。

javascript 复制代码
// 创建XMLHttpRequest对象
var xhr = new XMLHttpRequest()
// 指定请求的方法、URL和是否为异步请求
xhr.open('POST', 'https://api.example.com/data', true)
// 设置接收到响应时的处理函数
xhr.onload = function () {
  if (xhr.status === 200) {
    // 在这里处理从服务器返回的数据
    var response = JSON.parse(xhr.responseText)
    console.log(response)
  } else {
    console.error('请求失败:' + xhr.status)
  }
}
// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/json')
// 发送请求到服务器
var data = {
  username: 'John',
  password: 'secret'
}
xhr.send(JSON.stringify(data))

15. ajax、axios、fetch的区别

  • Ajax: 是一种较为早期的技术,基于 XMLHttpRequest 对象发起 HTTP 请求,提供了很大的灵活性,但也带来了复杂性。
  • Fetch: 作为现代 API,它基于 Promise ,提供更简洁和易于理解的接口,使得处理异步请求 变得更加方便。不过在错误处理上有些不足。
  • Axios: 基于 Promise 的 HTTP 客户端,适用于 浏览器 和 Node.js。它封装了 XMLHttpRequest 和 fetch API,提供了简单的 API 进行网络请求。尤其适合需要处理大量 HTTP 请求的项目,但需要依赖库的额外引入。

五、原型与原型链 ??

41. 对原型、原型链的理解

总结:

  • 原型: 每个函数在创建时 都会自动生成一个 prototype 属性 ,它指向一个对象函数.prototype得到的这个对象 ),这个对象称为原型对象 。通过这个原型对象,所有由该函数构造出来的实例对象可以共享方法和属性
  • 原型链:

原型详解

javascript 复制代码
 var a=function (){}
 console.log(a.prototype) 

打印结果如下:从结果来看,函数.prototype得到的是一个对象,包含了constructor和_proto_。有constructor说明是一个构造函数。

构造函数创建实例时,把通用方法写到 prototype 上,让所有实例共享。

javascript 复制代码
function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function() {
  return 'Hi, I am ' + this.name;
};
const p1 = new Person('Tom');
const p2 = new Person('Jerry');
console.log(p1.sayHi === p2.sayHi); // true,共享方法

42. 原型修改、重写

43. 原型链指向

44. 原型链的终点是什么?如何打印出原型链的终点?

45. 如何获得对象非原型链上的属性?

六、执行上下文/作用域链/闭包

1. 对闭包的理解

闭包:当一个函数内部定义了另一个函数,并且内部函数引用了外部函数的变量时,就形成了闭包 。这些变量不会因为外部函数执行结束而被垃圾回收机制回收,而是被保留下来。

闭包的原理: "从内存角度 看,正常情况下函数执行完毕后,其执行上下文会被销毁,局部变量会被垃圾回收。闭包 会让被引用的变量保留在内存 中,因为内部函数持有对这些变量的引用。"

闭包的形成条件:

  • 必须有外层函数和内层函数的嵌套关系
  • 内层函数必须访问外层函数作用域中的变量
  • 内层函数被外部使用,也就是说内层函数必须以某种方式被保留(返回、赋值、传递等)

闭包的核心使用场景

  • 数据私有化(模拟私有变量) 外层函数实例拿不到它定义的变量counter.count 结果是 undefined
  • 函数工厂(柯里化)
  • 防抖与节流
  • 循环中的异步问题
javascript 复制代码
function createCounter() {
    let count = 0; // 这个变量本应在函数执行完后被销毁
    let inner =  function() {
        count++; // 但闭包让它"活"了下来
        return count;
    }
    inner()    //只是内部调用,没有返回inner,不形成闭包
    return inner     //内层函数被返回到外部,形成闭包
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

使用场景一、数据封装和私有变量

javascript 复制代码
function createCounter() {
    let count = 0; // 私有变量
    return {
        increment: function() { count++; },
        getCount: function() { return count; }
    };
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
console.log(counter.count); // undefined (无法直接访问)

使用场景二、函数工厂(柯里化)

javascript 复制代码
function makeAdder(x) {
    return function(y) {
        return x + y;
    };
}
const add5 = makeAdder(5);
console.log(add5(2));  // 7

使用场景三、循环中的异步问题

javascript 复制代码
// ❌ 错误示例
for (var i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i); // 输出 5 个 5
    }, 1000);
}
 
// ✅ 使用闭包解决
for (var i = 0; i < 5; i++) {
    (function(j) {
        setTimeout(() => {
            console.log(j); // 输出 0, 1, 2, 3, 4
        }, 1000);
    })(i);
}
//  ✅ 使用let解决
for (let i = 1; i <= 5; i++) {
    setTimeout(function() {
        console.log(i); // 1,2,3,4,5
    }, 1000);
}

1.1 .使用场景四、防抖与节流

javascript 复制代码
// 防抖
function debounce(fn, delay) {
    let timer = null; // 闭包保存 timer
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}
// 使用
const handleInput = debounce(() => {
    console.log("搜索请求");
}, 500);

2. 对作用域、作用域链的理解

3. 对执行上下文的理解

七、this/call/apply/bind

1. 对this对象的理解

2. call() 和 apply() 的区别?

3. 实现call、apply 及 bind 函数

连续多个 bind,最后this指向是什么?

八、面向对象

1. 对象创建的方式有哪些?

2. 对象继承的方式有哪些?

九、垃圾回收与内存泄漏

1. 浏览器的垃圾回收机制

2. 哪些情况会导致内存泄漏

相关推荐
于慨20 小时前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
石小石Orz20 小时前
油猴脚本实现生产环境加载本地qiankun子应用
前端·架构
从前慢丶20 小时前
前端交互规范(Web 端)
前端
@yanyu66620 小时前
07-引入element布局及spring boot完善后端
javascript·vue.js·spring boot
CHU72903521 小时前
便捷约玩,沉浸推理:线上剧本杀APP功能版块设计详解
前端·小程序
GISer_Jing21 小时前
Page-agent MCP结构
前端·人工智能
王霸天21 小时前
💥别再抄网上的Scale缩放代码了!50行源码教你写一个永不翻车的大屏适配
前端·vue.js·数据可视化
小领航21 小时前
用 Three.js + Vue 3 打造炫酷的 3D 行政地图可视化组件
前端·github
@大迁世界21 小时前
2026年React大洗牌:React Hooks 将迎来重大升级
前端·javascript·react.js·前端框架·ecmascript
PieroPc21 小时前
一个功能强大的 Web 端标签设计和打印工具,支持服务器端直接打印到局域网打印机。Fastapi + html
前端·html·fastapi