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

前端梳理体系从常问问题去完善-基础篇(html,css,js,ts)

前言

其实很多人都不知道怎么去梳理自己得知识体系,而且不太记得住,想要记住,好像是多刷题目,通过做题得这种方式,让自己进行记忆,所以我会总体得看一遍书,然后去刷一些问题,通过问题得方式形成记忆,抓住重点,看看自己是否记得住。同时,这也是常问得面试问题吧。本来之前就想整理得,只是入职新公司,要时间去适应公司。害,分几篇分享。

js

ES6+ 新语法

  1. ES6(2015)
    • 变量:let/const、块级作用域
    • 函数:箭头函数、默认参数、剩余 / 扩展参数
    • 字符串:模板字符串(```+ ${}
    • 数据:数组 / 对象解构、Map/Set
    • 面向对象:class 类与继承
    • 模块化:import/export
    • 异步:Promise
    • 其他:for...ofSymbol、生成器(function*
  2. ES7(2016)
    • 数组:includes()
    • 运算符:指数运算符(**
  3. ES2017(ES8)
    • 异步:async/await
    • 对象:Object.values()/entries()
    • 字符串:padStart()/padEnd()
  4. ES2018(ES9)
    • 对象:扩展运算符(... 用于对象)
    • 异步迭代:for await...of
    • 正则:命名捕获组、反向断言
  5. ES2019(ES10)
    • 数组:flat()/flatMap()(数组扁平化)
    • 字符串:trimStart()/trimEnd()
    • 对象:fromEntries()(将键值对转为对象)
  6. ES2020(ES11)
    • 数据类型:BigInt(大整数)
    • 运算符:可选链(?.)、空值合并(??
    • 模块化:动态 import()
    • 字符串:matchAll()
  7. ES2021(ES12)
    • 逻辑赋值:&&=/||=/??=
    • 数字:分隔符(1_000_000
    • 数组:at()(支持负索引)
  8. ES2022(ES13)
    • 类:私有属性(# 前缀)、静态类字段
    • 数组:findLast()/findLastIndex()
    • Top-level await(模块顶层使用 await
  9. 后续版本(2023+)
    • 新增 ArrayBuffer 扩展、Object 方法增强等小特性,以场景化优化为主。

整体趋势:ES6 奠定现代语法基础,后续版本逐年迭代,聚焦解决实际开发痛点(如异步简化、安全访问、代码可读性)。

?.与?? 的区别

在 JavaScript(及 TypeScript)中,?.(可选链运算符)和 ??(空值合并运算符)是两个不同用途的语法糖,核心区别在于它们解决的问题和使用场景不同。以下是详细对比:

1. ?.(可选链运算符):安全访问嵌套属性 / 方法

作用:用于安全地访问对象的嵌套属性、数组元素或调用方法,避免因中间值为 nullundefined 而抛出 Cannot read property 'x' of undefined 之类的错误。
逻辑:如果运算符左侧的值(对象 / 数组)为 nullundefined,则整个表达式直接返回 undefined,不再继续访问右侧的属性 / 方法。

示例

const user = {name: "Alice",address: { city: "Beijing" }
};// 正常访问(无错误)
console.log(user.address.city); // "Beijing"// 假设 user.address 可能不存在
const user2 = { name: "Bob" };
// 传统方式:需要手动判断,否则报错
console.log(user2.address && user2.address.city); // undefined(无错误)
// 可选链方式:更简洁
console.log(user2.address?.city); // undefined(无错误)// 访问数组元素
const arr = [1, 2, 3];
console.log(arr[0]); // 1
const arr2 = null;
console.log(arr2?.[0]); // undefined(无错误)// 调用方法(如果方法不存在,不会报错)
const utils = {format: (str) => str.toUpperCase()
};
console.log(utils.format?.("hello")); // "HELLO"
const utils2 = null;
console.log(utils2.format?.("hello")); // undefined(无错误)

2. ??(空值合并运算符):设置默认值

作用:当左侧操作数为 nullundefined 时,返回右侧的默认值;否则返回左侧操作数。
核心特点:仅对 nullundefined 生效,对其他 “假值”(如 0''false)不生效(这是它与 || 运算符的关键区别)。

示例

// 左侧为 null/undefined 时,返回右侧
const name = null ?? "Guest"; // "Guest"
const age = undefined ?? 18; // 18// 左侧为其他“假值”时,返回左侧(与 || 不同)
const score = 0 ?? 60; // 0(若用 || 会返回 60)
const emptyStr = "" ?? "default"; // ""(若用 || 会返回 "default")

核心区别总结

维度?.(可选链运算符)??(空值合并运算符)
用途安全访问嵌套属性 / 数组 / 方法,避免报错null/undefined 设置默认值
操作对象左侧是可能为 null/undefined 的值左侧是待判断的值,右侧是默认值
返回结果若左侧有效则返回属性值,否则 undefined若左侧是 null/undefined 则返回右侧,否则返回左侧
典型场景处理不确定存在的嵌套数据(如接口返回)为变量设置默认值(排除 0/'' 等有效假值)

常见组合使用

两者经常结合使用,例如先通过 ?. 安全访问属性,再通过 ?? 设置默认值

for in 与for of得区别

在 JavaScript 中,for...infor...of 是两种不同的循环语法,核心区别在于遍历目标、适用场景和遍历结果,具体如下:

1. 遍历目标不同

  • for...in:用于遍历对象的可枚举属性(包括自身属性和继承的属性),本质是遍历 “键名”。
    可枚举属性指的是那些 enumerable 标志为 true 的属性(如对象自身定义的属性、数组的索引等,默认情况下大部分原生属性不可枚举)。
  • for...of:用于遍历可迭代对象(Iterable Object)的元素值,本质是遍历 “值”。
    可迭代对象是指实现了 [Symbol.iterator] 接口的对象,包括:数组、字符串、MapSetarguments 对象、NodeList(DOM 节点集合)等。

2. 遍历结果不同

  • for...in 遍历的是 “键名”
    • 遍历对象时,得到的是对象的属性名(字符串类型);
    • 遍历数组时,得到的是数组的索引(字符串类型,而非数字);
    • 遍历字符串时,得到的是字符的索引(字符串类型)。
  • for...of 遍历的是 “值”
    • 遍历数组时,得到的是数组的元素值
    • 遍历字符串时,得到的是单个字符
    • 遍历 Map 时,得到的是 [key, value] 数组;
    • 遍历 Set 时,得到的是集合中的元素

3. 适用场景不同

  • for...in 适合遍历 “对象的属性”
    主要用于检查对象是否包含某个属性,或遍历对象的键名(需注意过滤继承属性)。
    不推荐用于遍历数组(因为可能遍历到非数字索引的属性,且索引是字符串类型)。
  • for...of 适合遍历 “可迭代对象的元素”
    主要用于获取数组、字符串、MapSet 等集合中的元素值,更符合 “遍历数据” 的直观需求。

4. 对继承属性的处理不同

  • for...in 会遍历继承的可枚举属性
    例如,若在 Object.prototype 上添加了自定义属性,for...in 会遍历到这些属性,可能导致意外结果。因此需要用 hasOwnProperty() 过滤自身属性。
  • for...of 只遍历自身元素
    不会涉及原型链上的属性,无需额外过滤。

示例对比

示例 1:遍历数组

const arr = [10, 20, 30];// for...in:遍历索引(字符串类型)
for (const key in arr) {console.log(key, typeof key); // 0 string, 1 string, 2 string
}// for...of:遍历元素值
for (const value of arr) {console.log(value); // 10, 20, 30
}

示例 2:遍历对象

const obj = { name: "foo", age: 18 };// for...in:遍历属性名(需注意过滤继承属性)
for (const key in obj) {// 过滤继承的属性(如 toString 等)if (obj.hasOwnProperty(key)) {console.log(key, obj[key]); // name foo, age 18}
}// for...of:不能直接遍历普通对象(普通对象不可迭代)
for (const value of obj) { console.log(value); // 报错:obj is not iterable
}

示例 3:遍历字符串

const str = "abc";// for...in:遍历索引(字符串类型)
for (const key in str) {console.log(key, str[key]); // 0 a, 1 b, 2 c
}// for...of:遍历字符
for (const char of str) {console.log(char); // a, b, c
}

示例 4:遍历 Map

const map = new Map();
map.set("name", "bar");
map.set("age", 20);// for...in:遍历 Map 的属性(非键值对,无意义)
for (const key in map) {console.log(key); // 输出 Map 的内置属性(如 size),而非键值对
}// for...of:遍历 [key, value] 数组
for (const [key, value] of map) {console.log(key, value); // name bar, age 20
}

总结对比表

特性for...infor...of
遍历目标对象的可枚举属性(键名)可迭代对象的元素(值)
遍历结果键名(字符串类型)元素值
适用对象所有对象(尤其是普通对象)可迭代对象(数组、字符串、Map 等)
继承属性处理会遍历继承的可枚举属性(需过滤)只遍历自身元素,不涉及原型链
典型用途检查对象属性、遍历对象键名获取集合元素值、遍历数据

简单说:for...in 是 “遍历键名的工具”,for...of 是 “遍历值的工具”。实际开发中,遍历对象属性用 for...in(记得过滤继承属性),遍历数组、字符串等集合的元素用 for...of

js基础数据类型

暂时性死锁
symbol使用场景
  1. 作为对象的唯一属性键,避免属性名冲突
  2. 定义对象的 “私有” 属性(模拟私有成员)
  3. 定义常量集合(避免魔法字符串)
  4. 扩展内置对象的方法
  5. 定义迭代器接口(Symbol.iterator

迭代器与生成器

https://www.yuque.com/ergz/web/qeeg7q18va9u2ykc#GRSje

对象、类与面向对象编程

https://www.yuque.com/ergz/web/ozpq9gmwga9tsyg5

期约与异步函数

https://www.yuque.com/ergz/web/cxzlaa1rolmsvbxk#vO4eg

函 数

https://www.yuque.com/ergz/web/vp2ulvbwu3tx5w0m

代理与反射

https://www.yuque.com/ergz/web/xpdllbmu1wil63qe

this全面讲解

https://www.yuque.com/ergz/web/gnncr1

js问题

如何获取url?a=‘11123’
// 1. 获取 URL 中的查询部分(即 "?a='11123'&b=456")
const queryString = window.location.search;// 2. 解析查询字符串
const params = new URLSearchParams(queryString);// 3. 获取参数 a 的值
const aValue = params.get('a');
proxy如何做数据得拦截

Proxy 通过陷阱函数实现对数据的拦截,核心步骤是:

  1. 创建代理对象(new Proxy(target, handler));
  2. handler 中定义需要拦截的操作(如 getset 等陷阱);
  3. 通过代理对象操作数据时,自动触发对应的陷阱函数,执行自定义逻辑。

这种机制广泛用于数据验证、日志记录、响应式系统(如 Vue 3 的响应式原理)等场景,相比 Object.defineProperty 更强大、更灵活。

js 的继承是怎么做的?

JavaScript 的继承机制与传统面向对象语言(如 Java)不同,它基于原型链(Prototype Chain) 实现,而非类的直接继承。随着语言发展,ES6 引入了 classextends 语法糖,简化了继承实现,但底层仍依赖原型链。以下是 JavaScript 中常见的继承方式及原理:

一、原型链继承(最基础的继承方式)

核心思想:通过让子类的原型对象(prototype)指向父类的实例,形成原型链,从而继承父类的属性和方法。

实现示例:

// 父类:动物
function Animal(name) {this.name = name; // 实例属性this.features = ['呼吸', '繁殖']; // 引用类型属性
}// 父类原型方法
Animal.prototype.eat = function() {console.log(`${this.name} 在吃东西`);
};// 子类:狗
function Dog() {}// 关键:让子类原型指向父类实例,形成原型链
Dog.prototype = new Animal(); 
// 修复子类构造函数指向(否则 Dog 实例的 constructor 会指向 Animal)
Dog.prototype.constructor = Dog;// 子类实例
const dog = new Dog();
dog.name = '旺财'; 
dog.eat(); // 输出:"旺财 在吃东西"(继承父类原型方法)
console.log(dog.features); // 输出:['呼吸', '繁殖'](继承父类实例属性)

缺点:

  1. 子类实例会共享父类的引用类型属性(如 features),一个实例修改会影响其他实例。
  2. 无法在创建子类实例时向父类构造函数传递参数(如 Animalname 参数)。

二、构造函数继承(解决原型链的传参和共享问题)

核心思想:在子类构造函数中通过 call/apply 调用父类构造函数,强制绑定 this,从而继承父类的实例属性。

实现示例:

// 父类
function Animal(name) {this.name = name;this.features = ['呼吸', '繁殖'];
}Animal.prototype.eat = function() {console.log(`${this.name} 在吃东西`);
};// 子类
function Dog(name) {// 关键:调用父类构造函数,传递参数Animal.call(this, name); 
}// 子类实例
const dog1 = new Dog('旺财');
const dog2 = new Dog('小白');dog1.features.push('汪汪叫'); 
console.log(dog1.features); // ['呼吸', '繁殖', '汪汪叫']
console.log(dog2.features); // ['呼吸', '繁殖'](不共享,解决了引用类型共享问题)dog1.eat(); // 报错!无法继承父类原型方法(构造函数继承只继承实例属性)

缺点:

  • 只能继承父类的实例属性和方法,无法继承父类原型上的方法(如 eat),导致方法无法复用(每个实例都需单独定义)。

三、组合继承(原型链 + 构造函数,主流方案)

核心思想:结合原型链继承(继承原型方法)和构造函数继承(继承实例属性),取长补短。

实现示例:

// 父类
function Animal(name) {this.name = name;this.features = ['呼吸', '繁殖'];
}Animal.prototype.eat = function() {console.log(`${this.name} 在吃东西`);
};// 子类
function Dog(name) {// 1. 构造函数继承:继承实例属性,传递参数Animal.call(this, name); 
}// 2. 原型链继承:继承原型方法
Dog.prototype = new Animal(); 
Dog.prototype.constructor = Dog;// 子类可添加自己的原型方法
Dog.prototype.bark = function() {console.log(`${this.name} 在汪汪叫`);
};// 测试
const dog = new Dog('旺财');
dog.eat(); // "旺财 在吃东西"(继承原型方法)
dog.bark(); // "旺财 在汪汪叫"(子类自有方法)
console.log(dog.features); // ['呼吸', '繁殖'](继承实例属性)

优点:

  • 既继承了父类的实例属性(不共享),又继承了原型方法(可复用),还能向父类传参。

缺点:

  • 父类构造函数会被调用两次:一次是 new Animal() 创建子类原型时,一次是 Animal.call(this) 时,造成性能浪费。

四、寄生组合继承(优化组合继承的缺陷)

核心思想:通过 Object.create 复制父类原型作为子类原型,避免父类构造函数被调用两次,是目前最理想的继承方式。

实现示例:

// 父类
function Animal(name) {this.name = name;this.features = ['呼吸', '繁殖'];
}Animal.prototype.eat = function() {console.log(`${this.name} 在吃东西`);
};// 子类
function Dog(name) {Animal.call(this, name); // 只调用一次父类构造函数
}// 关键:复制父类原型作为子类原型(不调用父类构造函数)
Dog.prototype = Object.create(Animal.prototype); 
Dog.prototype.constructor = Dog; // 修复构造函数指向// 测试
const dog = new Dog('旺财');
dog.eat(); // "旺财 在吃东西"(继承原型方法)
console.log(dog instanceof Animal); // true(正确的原型链关系)

优点:

  • 完美解决组合继承的缺陷(父类构造函数只调用一次)。
  • 保留了原型链继承和构造函数继承的所有优点。
    (ES6 的 class extends 本质就是这种方式的语法糖)

五、ES6 class 继承(语法糖,推荐使用)

ES6 引入 classextends 关键字,简化了继承写法,底层仍基于原型链,但更贴近传统面向对象的语法。

实现示例:

// 父类
class Animal {// 构造函数(对应 ES5 的构造函数)constructor(name) {this.name = name;this.features = ['呼吸', '繁殖'];}// 原型方法(自动挂载到 Animal.prototype)eat() {console.log(`${this.name} 在吃东西`);}// 静态方法(不会被实例继承,只能通过类调用)static isAnimal(obj) {return obj instanceof Animal;}
}// 子类:通过 extends 继承父类
class Dog extends Animal {constructor(name) {// 必须先调用 super(),相当于调用父类构造函数super(name); }// 子类自有方法bark() {console.log(`${this.name} 在汪汪叫`);}// 重写父类方法eat() {console.log(`${this.name} 爱吃肉`);}
}// 测试
const dog = new Dog('旺财');
dog.eat(); // "旺财 爱吃肉"(重写父类方法)
dog.bark(); // "旺财 在汪汪叫"(子类方法)
console.log(Animal.isAnimal(dog)); // true(调用父类静态方法)

特点:

  • extends 对应原型链继承,super() 对应构造函数继承中的 call
  • 支持重写父类方法(多态)。
  • 静态方法(static)会被子类继承(通过 类名.方法 调用)。

六、其他继承方式(了解即可)

  1. 原型式继承:通过 Object.create 直接创建一个基于现有对象的新对象(适合简单对象的继承)。

    const animal = { name: '动物', eat: () => {} };
    const dog = Object.create(animal); // dog 继承 animal 的属性和方法
    
  2. 寄生式继承:在原型式继承的基础上,增强新对象(添加属性 / 方法)。

    function createDog(animal) {const dog = Object.create(animal);dog.bark = () => console.log('汪汪叫'); // 增强对象return dog;
    }
    
  3. 多重继承:JavaScript 不直接支持多继承,但可通过 “混入(mixin)” 实现(复制多个对象的属性到目标对象)。

总结

JavaScript 继承的核心是原型链,所有继承方式都是围绕原型链的优化:

  • 早期通过原型链 + 构造函数组合实现继承。
  • 寄生组合继承是 ES5 中最完美的方案。
  • ES6 的 class extends 是寄生组合继承的语法糖,简化了写法,是目前的推荐方式。

理解原型链的工作原理(对象通过 __proto__ 指向原型,构造函数通过 prototype 关联原型),是掌握 JavaScript 继承的关键。

import()与require()的区别

在 JavaScript 中,import()require() 都是用于模块导入的语法,但它们属于不同的模块系统,存在多方面区别:

  1. 所属模块系统不同
  • require()CommonJS 模块系统的语法,主要用于 Node.js 环境(早期前端也通过 Webpack 等工具兼容)。
  • import(包括 import() 动态导入)是 ES6 模块系统(ESM)的语法,是 JavaScript 官方标准化的模块系统,现在浏览器和 Node.js(需配置)均支持。
  1. 加载时机不同
  • require()运行时加载:代码执行到 require() 语句时才会加载模块,属于动态加载。
  • 静态 import(如 import xxx from 'xxx')是 编译时(解析阶段)加载:在代码执行前就会解析模块依赖,属于静态加载(无法在条件语句中使用)。
  • import() 是 ESM 中的动态导入:虽然语法是 import(),但本质是运行时加载,返回一个 Promise,可在条件语句中使用。
  1. 加载方式与阻塞性
  • require()同步加载:会阻塞后续代码执行,直到模块加载完成。
  • 静态 import异步加载(浏览器环境):加载模块时不会阻塞页面渲染,模块依赖会并行加载。
  • import()异步加载:返回 Promise,通过 .then()await 处理结果,完全非阻塞。
  1. 返回值性质不同
  • require() 返回的是模块导出对象的拷贝:模块内部的变化不会影响已导入的拷贝(除非导出的是引用类型,如对象 / 数组,修改其属性会生效)。

    // 模块 a.js
    let count = 1;
    module.exports = { count };// 主文件
    const a = require('./a');
    a.count = 2; // 仅修改拷贝,原模块的 count 仍为 1
    
  • import 导入的是模块导出的引用:模块内部的变化会实时反映到导入处(因为 ESM 是动态绑定)。

    // 模块 a.js
    export let count = 1;
    export const increment = () => { count++ };// 主文件
    import { count, increment } from './a.js';
    increment(); 
    console.log(count); // 输出 2(实时反映模块内部变化)
    
  1. 语法灵活性不同
  • require() 可动态生成路径,支持表达式:

    const path = './module-' + Math.random();
    const module = require(path); // 合法
    
  • 静态 import 路径必须是静态字符串(无法动态拼接):

    const path = './module.js';
    import { func } from path; // 报错(路径必须是字面量)
    
  • import() 支持动态路径(结合 ESM 的动态加载能力):

    const path = './module-' + Math.random();
    import(path).then(module => { /* 使用模块 */ }); // 合法
    
  1. 导出语法配合不同
  • require() 对应 CommonJS 的导出语法 module.exportsexports

    // 导出
    module.exports = { name: 'foo' };
    exports.age = 18;// 导入
    const obj = require('./module');
    
  • import 对应 ESM 的导出语法 exportexport default

    // 导出
    export const name = 'foo';
    export default { age: 18 };// 导入
    import { name }, defaultObj from './module.js';
    
  1. 适用场景不同
  • require():主要用于 Node.js 环境(默认使用 CommonJS),或需要动态加载且兼容旧系统的场景。
  • 静态 import:用于前端工程化项目(如 Vue/React)、现代 Node.js 项目(需配置 "type": "module"),适合静态分析和 Tree-Shaking 优化。
  • import():用于按需加载(如路由懒加载)、条件加载场景,兼顾 ESM 特性和动态性。

总结:require() 是 CommonJS 的同步动态加载,import(静态)是 ESM 的编译时静态加载,import() 是 ESM 的异步动态加载,三者在模块系统、加载机制和使用场景上有显著区别。

forEach 跟map得区别以及他们得参数

forEachmap 都是 JavaScript 数组的遍历方法,用于对数组中的每个元素执行回调函数,但它们的核心用途返回值有本质区别,参数则基本一致。

一、参数对比

两者的参数结构完全相同,都接收两个参数:

  1. 回调函数(必选):对数组每个元素执行的函数,包含 3 个参数:
    • currentValue:当前正在处理的数组元素(必选)
    • index:当前元素的索引(可选)
    • array:调用该方法的原数组(可选)
  2. thisArg(可选):执行回调函数时,指定 this 的指向(在回调中可通过 this 访问)。

二、核心区别

特性forEachmap
返回值返回 undefined(无实际返回值)返回一个新数组(由回调函数的返回值组成)
核心用途用于 “执行操作”(如打印、修改外部变量等)用于 “转换数组”(根据原数组生成新数组)
是否改变原数组本身不改变,但回调中可手动修改原数组本身不改变原数组,仅返回新数组
链式调用不能(因返回 undefined可以(因返回新数组,可继续调用其他数组方法)

三、示例说明

  1. forEach:执行操作,无返回值
const arr = [1, 2, 3];
let sum = 0;// 遍历数组,累加元素值(执行操作)
arr.forEach((item, index, array) => {sum += item;console.log(`索引${index}的值:${item}`); // 打印每个元素
});console.log(sum); // 输出:6
console.log(arr.forEach(...)); // 输出:undefined(无返回值)
  1. map:转换数组,返回新数组
const arr = [1, 2, 3];// 遍历数组,返回每个元素的2倍组成的新数组(转换操作)
const newArr = arr.map((item, index) => {return item * 2; // 回调返回值会被放入新数组
});console.log(newArr); // 输出:[2, 4, 6](新数组)
console.log(arr); // 输出:[1, 2, 3](原数组不变)// 支持链式调用(因返回新数组)
const filteredArr = arr.map(item => item * 2).filter(item => item > 3);
console.log(filteredArr); // 输出:[4, 6]

四、使用建议

  • 当需要仅执行操作(如日志打印、修改外部状态),无需生成新数组时,用 forEach

  • 当需要根据原数组生成新数组(如数据转换、格式化)时,用 map(更符合 “函数式编程” 思想)。

  • 注意:两者都不能通过 break 中断遍历(若需中断,可考虑 for 循环或 some/every)。

操作类型Object.defineProperty 能否拦截Proxy 能否拦截
读取属性(obj.prop能(通过 get能(get 陷阱)
赋值属性(obj.prop = x能(通过 set能(set 陷阱)
删除属性(delete obj.prop不能(需额外处理)能(deleteProperty 陷阱)
检查属性是否存在(prop in obj不能能(has 陷阱)
遍历对象(for...in不能能(ownKeys 陷阱)
调用函数(obj.fn()不能(需单独处理函数属性)能(apply 陷阱)
数组操作(push/pop 等)不能(默认不触发 set能(通过 set 陷阱拦截)
访问原型链(obj.__proto__不能能(getPrototypeOf 陷阱)

简单说:

  • Object.defineProperty 只能拦截单个属性的读写,其他操作(如删除属性、数组方法调用)无法直接拦截。
  • Proxy 可以拦截对象的所有操作(共 13 种陷阱),覆盖更全面。

3. 对数组的支持

Object.defineProperty 对数组的拦截能力很弱,而 Proxy 天然支持数组操作拦截:

  • Object.defineProperty
    数组的 pushpopsplice 等方法会修改数组长度或元素,但默认不会触发 defineProperty 定义的 set 拦截(因为这些操作本质是修改数组的 length 或索引,而非直接赋值)。
    若要拦截数组操作,需手动重写数组原型方法(如 Vue 2 的实现方式),非常繁琐。

  • Proxy
    数组的任何修改操作(包括 pushsplice 等)都会触发 ProxysetdeleteProperty 陷阱,无需额外处理。例如:

    const arr = [1, 2, 3];
    const proxyArr = new Proxy(arr, {set(target, prop, value) {console.log(`修改了属性 ${prop}${value}`);target[prop] = value;return true;}
    });
    proxyArr.push(4); // 会触发 set 陷阱(因为 push 会修改索引 3 和 length)
    

4. 对原对象的影响

  • Object.defineProperty
    直接在原对象上修改属性描述符,会改变原对象的结构。例如:

    const obj = {};
    Object.defineProperty(obj, 'name', {get() { return 'xxx'; },set(v) { /* ... */ }
    });
    // obj 本身被修改了,新增了 name 属性的访问器
    
  • Proxy
    不修改原对象,而是返回一个新的代理对象,所有操作通过代理对象进行。原对象保持不变:

    const obj = { name: 'xxx' };
    const proxyObj = new Proxy(obj, { /* 拦截器 */ });
    // obj 未被修改,proxyObj 是新的代理层
    

5. 嵌套对象的处理

  • Object.defineProperty
    只能拦截当前对象的属性,若对象包含嵌套对象(如 obj.a.b),需要手动递归对嵌套对象的属性设置 get/set,否则无法拦截嵌套属性的操作。

  • Proxy
    可以在 get 陷阱中自动递归代理嵌套对象,实现对深层属性的拦截,更简洁:

    function createProxy(obj) {return new Proxy(obj, {get(target, prop) {const value = target[prop];// 若属性值是对象,递归创建代理if (typeof value === 'object' && value !== null) {return createProxy(value);}return value;},set(target, prop, value) {console.log(`设置 ${prop}=${value}`);target[prop] = value;return true;}});
    }
    const obj = { a: { b: 1 } };
    const proxy = createProxy(obj);
    proxy.a.b = 2; // 会触发 set 陷阱,拦截成功
    

6. 兼容性

  • Object.defineProperty
    支持 IE9+(IE8 及以下部分支持),兼容性较好,适合需要兼容旧浏览器的场景。
  • Proxy
    不支持 IE 浏览器,仅支持现代浏览器(Chrome 49+、Firefox 18+ 等),兼容性较差,但功能更强大。

7. 典型应用场景

  • Object.defineProperty
    因兼容性较好,早期常用于实现简单的响应式系统(如 Vue 2 的响应式原理),但需手动处理数组和嵌套对象。
  • Proxy
    因功能全面,现代框架更倾向于使用(如 Vue 3 的响应式原理、MobX 等),能更优雅地处理对象和数组的各种操作。

总结

维度Object.definePropertyProxy
拦截范围仅单个属性的读写所有对象操作(13 种陷阱)
数组支持弱(需手动重写方法)强(天然支持所有数组操作)
原对象影响直接修改原对象不修改原对象,返回代理对象
嵌套对象处理需手动递归可自动递归代理
兼容性较好(IE9+)较差(不支持 IE)
典型应用Vue 2 响应式、简单属性拦截Vue 3 响应式、复杂对象代理

简单说:Object.defineProperty 是 “属性级” 的拦截工具,功能有限但兼容性好;Proxy 是 “对象级” 的拦截工具,功能强大但兼容性较差,是更现代的解决方案。

map跟Oject得区别

简单说:Object 是 “传统键值对容器”,适合简单场景;Map 是 “更现代的集合类型”,在灵活性、性能和功能上更优,尤其适合复杂的键值对管理。

对比维度ObjectMap
键的类型只能是字符串(String)或 Symbol,非字符串键会被自动转为字符串支持任意类型(基本类型、对象、函数等),键不会被转换
键的顺序顺序复杂:数字键按数值排序,字符串键按插入顺序,Symbol 键最后严格按照插入顺序保存,迭代时也按插入顺序返回
大小获取无内置属性,需通过 Object.keys(obj).length 计算size 属性,直接返回键值对数量
迭代方式本身不可迭代,需通过 Object.keys()/Object.entries() 转换后迭代本身是可迭代对象,支持直接迭代,提供 keys()/values()/entries() 方法
原型链影响继承原型链属性(如 toString),可能导致键冲突,需用 hasOwnProperty 检查无原型链干扰,所有键均为自身属性,无默认属性冲突
增删改查操作通过点语法 / 方括号(obj.key = val),删除用 delete,检查用 in通过专门方法:set()/get()/delete()/has(),清空用 clear()
性能适合静态数据,频繁增删属性时性能较差优化了动态增删操作,大量键值对且频繁变化时性能更优
序列化可直接用 JSON.stringify() 序列化(会丢失部分类型)不能直接序列化,需手动转为数组 / 对象后处理
适用场景静态配置、简单数据结构、需 JSON 序列化的场景键为非字符串类型、需保证插入顺序、频繁增删或遍历的场景
map跟set得区别

在 JavaScript 中,MapSet 都是 ES6 引入的集合类型,用于存储数据,但它们的存储结构核心用途有本质区别。以下通过对比表和示例详细说明:

对比维度MapSet
存储内容键值对(key-value),类似 “字典”唯一的值(value),类似 “无重复元素的列表”
元素唯一性键(key)唯一,相同键会覆盖旧值值(value)唯一,重复值会被忽略
主要方法set(key, value):添加 / 修改键值对 get(key):获取键对应的值 has(key):检查键是否存在 delete(key):删除键值对 clear():清空所有键值对add(value):添加值(重复值无效) has(value):检查值是否存在 delete(value):删除值 clear():清空所有值
迭代内容可迭代键(keys())、值(values())、键值对(entries(),默认)可迭代值(values(),默认)、entries()(返回 [value, value] 数组)
使用场景需要存储 “键 - 值关联” 数据(如字典、缓存、映射关系)需要存储 “唯一值”(如去重、集合运算、检查存在性)
== 做了怎样得转换

转换流程总结

== 的比较步骤可简化为:

  1. 若两边类型相同,直接比较值(特殊处理 NaN、±0);
  2. 若类型不同,根据 “类型对”(如数字 vs 字符串、布尔 vs 数字等)执行对应转换;
  3. 转换为相同类型后,再次比较值是否相等。

注意

== 的转换规则复杂且容易出现反直觉结果(如 "" == 0 → true[] == false → true),因此实际开发中更推荐使用 ===(严格相等运算符),它不进行类型转换,仅当类型和值都相同时才返回 true,避免潜在的逻辑错误。

原生js能发起请求的有哪些API

记忆方法:XHR(API设计繁琐,webworker不支持),fetch(需要手动处理错误),navigator.sendBeacon(页面卸载上报,只能发起post请求),websocket,SSE.

在原生 JavaScript 中,用于发起网络请求的 API 主要有以下几种,它们适用于不同的场景(如 HTTP 请求、实时通信、后台数据上报等):

  1. XMLHttpRequest(XHR)

最传统的网络请求 API,几乎所有浏览器都支持,可用于发送各种类型的 HTTP 请求(GET、POST 等)。
特点

  • 支持同步 / 异步请求(但同步请求会阻塞线程,不推荐);
  • 可监控请求进度(如上传 / 下载进度);
  • 兼容性极佳(包括 IE6+),但 API 设计较繁琐。

基本用法示例

// 创建 XHR 实例
const xhr = new XMLHttpRequest();// 配置请求(方法、URL、是否异步)
xhr.open('GET', 'https://api.example.com/data', true);// 设置请求头(可选)
xhr.setRequestHeader('Content-Type', 'application/json');// 监听请求状态变化
xhr.onreadystatechange = function() {// readyState 4 表示请求完成,status 200 表示成功if (xhr.readyState === 4 && xhr.status === 200) {const response = JSON.parse(xhr.responseText); // 解析响应数据console.log('请求成功:', response);} else if (xhr.readyState === 4) {console.error('请求失败:', xhr.statusText);}
};// 发送请求(POST 请求可在这里传参:xhr.send(JSON.stringify(data)))
xhr.send();
  1. Fetch API

现代网络请求 API(ES6+ 引入),基于 Promise 设计,语法更简洁,支持链式调用和 async/await,是目前推荐的主流方案。
特点

  • 默认异步,返回 Promise 对象,适合处理异步逻辑;
  • 支持流式处理响应(如大文件下载);
  • 原生支持 Promise,可与 async/await 配合使用,代码更清晰;
  • 不支持同步请求,且错误处理需要手动判断 HTTP 状态码(如 404、500 不会触发 Promise 的 catch)。

基本用法示例

// GET 请求
fetch('https://api.example.com/data').then(response => {// 检查 HTTP 状态码(2xx 表示成功)if (!response.ok) {throw new Error(`HTTP 错误:${response.status}`);}return response.json(); // 解析 JSON 响应(也可使用 text()、blob() 等)}).then(data => console.log('请求成功:', data)).catch(error => console.error('请求失败:', error));// POST 请求(带参数)
async function postData() {try {const response = await fetch('https://api.example.com/submit', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({ name: 'test', value: 123 }), // 请求体(需序列化)});if (!response.ok) throw new Error('提交失败');const result = await response.json();console.log('提交成功:', result);} catch (error) {console.error('错误:', error);}
}
  1. navigator.sendBeacon()

专门用于后台发送小型数据(如统计信息、日志上报)的 API,确保页面卸载(如关闭标签页、刷新)时数据能被成功发送,不阻塞页面卸载流程。
特点

  • 异步发送,但不会阻塞当前页面的卸载或导航;
  • 主要用于 POST 请求,数据大小有限制(通常几 KB);
  • 适用于页面离开时的埋点、日志上报等场景。

基本用法示例

// 页面关闭时上报数据
window.addEventListener('unload', () => {// 发送数据到服务器(数据会被编码为 form-data 格式)const data = { action: 'page_close', time: new Date().getTime() };const success = navigator.sendBeacon('https://api.example.com/log',JSON.stringify(data) // 数据需序列化(或使用 FormData));console.log('上报是否成功:', success);
});
  1. WebSocket

用于建立持久化的全双工通信连接(基于 TCP),适用于实时通信场景(如聊天、实时数据更新)。与 HTTP 请求不同,WebSocket 是 “长连接”,服务器和客户端可双向主动发送数据。
特点

  • 一旦连接建立,客户端和服务器可随时互发消息,无需重复发起请求;

  • 基于帧(frame)传输数据,开销小,实时性高;

  • 使用 ws://wss:// 协议(后者加密)。

基本用法示例

// 建立 WebSocket 连接
const ws = new WebSocket('wss://api.example.com/realtime');// 连接成功时触发
ws.onopen = () => {console.log('WebSocket 连接已建立');// 向服务器发送消息ws.send(JSON.stringify({ type: 'hello', data: '客户端已连接' }));
};// 收到服务器消息时触发
ws.onmessage = (event) => {const message = JSON.parse(event.data);console.log('收到服务器消息:', message);
};// 连接关闭时触发
ws.onclose = (event) => {console.log('连接关闭,代码:', event.code);
};// 连接出错时触发
ws.onerror = (error) => {console.error('WebSocket 错误:', error);
};

总结

API适用场景特点
XMLHttpRequest兼容旧浏览器、需要监控进度的请求支持同步 / 异步,API 较繁琐
Fetch API现代 HTTP 请求(GET/POST 等)基于 Promise,语法简洁,推荐优先使用
navigator.sendBeacon页面卸载时的后台数据上报不阻塞页面,确保数据送达
WebSocket实时通信(聊天、实时更新)长连接,双向通信,低延迟

兼容性与局限性

API兼容性主要局限性
XMLHttpRequest极佳(IE6+ 及所有现代浏览器)API 设计繁琐,异步处理易嵌套,同步阻塞线程
Fetch API现代浏览器(IE 完全不支持)默认不携带 Cookie,错误需手动判断(404/500 不触发 catch)
navigator.sendBeacon()IE 不支持,现代浏览器支持数据大小有限制,无法获取响应,请求方法固定为 POST
WebSocket现代浏览器(IE10+)需服务器支持 WebSocket 协议,不适合简单请求

适用场景总结

  • XMLHttpRequest:需兼容旧浏览器(如 IE)、需要监控上传 / 下载进度的场景(如文件上传)。
  • Fetch API:现代 Web 应用的常规 HTTP 请求(GET/POST 等),优先推荐(配合 async/await 代码清晰)。
  • navigator.sendBeacon():页面离开时的日志上报、用户行为统计(确保数据不丢失)。
  • WebSocket:实时通信场景(如在线聊天、股票行情、多人协作工具)。

通过这些区别可以看出,没有 “万能” 的 API,需根据具体需求(如是否实时、是否跨域、是否需要处理页面卸载)选择最合适的工具。

SSE 是原生 JavaScript 中专门用于 服务器单向持续推送数据 的 API(通过 EventSource 实现),它填补了 “服务器主动推送” 的需求空白,与其他 API 的核心区别在于 单向长连接推送基于 HTTP 协议的轻量实现

如果你的场景是 “客户端只需被动接收服务器更新,无需频繁向服务器发送数据”,SSE 比 WebSocket 更简单易用;如果需要双向通信,则优先选 WebSocket。

实际开发中,Fetch API 是处理常规 HTTP 请求的首选;需要兼容旧浏览器时用 XMLHttpRequest;实时场景用 WebSocket;后台上报用 sendBeacon

JSONP实现原理

记忆点:核心原理是利用 <script> 标签的 src 属性不受同源策略限制的特性,实现跨域数据传输。

JSONP 的核心思想是:客户端定义一个回调函数,通过 <script> 标签跨域请求服务器,服务器将数据包裹在回调函数中返回,客户端通过执行回调函数获取数据

JSONP(JSON with Padding)是一种经典的跨域数据请求解决方案,其核心原理是利用 <script> 标签的 src 属性不受同源策略限制的特性,实现跨域数据传输。以下是其详细实现原理:

一、核心背景:同源策略与跨域限制

浏览器的同源策略规定:不同协议、域名、端口的页面之间,默认不允许通过 AJAX(XMLHttpRequest/Fetch)直接进行数据交互。例如,http://a.com 的页面无法直接通过 AJAX 请求 http://b.com 的接口,否则会被浏览器拦截。

<script><img><link> 等标签的 src 属性是例外,它们的请求不受同源策略限制,可以跨域加载资源。JSONP 正是利用了 <script> 标签的这一特性。

二、JSONP 的实现原理

JSONP 的核心思想是:客户端定义一个回调函数,通过 <script> 标签跨域请求服务器,服务器将数据包裹在回调函数中返回,客户端通过执行回调函数获取数据

具体步骤如下:

1. 客户端准备回调函数

客户端在页面中预先定义一个回调函数(名称可自定义),用于接收和处理服务器返回的数据。例如:

// 定义回调函数:参数为服务器返回的数据
function handleResponse(data) {console.log("跨域获取的数据:", data);// 处理数据的逻辑...
}

2. 动态创建 <script> 标签发起跨域请求

客户端通过 JavaScript 动态创建 <script> 标签,并将其 src 属性指向目标服务器的接口 URL。同时,需要在 URL 中通过参数(通常约定为 callback)告知服务器回调函数的名称(即步骤 1 中定义的函数名)。

示例代码:

// 生成唯一的回调函数名(避免重复)
const callbackName = "handleResponse"; // 创建 script 标签
const script = document.createElement("script");
// 设置 src:跨域请求的 URL + 回调函数名参数
script.src = "http://example.com/api?callback=" + callbackName;
// 将 script 插入页面,触发请求
document.body.appendChild(script);

3. 服务器返回「回调函数包裹数据」的响应

服务器接收到请求后,解析 URL 中的 callback 参数(即客户端定义的回调函数名 handleResponse),然后将需要返回的数据包裹在该回调函数中,以 JavaScript 代码的形式返回。

例如,服务器需要返回的数据是 { "name": "JSONP", "type": "cross-domain" },则返回的响应内容为:

handleResponse({ "name": "JSONP", "type": "cross-domain" });

4. 客户端执行回调函数获取数据

<script> 标签加载完服务器返回的响应后,浏览器会将其视为 JavaScript 代码并立即执行。此时,客户端预先定义的 handleResponse 函数会被调用,参数就是服务器返回的数据,从而实现跨域数据的获取。

5. 清理资源(可选)

请求完成后,可以移除动态创建的 <script> 标签,避免页面冗余:

script.onload = function() {document.body.removeChild(script); // 加载完成后移除 script 标签
};

三、JSONP 的特点

  1. 优点
    • 实现简单,兼容性好(支持所有主流浏览器,包括低版本 IE)。
    • 不受同源策略限制,可跨域请求数据。
  2. 缺点
    • 仅支持 GET 请求:因为 <script> 标签的 src 属性只能发起 GET 请求,无法用于 POST、PUT 等其他请求方式。
    • 安全性风险:服务器返回的是可执行的 JavaScript 代码,如果服务器被恶意攻击,可能返回恶意代码(如 XSS 攻击),导致客户端安全问题。
    • 无错误处理机制<script> 标签加载失败时,无法通过常规的 error 事件可靠捕获(不同浏览器行为不一致),难以处理请求失败的场景。
    • 依赖服务器配合:需要服务器主动支持 JSONP 格式,即在响应中包裹回调函数。

四、与 JSON 的区别

  • JSON 是一种轻量级的数据交换格式(纯数据),本质是字符串。
  • JSONP 是一种跨域请求方式,本质是通过 <script> 标签加载并执行包含数据的 JavaScript 代码,其响应内容是「回调函数调用 + JSON 数据」的组合。

总结

JSONP 通过巧妙利用 <script> 标签的跨域特性,以「回调函数包裹数据」的方式实现跨域数据传输,是早期解决跨域问题的重要方案。但由于其局限性(仅支持 GET、安全风险等),现代 Web 开发中更多使用 CORS(跨域资源共享) 方案,JSONP 逐渐成为历史遗留技术。

JSON进行深拷贝会有什么问题

记忆点:

JSON 深拷贝的核心问题是:JSON 格式无法完整映射 JavaScript 的所有数据类型和特性。它仅适用于 简单的纯数据对象(仅包含字符串、数字、布尔、数组、普通对象等基础类型),而对于包含函数、日期、正则、循环引用等复杂场景,会导致数据丢失、失真或报错。

使用 JSON 方法(JSON.stringify() 转字符串 + JSON.parse() 转对象)实现深拷贝是一种简单的方案,但它存在诸多限制和问题,主要源于 JSON 数据格式的局限性JavaScript 数据类型的复杂性 之间的不匹配。具体问题如下:

  1. 不支持非 JSON 标准数据类型

JSON 仅支持 null、布尔值、数字、字符串、数组、普通对象 这几种基础类型,对于 JavaScript 中的其他特殊类型,会出现 丢失或失真

  • 函数(Function)JSON.stringify() 会直接忽略函数(包括对象的方法),拷贝后该属性消失。

    const obj = { fn: () => 123 };
    const copy = JSON.parse(JSON.stringify(obj));
    console.log(copy.fn); // undefined(函数丢失)
    
  • 日期对象(Date)JSON.stringify() 会将 Date 转为 ISO 格式字符串(如 "2023-01-01T00:00:00.000Z"),但 JSON.parse() 会将其还原为 字符串 而非 Date 对象。

    const obj = { time: new Date() };
    const copy = JSON.parse(JSON.stringify(obj));
    console.log(copy.time instanceof Date); // false(变成字符串)
    
  • 正则对象(RegExp)JSON.stringify() 会将正则转为空对象 {},拷贝后完全丢失正则特性。

    const obj = { reg: /abc/g };
    const copy = JSON.parse(JSON.stringify(obj));
    console.log(copy.reg); // {}(正则丢失)
    
  • Symbol 类型:作为对象的键或值时,JSON.stringify() 会直接忽略(键会被跳过,值会被转为 null

    const s = Symbol('key');
    const obj = { [s]: 'value', val: s };
    const copy = JSON.parse(JSON.stringify(obj));
    console.log(copy); // {}(Symbol 键和值均丢失)
    
  • 其他特殊类型:如 MapSetWeakMapWeakSet 等集合类型,会被转为空对象或数组,失去原有的数据结构和方法。

  1. 无法处理循环引用

如果对象存在 循环引用(如 obj.self = obj),JSON.stringify() 会直接 抛出错误,导致拷贝失败。

const obj = {};
obj.self = obj; // 循环引用:对象引用自身// 报错:Converting circular structure to JSON
const copy = JSON.parse(JSON.stringify(obj)); 
  1. 数值精度与特殊值失真
  • NaN、Infinity、-Infinity:JSON 不支持这些特殊数值,JSON.stringify() 会将它们转为 null

    const obj = { a: NaN, b: Infinity, c: -Infinity };
    const copy = JSON.parse(JSON.stringify(obj));
    console.log(copy); // { a: null, b: null, c: null }(数值失真)
    
  • 大整数精度丢失:当数值超过 JavaScript 安全整数范围(2^53 - 1)时,JSON.parse() 会导致精度丢失。

    const obj = { num: 9007199254740993n }; // 大整数
    const copy = JSON.parse(JSON.stringify(obj));
    console.log(copy.num); // 9007199254740992(精度丢失)
    
  1. 忽略对象的特殊属性
  • 不可枚举属性JSON.stringify() 仅序列化对象的 可枚举自有属性,不可枚举属性(如 Object.defineProperty 定义的 enumerable: false 属性)会被忽略。

  • 原型链属性

    :拷贝结果会丢失原对象的原型链,变成一个纯粹的

    Object
    

    实例,无法继承原型上的方法。

    function Person(name) { this.name = name; }
    Person.prototype.sayHi = () => 'hi';
    const p = new Person('Tom');const copy = JSON.parse(JSON.stringify(p));
    console.log(copy instanceof Person); // false(原型链丢失)
    console.log(copy.sayHi); // undefined(原型方法丢失)
    
  1. 性能问题

对于 大型复杂对象(如嵌套层级极深、包含大量数据),JSON.stringify()JSON.parse() 的序列化 / 反序列化过程会产生额外的性能开销,效率低于专门的深拷贝实现(如递归拷贝 + 类型判断)。

总结

JSON 深拷贝的核心问题是:JSON 格式无法完整映射 JavaScript 的所有数据类型和特性。它仅适用于 简单的纯数据对象(仅包含字符串、数字、布尔、数组、普通对象等基础类型),而对于包含函数、日期、正则、循环引用等复杂场景,会导致数据丢失、失真或报错。

如需处理复杂类型,建议使用专门的深拷贝方法(如递归遍历 + 类型判断、lodash.cloneDeep 等库函数)。

递归深拷贝怎么处理日期正则

记忆点:使用 Object.prototype.toString.call(target) 而非 instanceof,因为 instanceof 可能受原型链修改影响,而 toString 能稳定返回内置类型标签(如 [object Date])。

在递归深拷贝中处理日期(Date)和正则(RegExp)等特殊引用类型时,需要通过类型判断针对性构造新实例的方式,确保拷贝后的对象既保持原始值,又不与原对象共享引用。以下是具体实现思路和代码示例:

一、核心处理思路

  1. 类型精准判断
    利用 Object.prototype.toString.call() 区分特殊类型(DateRegExp)与普通对象 / 数组,避免使用 typeof(因其对引用类型均返回 'object',无法区分)。
  2. 针对性复制
    • 日期(Date:通过原日期的时间戳创建新 Date 实例(new Date(originalDate.getTime())),确保新对象与原对象时间一致但引用不同。
    • 正则(RegExp:提取原正则的 source(模式)和 flags(修饰符,如 g/i/m),用 new RegExp(source, flags) 创建新实例,保持正则特性不变。

二、递归深拷贝实现(含日期和正则处理

function deepClone(target) {// 1. 基本类型直接返回(null 单独处理,因 typeof null 为 'object')if (target === null || typeof target !== 'object') {return target;}// 2. 处理日期类型if (Object.prototype.toString.call(target) === '[object Date]') {return new Date(target.getTime()); // 基于原时间戳创建新实例}// 3. 处理正则类型if (Object.prototype.toString.call(target) === '[object RegExp]') {// 提取正则的模式(source)和修饰符(flags)const reg = new RegExp(target.source, target.flags);// 复制 lastIndex 属性(正则.exec() 会用到的匹配位置)reg.lastIndex = target.lastIndex;return reg;}// 4. 处理数组(先判断数组,再判断普通对象,因数组也是 object)if (Array.isArray(target)) {const copy = [];for (let i = 0; i < target.length; i++) {copy[i] = deepClone(target[i]); // 递归拷贝数组元素}return copy;}// 5. 处理普通对象(排除上述特殊类型后)if (Object.prototype.toString.call(target) === '[object Object]') {const copy = {};// 遍历对象自身可枚举属性(不包含原型链)for (const key in target) {if (target.hasOwnProperty(key)) {copy[key] = deepClone(target[key]); // 递归拷贝属性值}}return copy;}// 6. 其他特殊类型(如 Map、Set 等,可按需扩展)return target;
}

三、测试验证

// 测试日期拷贝
const originalDate = new Date('2023-01-01');
const clonedDate = deepClone(originalDate);
console.log(clonedDate); // 2023-01-01T00:00:00.000Z(值相同)
console.log(clonedDate === originalDate); // false(引用不同)
clonedDate.setFullYear(2024);
console.log(originalDate.getFullYear()); // 2023(原对象不受影响)// 测试正则拷贝
const originalReg = /abc/gim;
originalReg.lastIndex = 5; // 设置匹配位置
const clonedReg = deepClone(originalReg);
console.log(clonedReg.source); // 'abc'(模式相同)
console.log(clonedReg.flags); // 'gim'(修饰符相同)
console.log(clonedReg.lastIndex); // 5(lastIndex 被复制)
console.log(clonedReg === originalReg); // false(引用不同)

四、关键说明

  1. 类型判断的准确性
    使用 Object.prototype.toString.call(target) 而非 instanceof,因为 instanceof 可能受原型链修改影响,而 toString 能稳定返回内置类型标签(如 [object Date])。
  2. 正则的完整复制
    除了 sourceflags,正则的 lastIndex 属性(用于全局匹配时记录下一次匹配位置)也需要手动复制,否则可能影响拷贝后正则的匹配行为。
  3. 扩展性
    上述代码可扩展至其他特殊类型(如 MapSetError 等),思路类似:先判断类型,再通过对应构造函数创建新实例并复制关键属性。

通过这种方式,递归深拷贝能准确处理日期和正则类型,既保证拷贝后的值与原对象一致,又避免引用共享导致的副作用。

原生的异步方法有那些

在 JavaScript 中,“原生异步方法” 通常指语言本身(ECMAScript 标准)或运行环境(浏览器、Node.js)内置的、无需额外依赖库即可使用的异步机制 / API。以下是常见的原生异步方法及分类:

一、语言层面的异步机制

  1. Promise 及相关方法

ES6(2015)引入的 Promise 是 JavaScript 异步编程的核心,本身是异步操作的容器,其相关方法均为原生异步:

  • new Promise((resolve, reject) => { ... }):通过构造函数创建异步任务,通过 resolve/reject 控制状态。
  • 静态方法:Promise.resolve()(包装同步值为成功的 Promise)、Promise.reject()(包装同步值为失败的 Promise)、Promise.all()(并行执行多个 Promise,全部成功才返回)、Promise.race()(取第一个完成的 Promise 结果)、Promise.allSettled()(等待所有 Promise 完成,无论成功失败)、Promise.any()(取第一个成功的 Promise 结果)。
  1. async/await

ES2017 引入的语法糖,基于 Promise 实现,简化异步代码逻辑:

  • async function:声明异步函数,返回值自动包装为 Promise。
  • await:在异步函数中暂停执行,等待 Promise 完成后继续,避免回调嵌套。

二、定时器类异步方法

通过延迟或周期性执行回调实现异步,浏览器和 Node.js 均支持:

  • setTimeout(callback, delay, ...args):延迟 delay 毫秒后执行 callback(单次)。
  • setInterval(callback, interval, ...args):每隔 interval 毫秒执行一次 callback(周期性)。
  • 清除方法:clearTimeout(timerId)clearInterval(timerId)(终止定时器)。

三、微任务调度方法

微任务是优先级高于宏任务的异步任务,原生调度方法包括:

  • queueMicrotask(callback):ES2021 引入,将 callback 加入微任务队列,在当前宏任务完成后执行(优先级高于 setTimeout 等宏任务)。
  • Promise 的回调(then/catch/finally):Promise 状态变更后,其回调会作为微任务执行。

四、浏览器环境特有异步 API

浏览器提供的 Web API 中,大量异步方法用于处理网络、DOM、动画等:

  • 网络请求:
    • fetch(url, options):ES2015+ 原生网络请求 API,返回 Promise,替代传统的 XMLHttpRequest(XHR 也可异步,但 fetch 更现代)。
    • XMLHttpRequest:传统异步请求对象(通过 open(method, url, async=true) 开启异步)。
  • DOM 事:
    • 所有 DOM 事件回调(如 addEventListener('click', callback)onloadonerror 等)均为异步触发(事件队列调度)。
  • 动画与渲染:
    • requestAnimationFrame(callback):浏览器重绘前执行回调,用于高性能动画(同步于浏览器刷新频率,异步触发)。
    • requestIdleCallback(callback):在浏览器空闲时执行回调(低优先级异步)。
  • 其他:
    • WebSocket 事件(onopen/onmessage/onclose):WebSocket 通信的异步回调。
    • FileReader:异步读取本地文件(readAsText() 等方法的回调)。

五、Node.js 环境特有异步 API

Node.js 内置模块提供了大量异步方法,核心用于 I/O 操作(非阻塞 I/O 是 Node.js 核心特性):

  • 文件系统(fs 模块):
    • 异步文件操作:fs.readFile(path, callback)fs.writeFile(path, data, callback)fs.mkdir(path, callback) 等(所有不带 Sync 后缀的方法均为异步)。
  • 定时器与调度:
    • 同浏览器的 setTimeout/setInterval,额外支持 setImmediate(callback)(当前事件循环结束后执行,优先级低于微任务)。
  • 微任务与异步调度:
    • process.nextTick(callback):Node 特有微任务,优先级高于 queueMicrotask 和 Promise 微任务(在当前操作完成后立即执行)。
  • 流(Stream
    • 所有流操作(如 fs.createReadStream)的事件(data/end/error)均为异步触发。
  • 其他 I/O 模块:
    • 网络(net/http 模块):如 http.createServer()request 事件回调。
    • 数据库操作(原生 fs 之外,Node 内置模块无数据库 API,但第三方库通常基于原生异步机制实现)。

总结

原生异步方法的核心是通过 “非阻塞” 方式处理耗时操作(如网络请求、文件 I/O、定时器等),避免主线程阻塞。按场景可分为:语言层的 Promise/async-await、定时器、微任务调度,以及环境特有的 Web API(浏览器)或 Node.js 模块 API。

promise除了.catch还有哪些方法能铺获到异常

记忆点:

  • 最常用的是 .catch() 方法,可捕获整个 Promise 链的异常;

  • then() 的第二个参数可捕获当前 Promise 的 reject,但功能有限;

  • try/catch 配合 async/await 是异步代码中处理异常的更直观方式;

  • 静态方法Promise.all()Promise.race()的异常需通过其返回的 Promise 进行捕获。

在 Promise 中,除了 .catch() 方法,还有以下几种方式可以捕获异常(即处理 Promise 的 reject 状态):

  1. then() 方法的第二个参数

Promise.prototype.then() 方法可以接收两个参数:

  • 第一个参数:处理 Promise 成功状态(resolve)的回调函数;
  • 第二个参数:专门处理 Promise 失败状态(reject)的回调函数,作用等同于 .catch()

示例:

new Promise((resolve, reject) => {reject(new Error("出错了"));
})
.then((data) => { console.log("成功:", data); },  // 第一个参数:处理 resolve(err) => { console.log("捕获到错误:", err.message); }  // 第二个参数:处理 rect
);

注意then() 的第二个参数只能捕获当前 Promise 的 reject 错误,无法捕获其第一个参数(成功回调)中抛出的错误;而 .catch() 可以捕获整个 Promise 链中所有前置操作的错误(包括 then 回调中抛出的错误)。

2. try/catch(配合 async/await

虽然 try/catch 不是 Promise 自身的方法,但当使用 async/await 语法时,try/catch 可以捕获 await 后面 Promise 的异常(包括 reject 和回调中抛出的错误),是处理 Promise 异常的常用方式。

示例

async function handlePromise() {try {const result = await new Promise((resolve, reject) => {reject(new Error("出错了"));});console.log("成功:", result);} catch (err) {console.log("捕获到错误:", err.message);  // 捕获 Promise 的 reject}
}handlePromise();

3. Promise 静态方法的错误传播(间接捕获)

Promise 的静态方法(如 Promise.all()Promise.race() 等)返回的 Promise 会在内部某个 Promise 被 reject 时立即进入 reject 状态,此时可以通过 .catch()then() 的第二个参数捕获:

示例:

// Promise.all() 中某个 Promise reject 时,整体会 reject
Promise.all([Promise.resolve(1),Promise.reject(new Error("任务2失败")),Promise.resolve(3)
])
.catch(err => {console.log("捕获到错误:", err.message);  // 输出:"任务2失败"
});

总结

  • 最常用的是 .catch() 方法,可捕获整个 Promise 链的异常;
  • then() 的第二个参数可捕获当前 Promise 的 reject,但功能有限;
  • try/catch 配合 async/await 是异步代码中处理异常的更直观方式;
  • 静态方法的异常需通过其返回的 Promise 进行捕获。

这些方式共同构成了 Promise 异常处理的完整体系,可根据场景选择使用。

call和apply什么函数都生效嘛

callapply 并非对所有函数都生效:

  • 普通函数(有自己的 this 绑定)有效,可改变 this 并调用。

  • 箭头函数无效(this 固定,无法修改)。

  • 对部分内置函数可能有限制(若 this 类型不符合要求会报错)。

可以说一下操作系统里的堆和栈吗?

记忆点:

  • 栈是 “轻量、高效的临时内存”,用自动管理和快速访问支持函数调用,适合存储生命周期短、大小固定的数据;

  • 堆是 “灵活、动态的长期内存”,用手动管理(或自动回收)支持动态数据存储,适合存储生命周期长、大小不确定的数据。

在操作系统中,堆(Heap)和栈(Stack)是进程内存空间中两种核心的动态内存区域,它们承担不同的功能,遵循不同的管理规则,是程序运行的基础。理解操作系统层面的堆和栈,有助于理解程序的内存分配、函数调用机制以及常见的内存问题(如栈溢出、内存泄漏)。

一、栈(Stack):函数调用的 “临时舞台”

操作系统中的栈是一块连续的内存区域,主要用于支持函数调用和局部变量存储,其核心特性是 “先进后出(FILO)”,类似叠盘子:最后放入的盘子最先被取出。

1. 栈的核心功能

  • 存储函数调用上下文

    :当一个函数被调用时,操作系统会为其创建一个 “

    栈帧(Stack Frame)

    ”,包含:

    • 函数的返回地址(调用结束后回到哪里继续执行);
    • 函数的参数(按调用约定入栈,如 C 语言的 “从右到左入栈”);
    • 局部变量(函数内定义的临时变量,如int a = 10);
    • 被保存的寄存器状态(函数调用前需要保存寄存器的值,避免覆盖)。
  • 支持函数嵌套调用:每个嵌套调用的函数都会生成新的栈帧并 “压栈”,函数执行结束后栈帧 “出栈”,内存自动释放。

2. 栈的关键特性

  • 自动分配与释放:完全由操作系统(或编译器通过生成的机器码)自动管理,无需程序员干预。函数调用时栈帧入栈,函数返回时栈帧出栈,内存立即回收,不会产生碎片。
  • 连续内存与固定大小:栈的内存地址是连续的(从高地址向低地址增长),大小通常在程序启动时由操作系统固定(如 Linux 默认栈大小为 8MB,可通过ulimit -s调整)。
  • 访问速度极快:栈的内存地址连续,且 CPU 会对栈进行缓存优化(栈顶附近的内存大概率被频繁访问),因此读写速度远快于堆。
  • 严格的生命周期:栈上的变量生命周期与函数调用绑定,函数执行结束后,局部变量立即失效,无法被外部访问。
  1. 栈的典型问题
  • 栈溢出(Stack Overflow):若函数嵌套层数过深(如递归无终止条件)或局部变量过大(如定义巨型数组int arr[1000000]),会耗尽栈空间,触发栈溢出错误(程序崩溃)。

二、堆(Heap):动态内存的 “自由市场”

操作系统中的堆是一块非连续的内存区域,用于程序运行时动态分配内存(即 “按需申请、手动释放”),是存储长生命周期数据的主要区域。

  1. 堆的核心功能
  • 动态内存分配

    :程序运行时,通过系统调用(如 C 语言的

    malloc
    

    、C++ 的

    new
    

    ,底层依赖操作系统的

    brk
    

    sbrk
    

    mmap
    

    )向堆申请内存,用于存储大小不确定或生命周期较长的数据,例如:

    • 动态创建的对象(如new Object());
    • 长度动态变化的数组(如vector的动态扩容);
    • 跨函数共享的数据(如函数返回的动态分配指针)。

2. 堆的关键特性

  • 手动管理(或语言层自动管理):堆内存需要显式申请(如malloc)和释放(如free),若忘记释放会导致 “内存泄漏”;部分语言(如 Java、Python)通过垃圾回收器自动管理堆内存,避免手动操作。
  • 非连续内存与动态大小:堆的内存地址通常不连续(因频繁分配 / 释放导致碎片),大小没有固定上限(受限于系统可用内存和地址空间),从低地址向高地址增长。
  • 分配效率较低:堆的分配需要操作系统或内存管理库(如 glibc 的 ptmalloc)通过复杂算法(首次适应、最佳适应、伙伴系统等)查找空闲内存块,且可能涉及内存对齐、元数据记录(如块大小、是否已分配),因此速度远慢于栈。
  • 灵活的生命周期:堆上的数据生命周期不受函数调用限制,只要未被释放,就可以被程序的任意部分访问(通过指针或引用)。

3. 堆的典型问题

  • 内存泄漏:申请的堆内存未释放,导致内存被持续占用,长期运行会耗尽系统内存。
  • 内存碎片:频繁分配和释放不同大小的内存块,会导致堆中产生大量无法利用的小空闲块(碎片),降低内存利用率。
  • 悬空指针:若堆内存被释放后,指针未置空,后续访问该指针会导致 “未定义行为”(可能崩溃或数据错误)。

三、堆和栈在进程内存布局中的位置

在 32 位或 64 位进程的虚拟地址空间中,堆和栈的位置是固定划分的,典型布局如下(从低地址到高地址):
代码段(.text)→ 数据段(.data/.bss)→ 堆(Heap)→ 内存映射区(mmap)→ 栈(Stack)→ 内核空间

  • 栈从高地址向低地址增长(每次压栈,栈顶指针减小);
  • 堆从低地址向高地址增长(每次分配,堆顶指针增大);
  • 中间的 “内存映射区” 用于加载共享库、文件映射等,堆和栈之间有足够的空间避免重叠。

四、堆和栈的核心差异对比

维度栈(Stack)堆(Heap)
管理方式操作系统自动分配 / 释放(函数调用驱动)手动申请 / 释放(或垃圾回收)
内存地址连续,从高地址向低地址增长非连续,从低地址向高地址增长
大小限制固定且较小(通常 MB 级)动态且较大(可达 GB 级,受系统内存限制)
访问速度极快(CPU 缓存优化,连续地址)较慢(需查找空闲块,非连续地址)
用途函数调用上下文、局部变量动态数据、长生命周期对象
典型问题栈溢出内存泄漏、碎片、悬空指针
分配粒度小(栈帧大小固定或可预测)灵活(大小可变,按需分配)

五、总结

操作系统中的堆和栈是内存管理的 “两大支柱”,分工明确:

  • 是 “轻量、高效的临时内存”,用自动管理和快速访问支持函数调用,适合存储生命周期短、大小固定的数据;
  • 是 “灵活、动态的长期内存”,用手动管理(或自动回收)支持动态数据存储,适合存储生命周期长、大小不确定的数据。

两者的配合让程序既能高效处理函数调用和临时数据,又能灵活管理动态变化的复杂数据,是现代程序运行的基础内存模型。

为什么变量放到栈里,对象放堆里 ?

记忆点:

  • 栈用高效的自动管理和快速访问,适配大小固定、生命周期明确的基本类型变量;
  • 堆用动态分配和灵活的内存管理,适配大小可变、生命周期不确定的对象。

基本数据类型确定大小,生命周期确定。

在编程语言(如 JavaScript、Java、C# 等)的内存管理中,基本类型变量存放在栈(Stack)中,对象(引用类型)存放在堆(Heap)中,这一设计主要源于栈和堆的内存特性差异,以及变量与对象的自身特点的适配性。核心原因可以从内存分配机制、数据特性和访问效率三个维度来解释:

1. 栈和堆的内存特性差异

栈和堆是计算机内存中两种不同的存储区域,它们的分配 / 释放方式功能定位截然不同:

特性栈(Stack)堆(Heap)
分配方式自动分配、自动释放(由编译器 / 解释器管理)动态分配、手动或垃圾回收释放(需显式管理或语言自动回收)
空间大小空间较小(通常几 MB),大小固定空间较大(可至 GB 级),大小动态扩展
访问速度速度极快(内存地址连续,CPU 缓存友好)速度较慢(内存地址分散,需通过指针访问)
数据结构线性结构(先进后出,FILO)树形 / 图结构(无固定顺序)

2. 基本类型变量与栈的适配性

基本类型变量(如数字、布尔值、null、undefined、短字符串等)的特点是:

  • 大小固定:例如 JavaScript 中 Number 固定为 8 字节,Boolean 为 1 字节,编译期即可确定占用空间。
  • 生命周期明确:通常与函数作用域绑定(如局部变量),函数执行时创建,执行结束后销毁,生命周期短且可预测。

这些特点完美匹配栈的特性:

  • 栈的自动分配 / 释放机制可以高效处理这类变量:函数调用时,局部变量随 “栈帧”(Stack Frame)入栈;函数执行完毕,栈帧出栈,变量内存自动释放,无需手动管理,效率极高。
  • 栈的连续内存快速访问特性,让基本类型的读写速度更快(直接操作值,无需间接寻址)。

3. 对象(引用类型)与堆的适配性

对象(引用类型)(如对象、数组、函数等)的特点是:

  • 大小不固定:对象可以动态添加属性(如 obj.newProp = 1),数组可以动态扩容(如 arr.push(2)),编译期无法确定最终大小。
  • 生命周期不确定:对象可能被多个变量引用(如 let a = obj; let b = a),其生命周期不局限于某个函数作用域,无法随栈帧释放。

这些特点更适合堆的特性:

  • 堆的动态分配能力可以满足对象大小可变的需求:内存按需分配,即使后续扩容也能通过动态调整实现(代价是可能产生内存碎片,但灵活性更高)。
  • 堆的手动 / 垃圾回收释放机制适配对象的长生命周期:当对象不再被任何变量引用时,由垃圾回收器(如 JS 的标记 - 清除算法)识别并释放内存,避免了栈内存 “生命周期固定” 的限制。
  • 引用传递节省内存:对象在栈中只存储堆内存的地址(指针),而非对象本身。多个变量可以通过共享指针引用同一个对象,避免重复存储大数据,节省内存空间。

4. 典型场景举例(以 JavaScript 为例)

function fn() {// 基本类型变量:存栈中let num = 100; // 栈中直接存值 100let str = "hello"; // 栈中存字符串值(短字符串通常优化为栈存储)// 对象:栈中存地址,堆中存实际数据let obj = { name: "foo" }; // 栈中存地址(如 0x123),堆中存 { name: "foo" }let arr = [1, 2, 3]; // 栈中存地址(如 0x456),堆中存数组数据
}fn(); 
// 函数执行结束后,栈帧释放:num、str、obj/arr 的地址被清除
// 堆中的对象/数组若不再被引用,后续由垃圾回收器释放
  • 当访问 num 时,直接从栈中读取值;
  • 当访问 obj.name 时,先从栈中读取 obj 存储的地址(如 0x123),再通过地址到堆中找到对象,读取 name 属性。

总结

栈和堆的分工本质是 **“效率” 与 “灵活性” 的权衡 **:

  • 栈用高效的自动管理和快速访问,适配大小固定、生命周期明确的基本类型变量;
  • 堆用动态分配和灵活的内存管理,适配大小可变、生命周期不确定的对象。

这种设计既保证了基本类型的快速读写和内存高效利用,又满足了对象动态扩展和复杂引用关系的需求,是编程语言内存管理的经典优化方案。

async/await 实现原理

async/await 是 JavaScript 中用于简化异步编程的语法糖,其底层基于 PromiseGenerator 函数 的机制实现,本质是对 Promise 异步模式的封装,让异步代码的写法更接近同步代码的直观性。

核心原理拆解

1. async 函数的本质

async 关键字用于声明一个异步函数,其核心特性是:

  • 返回值自动包装为 Promise:无论函数内部 return 什么值(非 Promise 类型),都会被自动包装成一个 resolved 状态的 Promise;如果函数内部抛出错误,则会被包装成 rejected 状态的 Promise。
  • 内部支持 await 关键字:只有在 async 函数内部才能使用 await,用于 “等待” 一个 Promise 完成。

2. await 的工作机制

await 关键字的作用是 “暂停” 当前 async 函数的执行,等待其后的 Promise 完成(resolved 或 rejected),然后恢复执行并获取结果。其底层逻辑可拆解为:

  • 将后续代码转为回调await 后面的表达式会被优先执行,若结果不是 Promise,则会被自动包装成 resolved 状态的 Promise(如 await 123 等价于 await Promise.resolve(123))。
  • 暂停与恢复:当遇到 await 时,JavaScript 引擎会暂停当前 async 函数的执行,将函数的后续代码(await 之后的部分)封装成一个 “回调函数”,并将这个回调函数注册到 await 对应的 Promise 的 then 方法中。
  • 控制权移交:暂停执行后,控制权会交还给调用者(如事件循环),直到 await 的 Promise 完成,再通过之前注册的回调函数恢复 async 函数的执行。

3. 与 Generator 函数的关联

async/await 的实现借鉴了 Generator 函数(带 * 的函数,配合 yield 使用)的 “暂停 / 恢复” 特性,但做了关键优化:

  • Generator 函数需要手动调用 next() 方法恢复执行,而 async 函数会自动根据 Promise 的状态恢复执行(无需手动干预)。
  • Generator 函数本身不与 Promise 强绑定,而 async/await 天然与 Promise 结合,更适合异步场景。

可以简单理解:async 函数相当于一个 “自动执行的 Generator 函数”,其内部通过类似 Generator 的状态机管理执行流程,而 await 相当于增强版的 yield(自动处理 Promise 状态)。

4. 事件循环中的执行时机

await 后面的代码会被放入 微任务队列(与 Promise.then 的回调一致),等待当前同步代码执行完毕后,再按照微任务队列的顺序执行。这也是为什么 await 能 “暂停” 却不阻塞整个线程的原因(JavaScript 是单线程,通过事件循环实现非阻塞)。

代码示例:模拟 async/await 原理

下面用 Generator 函数配合 Promise 模拟 async/await 的执行逻辑,帮助理解其底层实现:

// 模拟 async 函数(自动执行的 Generator)
function asyncToGenerator(generatorFunc) {return function() {const gen = generatorFunc.apply(this, arguments);// 返回一个 Promise,对应 async 函数的返回值return new Promise((resolve, reject) => {// 递归执行 Generator 的 next()function step(key, arg) {let info;try {info = gen[key](arg); // 执行 next() 或 throw()} catch (error) {reject(error); // 捕获 Generator 内部抛出的错误return;}const { value, done } = info;if (done) {// Generator 执行完毕,resolve 最终结果resolve(value);} else {// 将 yield 的结果包装为 Promise,等待其完成后继续执行Promise.resolve(value).then((val) => step('next', val), // 成功:继续执行 next()(err) => step('throw', err) // 失败:抛出错误并终止);}}// 启动 Generatorstep('next');});};
}// 用模拟的 async 函数实现一个异步场景
const fetchData = () => new Promise(resolve => {setTimeout(() => resolve('数据加载完成'), 1000);
});// 模拟 async/await 的使用
const mockAsyncFunc = asyncToGenerator(function* () {console.log('开始执行');const data = yield fetchData(); // 模拟 awaitconsole.log(data); // 1秒后输出:数据加载完成return '执行结束';
});// 调用模拟的 async 函数
mockAsyncFunc().then(result => {console.log(result); // 输出:执行结束
});

总结

async/await 的本质是:

  1. 以更友好的语法封装了 Promise 的异步逻辑;
  2. 借助 Generator 的 “暂停 / 恢复” 机制实现代码的顺序执行感;
  3. 通过事件循环的微任务队列管理异步回调的执行时机。

这种设计既保留了 Promise 的非阻塞特性,又解决了 Promise 链式调用(.then())可能导致的 “回调地狱” 问题,让异步代码更易读、易维护。

Generator 是如何判断是否可执行?

在 JavaScript 中,Generator(生成器) 的 “可执行性” 主要指其返回的生成器对象(Generator Object)是否还能继续执行并产生值。这种判断取决于生成器对象的内部状态执行上下文,具体逻辑如下:

一、Generator 的核心概念

首先明确两个关键角色:

  • 生成器函数(Generator Function):用 function* 定义的函数,调用后不直接执行函数体,而是返回一个生成器对象
  • 生成器对象(Generator Object):既是迭代器(Iterator)(有 next() 方法),也是可迭代对象(Iterable)(有 Symbol.iterator 方法)。它是实际 “可执行” 的主体,负责暂停 / 恢复生成器函数的执行。

二、生成器对象的 “可执行性” 判断依据

生成器对象的可执行性由其内部状态决定,状态变化与 next()throw()return() 等方法的调用紧密相关。主要状态包括:

  1. 初始状态(suspended start)

生成器函数被调用后,生成器对象刚创建时处于此状态:

  • 函数体尚未开始执行(停在函数第一行代码之前)。
  • 可执行:调用 next() 会启动执行,直到遇到第一个 yield 暂停。
  1. 暂停状态(suspended yield)

执行过程中遇到 yield 表达式时,生成器进入此状态:

  • 函数体暂停在 yield,并将 yield 后的值作为 next() 返回结果的 value
  • 可执行:再次调用 next() 会从暂停处继续执行,直到下一个 yield 或函数结束。
  1. 执行中状态(executing)

调用 next()throw() 等方法后,生成器正在执行函数体时的临时状态:

  • 此时无法再次调用 next()(会报错,因为 JavaScript 是单线程,同一生成器对象不能并发执行)。
  • 不可执行:需等待当前执行完成(进入暂停或关闭状态)后才能再次操作。
  1. 关闭状态(closed)

当生成器函数体正常执行完毕(遇到 return 或函数结尾),或被强制终止(调用 return()throw() 且异常未捕获)时,进入此状态:

  • 函数体已终止,无法再恢复执行。
  • 不可执行:再次调用 next() 会直接返回 { value: undefined, done: true }

三、如何判断生成器对象是否可执行?

JavaScript 标准并未直接暴露生成器对象的状态属性,但可通过以下方式间接判断:

1. 通过 next() 方法的返回值

生成器对象的 next() 方法返回一个对象 { value: any, done: boolean }

  • donefalse:说明生成器处于暂停状态可继续执行(再次调用 next() 会恢复)。
  • donetrue:说明生成器处于关闭状态不可再执行
function* gen() {yield 1;yield 2;
}const g = gen(); // 生成器对象(初始状态)console.log(g.next()); // { value: 1, done: false } → 可继续执行
console.log(g.next()); // { value: 2, done: false } → 可继续执行
console.log(g.next()); // { value: undefined, done: true } → 不可执行

2. 检查是否已被强制关闭

以下操作会导致生成器进入关闭状态,后续调用 next() 均返回 done: true

  • 生成器函数执行到结尾(无 return 时,隐式返回 undefined)。
  • 调用生成器对象的 return(value) 方法(强制返回指定值并关闭)。
  • 调用生成器对象的 throw(error) 方法,且函数体内未捕获该异常(异常抛出后关闭)。
function* gen() {yield 1;try {yield 2;} catch (e) {console.log(e); // 捕获异常,生成器不会关闭}yield 3; // 仍可执行
}const g = gen();
g.next(); // { value: 1, done: false }
g.throw(new Error("中断")); // 抛出异常被捕获
g.next(); // { value: 3, done: false } → 仍可执行
g.return("结束"); // 强制关闭,返回 { value: "结束", done: true }
g.next(); // { value: undefined, done: true } → 不可执行

四、总结

Generator 的 “可执行性” 本质是生成器对象是否处于可恢复执行的状态(初始状态或暂停状态)。判断方式主要是:

  • 调用 next() 方法,通过返回值的 done 属性判断:done: false 表示可继续执行,done: true 表示已关闭(不可执行)。
  • 注意:生成器对象一旦进入关闭状态,无论何种原因,都无法再恢复执行。

这种设计使得 Generator 能够实现 “暂停 - 恢复” 的协作式多任务,是 JavaScript 中处理异步逻辑(如早期的 co 库)和迭代场景的重要工具。

什么是协程?

在计算机科学中,协程(Coroutine) 是一种轻量级的程序组件,用于实现多任务之间的协作式调度。它允许程序在执行过程中主动暂停自身,将控制权转移给其他协程,之后再从暂停处恢复执行。这种 “暂停 - 恢复” 的特性让协程能够高效地实现并发逻辑,尤其适合处理 I/O 密集型任务或需要灵活调度的场景。

一、协程的核心特点

  1. 用户态调度
    协程的调度完全由程序自身控制(用户态),而非操作系统内核(内核态)。这意味着协程的创建、暂停、恢复等操作无需经过内核,开销远小于线程或进程。
  2. 协作式并发
    协程之间的调度是 “自愿” 的:一个协程必须主动调用暂停操作(如 yieldawait),才能将控制权交给其他协程。而线程是 “抢占式” 的,操作系统可强制剥夺线程的 CPU 使用权。
  3. 状态保存与恢复
    当协程暂停时,其执行状态(如局部变量、程序计数器位置等)会被保存;恢复时,这些状态会被重新加载,程序能从暂停处继续执行,就像从未中断过一样。
  4. 轻量级
    一个进程可以包含多个线程,一个线程可以运行多个协程。协程的内存占用极小(通常仅几 KB),支持创建数十万甚至数百万个协程,而线程的数量受限于系统资源(通常最多几千个)。

二、协程与进程、线程的区别

特性进程(Process)线程(Thread)协程(Coroutine)
调度者操作系统内核操作系统内核程序自身(用户态)
上下文切换开销大(涉及内存、寄存器等)中(比进程小,仍需内核参与)极小(仅保存少量状态)
资源占用高(独立内存空间)中(共享进程内存)极低(共享线程资源)
并发模型抢占式抢占式协作式
通信方式IPC(管道、socket 等)共享内存 / 锁直接共享线程内存

三、协程的典型应用场景

  1. I/O 密集型任务
    当程序需要频繁等待 I/O 操作(如网络请求、文件读写、数据库查询)时,协程可以在等待期间主动让出 CPU,让其他协程执行,避免资源浪费。例如:
    • 网络爬虫:发起多个请求后,在等待响应时切换到其他任务。
    • 服务器开发:处理大量客户端连接,每个连接用协程管理,等待数据时不阻塞。
  2. 复杂流程控制
    协程的 “暂停 - 恢复” 特性适合实现需要分步执行、状态保存的逻辑,例如:
    • 游戏中的角色 AI:每帧执行一部分逻辑,暂停后下一帧继续。
    • 工作流引擎:按步骤执行任务,支持中断后恢复。
  3. 替代回调地狱
    在异步编程中,嵌套回调会导致代码可读性差(“回调地狱”)。协程可以用同步的代码风格编写异步逻辑,例如 JavaScript 的 async/await(基于 Promise 和生成器实现,本质是协程的简化)。

四、不同语言中的协程实现

协程的概念在不同编程语言中有不同的实现和命名,核心思想一致但细节有差异:

  • Python:通过 asyncio 模块提供 async/await 语法,协程需运行在事件循环中。
  • JavaScriptGenerator 函数(function*)支持暂停 / 恢复,async/await 是协程的语法糖(基于 Promise)。
  • Gogoroutine 本质是轻量级线程,但调度器结合了协程的协作式特点,由 Go runtime 控制。
  • Lua:原生支持协程(coroutine 库),可显式创建、暂停和恢复。
  • C++20:引入 std::coroutine 标准库,支持协程语法。

五、总结

协程是一种用户态的轻量级并发组件,通过主动暂停和恢复实现多任务协作。它的优势在于:

  • 极低的上下文切换开销,适合大规模并发;
  • 用同步代码风格编写异步逻辑,提升可读性;
  • 高效利用 CPU,尤其优化 I/O 等待场景。

协程并不替代进程或线程,而是建立在它们之上的更细粒度的调度单位,是现代编程语言处理并发任务的重要工具。

闭包它具体的作用机制是怎么样的?闭包具体是什么东西?可以解释一下吗?

闭包是编程语言中一种重要的语法特性,本质上是一个函数及其捆绑的周边环境状态(lexical environment,词法环境)的组合。简单说,就是当一个函数能记住并访问它创建时所在的作用域,即使这个函数在其创建的作用域之外执行,这种现象就叫闭包。

闭包的核心特征

要形成闭包,需要满足三个条件:

  1. 函数嵌套:存在内部函数和外部函数(外层函数包含内层函数)。
  2. 变量引用:内部函数引用了外部函数中的变量(或参数)。
  3. 外部返回:外部函数将内部函数作为返回值返回,使得内部函数能在外部函数的作用域之外被调用。

作用机制:为什么内部函数能 “记住” 外部变量?

我们通过一个具体例子(以 JavaScript 为例)理解其工作机制:

// 外部函数
function outer() {// 外部函数的局部变量let count = 0;// 内部函数(闭包)function inner() {count++; // 引用外部函数的变量return count;}// 返回内部函数return inner;
}// 调用外部函数,得到内部函数(此时outer已经执行完毕)
const closureFunc = outer();// 在outer作用域之外调用内部函数
console.log(closureFunc()); // 输出1
console.log(closureFunc()); // 输出2
console.log(closureFunc()); // 输出3

机制拆解:

  1. 作用域创建:当outer()被调用时,会创建一个作用域(可以理解为一个 “变量存储容器”),里面包含count变量和inner函数。
  2. 内部函数引用外部变量inner函数引用了outer作用域中的count变量,此时inner会 “记住” 这个变量的位置。
  3. 外部函数执行完毕但作用域不销毁:通常,函数执行完毕后,其内部变量会被垃圾回收机制回收(释放内存)。但这里outer返回了inner函数,而inner还在引用outer中的count,因此 JavaScript 引擎会保留outer的作用域,不让它被销毁。
  4. 闭包函数在外部调用时的行为:当closureFunc()(即inner)在outer作用域之外被调用时,它依然能通过 “作用域链” 找到并操作outer中保留的count变量,因此每次调用都会让count递增。

闭包的本质

闭包的本质是函数对其词法作用域的 “捕获”。无论函数被带到哪里执行,它始终能访问自己创建时所在的作用域中的变量,就像带着一个 “背包”,里面装着它需要的外部变量。

闭包的典型用途

  1. 保存状态:如上面的例子,通过闭包可以在函数调用之间保留变量的状态(类似 “私有变量”)。

  2. 模块化与封装:创建私有变量和方法,避免全局污染。例如:

    function createCounter() {let count = 0; // 私有变量,外部无法直接访问return {increment: () => { count++; },getCount: () => count};
    }const counter = createCounter();
    counter.increment();
    console.log(counter.getCount()); // 1(只能通过暴露的方法访问)
    
  3. 延迟执行:在定时器、事件监听等场景中,闭包可以记住当时的变量状态。例如:

    for (var i = 0; i < 3; i++) {setTimeout(() => console.log(i), 100); // 若用var,会输出3个3
    }// 用闭包解决:
    for (var i = 0; i < 3; i++) {(function(j) { // 闭包捕获每次循环的jsetTimeout(() => console.log(j), 100); // 输出0、1、2})(i);
    }
    

总结

闭包就是 “带着环境的函数”,它让函数突破了 “执行完就销毁内部变量” 的限制,能够记住并操作创建时的周边变量。这种特性让它在状态保存、封装、延迟执行等场景中非常有用,但也需要注意过度使用可能导致的内存占用问题(因为闭包会保留作用域,不及时释放可能造成内存泄漏)。

作用域闭包

https://www.yuque.com/ergz/web/gug7gq

深浅复制

https://www.yuque.com/ergz/web/sl6tdp

构造函数、原型和原型链

https://www.yuque.com/ergz/web/fi0l9t

GC(垃圾回收)的两种类型

GC 是浏览器自动回收不再使用的内存的机制,核心是识别 “垃圾”(不可访问的对象)并释放其占用的内存。常见的两种类型如下:

  1. 标记 - 清除(Mark-and-Sweep)

    • 原理:

      1. 标记阶段:从根对象(如 window、全局变量)出发,遍历所有可访问的对象,标记为 “活跃”。
      2. 清除阶段:未被标记的对象被视为 “垃圾”,回收其内存。
    • 优点:实现简单,适用于大多数场景。

    • 缺点:

      • 清除后会产生内存碎片(空闲内存分散),可能导致后续大对象无法分配连续内存。
      • 执行时会暂停 JS 主线程(“全停顿”),大型应用可能出现卡顿。
  2. 标记 - 整理(Mark-and-Compact)

    • 原理:

      1. 先执行 “标记 - 清除” 的标记阶段,识别活跃对象。
      2. 整理阶段:将所有活跃对象向内存一端移动,集中排列,然后清除边界外的所有垃圾内存。
    • 优点:解决了内存碎片问题,内存分配更高效。

    • 缺点:整理阶段需要移动对象,耗时更长,“全停顿” 时间更久。

补充:现代浏览器的 GC 优化

为减少 “全停顿” 影响,现代浏览器(如 Chrome 的 V8 引擎)采用分代回收(结合上述两种类型):

  • 新生代(Young Generation):存放短期存活对象(如局部变量),采用 “复制算法”(快速回收,无碎片)。
  • 老生代(Old Generation):存放长期存活对象(如全局变量),采用 “标记 - 清除”+“标记 - 整理”(兼顾效率与碎片问题)。

通过分代策略,GC 可针对不同生命周期的对象优化回收频率和方式,平衡性能与内存利用率。

重绘(Repaint)与重排 / 回流(Reflow)的区别

当 DOM 或样式发生变化时,浏览器可能触发重绘或重排,两者的核心区别在于是否影响元素的几何信息:

特性重排(Reflow/Layout)重绘(Repaint)
触发原因元素几何信息改变(位置、尺寸、结构变化等)元素样式改变但几何信息不变(颜色、背景、透明度等)
示例- 改变 widthheightleftdisplay: none - 窗口大小调整、字体变化 - 新增 / 删除 DOM 元素- 改变 colorbackgroundborder-radius - 改变 visibility: hidden(元素仍占据空间)
性能消耗高(需重新计算布局,可能连锁影响父 / 子元素)中(无需重新布局,仅重绘像素)
关联关系重排一定会导致重绘(布局变了,样式也需重新绘制)重绘不一定导致重排(样式变了,布局可能不变)

EventLoop

事件循环是一个不停的从 宏任务队列/微任务队列中取出对应任务的**「循环函数」。在一定条件下,你可以将其类比成一个永不停歇的「永动机」。 它从宏/微任务队列中「取出」任务并将其「推送」「调用栈」**中被执行。

事件循环包含了四个重要的步骤:

  1. 「执行Script」:以**「同步的方式」**执行script里面的代码,直到调用栈为空才停下来。
    其实,在该阶段,JS还会进行一些预编译等操作。(例如,变量提升等)。
  2. 执行**「一个」宏任务:从宏任务队列中挑选「最老」**的任务并将其推入到调用栈中运行,直到调用栈为空。
  3. 执行**「所有」微任务:从微任务队列中挑选「最老」的任务并将其推入到调用栈中运行,直到调用栈为空。「但是,但是,但是」(转折来了),继续从微任务队列中挑选最老的任务并执行。直到「微任务队列为空」**。
  4. 「UI渲染」:渲染UI,然后,「跳到第二步」,继续从宏任务队列中挑选任务执行。(这步只适用浏览器环境,不适用Node环境)

promise原理

Promise 是 JavaScript 中处理异步操作的核心机制,它通过状态管理和回调函数解决了传统回调地狱的问题。下面从实现原理、关键特性到完整代码,深入解析 Promise 的工作机制。

一、核心概念与状态机

  1. 三种状态
  • pending(进行中):初始状态。
  • fulfilled(已成功):操作完成且成功。
  • rejected(已失败):操作完成但失败。

状态转换规则

pending → fulfilled(不可逆转)
pending → rejected(不可逆转)
  1. 基本结构

Promise 本质是一个状态机,包含:

  1. Promise.race

返回一个 Promise,其结果为第一个完成(成功或失败)的 Promise 的结果。

  1. Promise.resolve/reject

快速创建已解决或已拒绝的 Promise。

五、与 ES6 Promise 的差异

上述实现是简化版,原生 ES6 Promise 还有以下特性:

  1. **微任务队列**:使用 `queueMicrotask` 而非 `setTimeout`,执行优先级更高。2. **Promise 嵌套处理**:更严格的 `thenable` 对象检测。3. **更完善的错误处理**:如 `unhandledrejection` 事件。

总结

Promise 通过状态机和回调队列实现了异步操作的同步化表达,核心在于:

  1. 状态管理:确保结果的不可变性。
  2. 异步处理:保证回调执行的时机一致性。
  3. 链式调用:解决回调地狱问题。

理解 Promise 的实现原理,有助于更深入掌握 JavaScript 的异步编程模型,为使用更高级的异步特性(如 async/await)打下坚实基础。

前端必会:Promise 全解析,从原理到实战1. 从 “回调地狱” 到 Promise 在前端开发的异步编程领域,我们 - 掘金

Promise.allSettled和 Promise.any 得区别

Promise.allSettledPromise.any 是 ES2020 新增的两个 Promise 静态方法,它们的核心区别在于处理多个 Promise 时的触发条件、返回结果和适用场景

1. 触发条件不同

  • Promise.allSettled(promises)
    等待所有传入的 Promise 都 “settle”(即所有 Promise 都完成,无论成功 fulfilled 还是失败 rejected),才会返回一个成功的 Promise。
    不会因为任何一个 Promise 失败而提前结束,必须等全部完成。
  • Promise.any(promises)
    只要有一个传入的 Promise 成功 fulfilled,就会立即返回这个成功的结果(只返回第一个成功的值)。
    只有当所有传入的 Promise 都失败 rejected 时,才会返回一个失败的 Promise(包含所有错误信息)。

2. 返回结果不同

  • Promise.allSettled 的返回值
    始终返回一个成功的 Promise,其结果是一个数组,数组中的每个元素对应传入的每个 Promise 的 “结算信息”:
    • 对于成功的 Promise:{ status: "fulfilled", value: 成功值 }
    • 对于失败的 Promise:{ status: "rejected", reason: 错误原因 }
  • Promise.any 的返回值
    • 若有一个 Promise 成功:返回一个成功的 Promise,结果是 “第一个成功的 Promise 的值”。
    • 若所有 Promise 失败:返回一个失败的 Promise,结果是 AggregateError 实例(包含所有失败的错误信息)。

3. 代码示例对比

// 定义几个测试用的 Promise
const p1 = Promise.resolve("成功1");
const p2 = Promise.reject("失败2");
const p3 = Promise.resolve("成功3");
const p4 = Promise.reject("失败4");// 测试 Promise.allSettled
Promise.allSettled([p1, p2, p3, p4]).then(result => {console.log("allSettled 结果:", result);// 输出:// [//   { status: "fulfilled", value: "成功1" },//   { status: "rejected", reason: "失败2" },//   { status: "fulfilled", value: "成功3" },//   { status: "rejected", reason: "失败4" }// ]
});// 测试 Promise.any
Promise.any([p1, p2, p3, p4]).then(value => console.log("any 成功结果:", value), // 输出:"any 成功结果:成功1"(第一个成功的p1)error => console.log("any 失败结果:", error)
);// 测试所有 Promise 都失败的情况
Promise.any([p2, p4]).catch(error => {console.log("all rejected 时 any 的结果:", error); // 输出:AggregateError: All promises were rejectedconsole.log("错误列表:", error.errors); // 输出:["失败2", "失败4"]
});
  1. 适用场景不同
  • Promise.allSettled:适合需要知道所有异步操作的结果(无论成功失败) 的场景。
    例如:批量提交表单后,需要统计成功提交了多少、失败了多少,并分别处理。
  • Promise.any:适合只要有一个异步操作成功即可,无需等待所有完成的场景。
    例如:从多个镜像服务器加载同一资源,只要有一个服务器返回成功,就使用该资源(优先用最快成功的)。

总结表格

特性Promise.allSettledPromise.any
触发条件所有 Promise 都 settle(成功 / 失败)第一个成功的 Promise 出现(或所有失败)
返回状态始终成功(fulfilled)有成功则成功,全失败则失败(rejected)
结果内容包含所有 Promise 的状态和结果的数组第一个成功的值,或包含所有错误的 AggregateError
核心用途获取所有操作的完整结果快速获取第一个可用的成功结果

普通函数和箭头函数的区别

场景普通函数箭头函数
需要动态 this✅(如构造函数、对象方法)❌(this 无法绑定)
作为构造函数✅(可用 new 调用)❌(报错:不是构造函数)
需要 arguments 对象✅(内置 arguments❌(需用剩余参数 ...args
简洁的回调函数❌(语法冗余)✅(省略 functionreturn
定义生成器函数✅(function*❌(不能使用 yield
需要 prototype✅(默认有 prototype❌(无 prototype

理解这些差异后,可根据具体场景灵活选择函数类型,避免因 this 指向或语法限制导致的错误。

水平垂直居中

在前端开发中,实现元素的水平垂直居中是一个常见需求。根据不同的场景和元素类型,可以采用多种方法。以下是一些常用的实现方式:

  1. 行内元素 / 文本(单行)

适用于文本、链接等行内元素,通过 text-alignline-height 实现:

.parent {text-align: center;line-height: 200px; /* 等于容器高度 */height: 200px;
}
  1. 行内元素 / 文本(多行)

使用 Flexbox 或 Grid:

.parent {display: flex;justify-content: center;align-items: center;
}
  1. 块级元素(已知宽高)

使用绝对定位和负边距:

.parent {position: relative;
}
.child {position: absolute;top: 50%;left: 50%;width: 100px;height: 100px;margin-top: -50px; /* 高度的一半 */margin-left: -50px; /* 宽度的一半 */
}
  1. 块级元素(未知宽高)

使用绝对定位和 transform

.parent {position: relative;
}
.child {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);
}
  1. Flexbox(现代方案)

适用于大多数场景,简洁高效:

.parent {display: flex;justify-content: center;align-items: center;
}
  1. Grid(现代方案)

更强大的二维布局:

.parent {display: grid;place-items: center;
}
  1. 表格布局

使用 display: table-cell

.parent {display: table-cell;text-align: center;vertical-align: middle;
}
  1. 绝对定位 + 自适应

使用 top/left/bottom/rightmargin: auto

.parent {position: relative;
}
.child {position: absolute;top: 0;left: 0;right: 0;bottom: 0;margin: auto; /* 关键 */width: 200px; /* 需指定宽高 */height: 100px;
}

总结

  • 推荐方案:优先使用 Flexbox 或 Grid,代码简洁且兼容性良好。
  • 兼容性:若需支持旧版浏览器(如 IE9-),可选用绝对定位方案。
  • 响应式:使用 transformflex/grid 可更好地适应不同尺寸。

根据具体场景选择合适的方法,能有效提升开发效率和布局稳定性。

CSS

常见的选择器有哪些

  1. 基础选择器
    • 元素选择器:通过 HTML 标签名匹配元素,语法:标签名 { ... }(例:p { color: red; }
    • ID 选择器:通过元素的id属性匹配唯一元素,语法:#id值 { ... }(例:#header { width: 100%; }
    • 类选择器:通过元素的class属性匹配多个元素,语法:.类名 { ... }(例:.active { background: blue; }
    • 通配符选择器:匹配所有元素,语法:* { ... }(例:* { margin: 0; padding: 0; }
    • 属性选择器:通过元素属性匹配,常见形式:[attr](含 attr 属性)、[attr=value](attr 值为 value)、[attr^=value](attr 值以 value 开头)等(例:[type="text"] { border: 1px solid; }
  2. 组合选择器
    • 后代选择器:匹配父元素内的所有后代元素,语法:父选择器 后代选择器 { ... }(例:ul li { list-style: none; }
    • 子元素选择器:匹配父元素的直接子元素,语法:父选择器 > 子选择器 { ... }(例:div > p { font-size: 16px; }
    • 相邻兄弟选择器:匹配某元素后紧邻的同级元素,语法:元素 + 相邻元素 { ... }(例:h1 + p { margin-top: 10px; }
    • 通用兄弟选择器:匹配某元素后所有同级元素,语法:元素 ~ 兄弟元素 { ... }(例:h2 ~ span { color: gray; }
  3. 伪类选择器
    • 链接伪类::link(未访问链接)、:visited(已访问链接)
    • 交互伪类::hover(鼠标悬停)、:active(元素激活)、:focus(元素获焦)
    • 结构伪类::first-child(第一个子元素)、:last-child(最后一个子元素)、:nth-child(n)(第 n 个子元素)、:only-child(唯一子元素)
    • 状态伪类::checked(选中的表单元素)、:disabled(禁用的表单元素)
    • 否定伪类::not(选择器)(排除匹配选择器的元素,例::not(.active) { opacity: 0.5; }
  4. 伪元素选择器
    • ::before:在元素内容前插入内容
    • ::after:在元素内容后插入内容
    • ::first-letter:匹配文本首字母
    • ::first-line:匹配文本首行
    • ::selection:匹配用户选中的文本(例:::selection { background: yellow; }

选择器的权重

CSS 选择器的权重(Specificity)决定了样式的优先级,权重值越高,优先级越高。权重通常用 “(ID 数,类 / 伪类 / 属性数,元素 / 伪元素数)” 的形式表示,具体对应规则如下:

  1. 基础选择器
  • 元素选择器(如 pdiv):权重 (0, 0, 1)
  • ID 选择器(如 #header):权重 (1, 0, 0)(优先级最高的基础选择器)
  • 类选择器(如 .active):权重 (0, 1, 0)
  • 通配符选择器*):权重 (0, 0, 0)(无优先级,低于所有具体选择器)
  • 属性选择器(如 [type="text"][class^="btn"]):权重 (0, 1, 0)(与类选择器同级)
  1. 组合选择器

组合选择器的权重为组成它的所有单个选择器权重之和

  • 后代选择器(如 ul li):元素选择器 + 元素选择器(0, 0, 1+1) = (0, 0, 2)
  • 子元素选择器(如 div > p):元素 + 元素(0, 0, 2)
  • 相邻兄弟选择器(如 h1 + p):元素 + 元素(0, 0, 2)
  • 通用兄弟选择器(如 h2 ~ span):元素 + 元素(0, 0, 2)
  • 复杂组合示例(如 #nav .item > a):ID + 类 + 元素(1, 1, 1)

3. 伪类选择器

所有伪类选择器权重与类选择器一致:

  • :hover:first-child:checked:not(...) 等:权重 (0, 1, 0)
    示例:a:hover元素 + 伪类(0, 1, 1)

4. 伪元素选择器

所有伪元素选择器权重与元素选择器一致:

  • ::before::first-letter::selection 等:权重 (0, 0, 1)
    示例:p::first-line元素 + 伪元素(0, 0, 2)

补充说明

  • 权重计算只累加对应类别数量,不进位(如 11个类选择器 权重为 (0, 11, 0),仍低于 1个ID选择器 (1, 0, 0))。

  • 内联样式(style="...")权重为 (10, 0, 0)(即 1000),高于所有选择器;!important 优先级最高(强制覆盖,除非另一个 !important 权重更高)。

flex:1表示什么?

flex-grow: 1****flex-shrink: 1 flex-basis: 0%

margin 0 0 0 0表示什么等价margin 0 0 0 自动补充

margin: 0 0 0 0 的含义是:将元素四个方向的外边距都设置为 0,即元素与周围元素 / 容器在上下左右四个方向上都没有额外的间距。

margin 合并问题如何解决

  • 改变元素的 display 类型(如 inline-blockflex);

  • 触发 BFC(如 overflow: hiddendisplay: flex);

  • 用 border 或 padding 隔离 margin;

  • 添加隔离元素阻断相邻关系。

BFC

BFC(Block Formatting Context,块级格式化上下文)是 CSS 渲染页面时的一种布局模式,它决定了元素如何对其内容进行布局,以及与其他元素的关系。理解 BFC 是掌握 CSS 布局的关键之一,尤其是解决一些常见的布局问题。

一、基础概念

  1. 什么是 BFC?
    BFC 是页面上的一个独立渲染区域,区域内的元素布局不会影响区域外的元素。它有自己的布局规则,例如内部的子元素垂直排列,间距由 margin 决定,且不会与外部元素发生布局冲突。
  2. BFC 的创建条件
    满足以下任一条件即可触发 BFC:
    • 根元素 <html>
    • 浮动元素(float 不为 none)。
    • 绝对定位元素(position: absolute/fixed)。
    • display: inline-block/table-cell/flex/grid 等。
    • overflow 不为 visible 的块元素(如 overflow: hidden/auto)。

二、BFC 的核心作用

1. 解决外边距折叠(Margin Collapse)

  • 问题:相邻块级元素的上下外边距会折叠(合并为一个值)。

  • BFC 的解决方案:将其中一个元素包裹在 BFC 容器中,使其内外边距不再折叠。

    <div class="container"><div class="child"></div>
    </div>
    <div class="bfc-container" style="overflow: hidden;"><div class="child"></div>
    </div>
    

2. 清除浮动(Containing Floats)

  • 问题:父元素高度塌陷(子元素浮动后脱离文档流,父元素高度为 0)。

  • BFC 的解决方案:触发父元素的 BFC,使其包裹浮动子元素。

    .parent {overflow: hidden; /* 触发 BFC */
    }
    

3. 阻止元素被浮动覆盖

  • 问题:浮动元素会脱离文档流,覆盖后续的非浮动元素。

  • BFC 的解决方案:为被覆盖元素创建 BFC,使其与浮动元素隔离。

    .non-float-element {overflow: hidden; /* 触发 BFC,不再被浮动元素覆盖 */
    }
    

4. 自适应两栏/三栏布局

  • 问题:实现一侧固定宽度、另一侧自适应的布局。

  • BFC 的解决方案:利用 BFC 区域不与浮动元素重叠的特性。

    .left {float: left;width: 200px;
    }
    .right {overflow: hidden; /* 触发 BFC,自适应剩余宽度 */
    }
    

三、深度解析 BFC 的原理

  1. BFC 的布局规则
    • 内部的 Box 垂直排列,间距由 margin 决定。
    • BFC 的区域不会与浮动元素重叠。
    • BFC 内外的布局互相独立。
    • 计算 BFC 高度时,浮动元素也参与计算。
  2. BFC 与文档流的关系
    BFC 是页面文档流中的一个独立容器,其内部布局遵循普通流,但与外部的元素隔离。这种隔离性使得 BFC 可以避免外部浮动、外边距折叠等问题。
  3. BFC 的渲染机制
    浏览器在渲染时,会为每个 BFC 分配一个独立的布局上下文。这个上下文决定了元素如何定位、尺寸计算及与其他元素的关系。例如,BFC 容器会阻止浮动元素溢出到容器外部。

四、实际开发中的应用场景

  1. 避免浮动导致的布局错乱
    通过触发父元素的 BFC,确保父容器正确包含浮动子元素。
  2. 实现复杂布局
    结合浮动和 BFC 实现自适应布局,无需依赖现代布局方案(如 Flexbox/Grid)。
  3. 隔离第三方组件样式
    在组件外层创建 BFC,防止组件内外样式相互干扰(如外边距折叠)。

五、注意事项

  1. BFC 的副作用
    • 使用 overflow: hidden 可能导致内容被裁剪或出现滚动条。
    • 某些触发方式(如 float)会改变元素的显示模式。
  2. BFC 与现代布局方案的对比
    • Flexbox 和 Grid 提供了更直观的布局方式,但在某些场景(如清除浮动)中,BFC 仍是简单有效的方案。

六、总结

BFC 是 CSS 布局的底层机制之一,它通过隔离渲染区域解决外边距折叠、浮动塌陷等问题,同时为复杂布局提供基础支持。虽然现代布局方案(Flexbox/Grid)简化了部分场景,但理解 BFC 仍有助于开发者更彻底地掌握 CSS 布局原理,写出更健壮的代码。

flex属性

SS Flexbox 是现代前端开发中用于一维布局的核心技术,其属性分为 容器属性(控制整体布局)和 项目属性(控制子项行为)。以下是详细的分类和解释,从基础到深入:

一、Flex 容器属性

  1. display: flex | inline-flex

    • 作用:将元素定义为 Flex 容器,子元素成为 Flex 项目。
    • 区别
      • flex:容器表现为块级元素。
      • inline-flex:容器表现为行内元素,内部仍为 Flex 布局。
  2. flex-direction

    • 作用:定义主轴方向(项目的排列方向)。
      • row(默认):水平方向,从左到右。
      • row-reverse:水平方向,从右到左。
      • column:垂直方向,从上到下。
      • column-reverse:垂直方向,从下到上。
  3. flex-wrap

    • 作用:控制项目是否换行。
      • nowrap(默认):不换行,项目可能溢出。
      • wrap:换行,第一行在上方。
      • wrap-reverse:换行,第一行在下方。
  4. flex-flow

    • 作用flex-directionflex-wrap 的简写。
    • 语法flex-flow: <flex-direction> <flex-wrap>
    • 示例flex-flow: row wrap;
  5. justify-content

    • 作用:定义项目在主轴上的对齐方式。
      • flex-start(默认):左对齐。
      • flex-end:右对齐。
      • center:居中。
      • space-between:两端对齐,项目间隔相等。
      • space-around:项目两侧间隔相等。
      • space-evenly:所有间隔完全相等(包括边缘)。
  6. align-items

    • 作用:定义项目在交叉轴上的对齐方式。
      • stretch(默认):拉伸填满容器高度。
      • flex-start:顶部对齐。
      • flex-end:底部对齐。
      • center:垂直居中。
      • baseline:按基线对齐。
  7. align-content

    • 作用:多行项目在交叉轴上的对齐方式(需 flex-wrap: wrap)。
    • :与 justify-content 类似,如 flex-startcenterspace-between 等。

二、Flex 项目属性

  1. order

    • 作用:定义项目的排列顺序,数值越小越靠前。
    • :整数(默认 0)。
    • 示例order: -1; 使项目提前。
  2. flex-grow

    • 作用:定义项目的放大比例(当容器有剩余空间时)。
    • :非负整数(默认 0,不放大)。
    • 示例flex-grow: 2; 表示占据剩余空间的比例是其他项目的两倍。
  3. flex-shrink

    • 作用:定义项目的缩小比例(当容器空间不足时)。
    • :非负整数(默认 1,允许缩小)。
    • 示例flex-shrink: 0; 禁止项目缩小。
  4. flex-basis

    • 作用:定义项目在分配多余空间前的初始大小。
    • :长度(如 200px)或 auto(默认,基于内容计算)。
    • 注意:优先级高于 width
  5. flex

    • 作用flex-growflex-shrinkflex-basis 的简写。
    • 语法flex: <flex-grow> <flex-shrink> <flex-basis>
    • 常用简写
      • flex: 11 1 0(占满剩余空间)。
      • flex: auto1 1 auto(基于内容伸缩)。
      • flex: none0 0 auto(固定大小,不伸缩)。
  6. align-self

    • 作用:覆盖容器的 align-items,定义单个项目的交叉轴对齐方式。
    • auto(默认继承容器)、stretchflex-startflex-endcenterbaseline

三、深入理解

  1. 空间分配逻辑

    • 剩余空间:容器大小减去所有项目的 flex-basis 或固定大小后的空间。
    • 放大:按 flex-grow 比例分配剩余空间。
    • 缩小:按 flex-shrink 比例压缩超出容器的空间。
  2. flex-basiswidth 的关系

    • flex-direction: row 时,flex-basis 控制宽度;
    • flex-direction: column 时,flex-basis 控制高度。
    • 若同时设置 flex-basiswidthflex-basis 优先级更高。
  3. 负空间的压缩

    • flex-shrink: 0,项目不会缩小,可能导致溢出。

    • 实际压缩量计算公式:

      项目压缩量 = (负空间 * flex-shrink值) / 所有项目的 (flex-shrink值 * flex-basis值) 总和  
      
  4. 浏览器兼容性

    • 现代浏览器全面支持 Flexbox,但旧版浏览器(如 IE 10/11)需加前缀 -ms-
    • 使用 Autoprefixer 工具自动处理兼容性。

四、最佳实践

  1. 优先使用 flex 简写

    .item {flex: 1; /* 等价于 flex: 1 1 0 */`flex-grow`、`flex-shrink`、`flex-basis`
    }
    
  2. 固定侧边栏 + 自适应内容布局

    .sidebar { flex: 0 0 200px; }  /* 固定宽度 */
    .content { flex: 1; }          /* 占满剩余空间 */
    
  3. 等高布局

    .container {display: flex;align-items: stretch; /* 默认值,项目高度一致 */
    }
    
  4. 响应式导航栏

    .nav {display: flex;flex-wrap: wrap;      /* 小屏幕自动换行 */justify-content: space-between;
    }
    

总结

  • 核心思想:通过容器和项目的属性组合,实现灵活的空间分配和对齐。
  • 适用场景:一维布局(如导航栏、卡片列表、表单控件等)。
  • 进阶方向:结合 CSS Grid(二维布局)和媒体查询,构建复杂响应式设计。

掌握这些属性后,可以高效解决大多数布局问题,减少对浮动(float)和定位(position)的依赖。

网格布局

 grid-template-columns: repeat(3, 1fr); /* 三列等分 */

1fr 代表网格容器中 “剩余空间的一份”。当为网格的列(grid-template-columns)或行(grid-template-rows)设置 fr 单位时,浏览器会先扣除固定尺寸(如 px% 等)占用的空间,再将剩余的可用空间按 fr 的比例分配给对应轨道。

display属性

一、基础常用值

  1. block

    • 元素生成块级盒子,独占一行,可设置宽高、内外边距。
    • 典型元素:<div>, <p>, <h1>-<h6>
    • 示例:display: block;
  2. inline

    • 元素生成行内盒子,不独占一行,不可直接设置宽高,大小由内容决定。
    • 典型元素:<span>, <a>, <strong>
    • 示例:display: inline;
  3. inline-block

    • 混合特性:行内排列,但可设置宽高、内外边距。
    • 典型应用:导航按钮、图标与文字混排。
    • 示例:display: inline-block;
  4. none

    • 元素不渲染,完全从文档流中移除,不占据空间。
    • visibility: hidden 的区别:后者隐藏元素但保留空间。
    • 示例:display: none;

二、布局相关值

  1. flex

    • 启用弹性盒子布局(一维布局),子元素成为弹性项。
    • 控制属性:flex-direction, justify-content, align-items 等。
    • 示例:display: flex;
  2. grid

    • 启用网格布局(二维布局),子元素按网格行列排列。
    • 控制属性:grid-template-columns, grid-gap, grid-area 等。
    • 示例:display: grid;
  3. inline-flex / inline-grid

    • 行内版本的弹性或网格容器,外部表现为行内元素,内部按弹性/网格布局。
    • 示例:display: inline-flex;

三、传统布局模型

  1. table 系列

    • 模拟表格布局(逐渐被 Flex/Grid 替代):
      • table:行为类似 <table>
      • table-row:类似 <tr>
      • table-cell:类似 <td>,常用于垂直居中。
    • 示例:display: table-cell;
  2. list-item

    • 元素表现为列表项(如 <li>),生成标记(如圆点)。
    • 示例:display: list-item;

四、特殊场景值

  1. contents

    • 元素自身不生成盒子,子元素直接继承父级布局(类似“溶解”自身)。
    • 注意:可能影响可访问性(屏幕阅读器会忽略该元素)。
    • 示例:display: contents;
  2. flow-root

    • 创建块级格式化上下文(BFC),避免外边距合并或浮动塌陷。
    • 类似 overflow: hidden 但更语义化。
    • 示例:display: flow-root;
  3. run-in

    • 根据上下文决定表现为块级或行内元素(浏览器支持有限)。
    • 示例:display: run-in;

五、实验性或特定用途值

  1. ruby 系列

    • 用于东亚文字排版(如注音):
      • ruby:定义 ruby 容器。
      • ruby-text:定义注音文本。
    • 示例:display: ruby-text;
  2. subgrid

    • 网格布局的子网格(CSS Grid Level 2 特性,部分浏览器支持)。
    • 允许子网格继承父网格的轨道定义。
    • 示例:display: subgrid;

六、全局值

  • inherit:继承父元素的 display 值。
  • initial:重置为默认值(通常是 inline)。
  • unset:根据属性是否可继承,表现为 inheritinitial

总结与选择建议

  • 基础布局:优先使用 blockinlineinline-block
  • 现代布局:首选 flex(一维)和 grid(二维)。
  • 隐藏元素:用 none 完全移除,或用 opacity: 0 保留交互。
  • 兼容性:传统布局(如 table)适用于旧项目,新项目推荐 Flex/Grid。

通过灵活组合这些值,可以实现从简单到复杂的响应式布局设计。

postion属性

一、CSS定位基础

CSS的position属性定义了元素的定位模式,主要类型包括:

  1. static:默认值,元素在正常文档流中。
  2. relative:相对自身原始位置偏移,不脱离文档流。
  3. absolute:相对于最近的定位祖先元素(非static)绝对定位,脱离文档流。
  4. fixed:相对于**视口(viewport)**定位,脱离文档流,不受滚动影响。
  5. sticky:混合模式,滚动到阈值前表现为relative,之后表现为fixed

使用场景

  • absolute:悬浮菜单、弹窗。
  • fixed:导航栏、广告。
  • sticky:表格标题、侧边栏。

二、浏览器如何实现定位

浏览器的渲染引擎(如Blink、WebKit)通过以下流程处理定位:

  1. 布局阶段(Layout/Reflow)
  • static/relative:参与正常文档流布局,通过盒模型计算位置。
  • absolute/fixed:脱离文档流,单独计算位置,减少父容器回流影响。
  • sticky:动态切换布局模式,需持续监听滚动事件。
  1. 分层与合成(Composite)
  • 提升为合成层fixedsticky或使用transform的元素会被提升为独立的合成层(Composite Layer),由GPU处理。
  • GPU加速:合成层通过纹理上传到GPU,滚动时仅变换位置(translate),无需重绘(Repaint)。
  1. 滚动处理
  • 主线程与合成线程协作
    • 主线程:处理JavaScript、样式计算、布局。
    • 合成线程:管理图层合成,直接响应滚动事件,避免阻塞主线程。

三、操作系统底层协作

操作系统通过以下方式支持浏览器渲染:

  1. 图形接口与硬件加速
  • GPU资源管理:操作系统(如Windows的DirectX,macOS的Metal)提供图形API,浏览器通过它们调用GPU进行图层合成。
  • 垂直同步(VSync):操作系统协调GPU渲染帧率与显示器刷新率,避免画面撕裂。
  1. 进程调度
  • 多进程架构:浏览器(如Chrome)将渲染任务分配给渲染进程,合成任务由GPU进程处理,操作系统调度这些进程的CPU/GPU资源。
  • 输入事件处理:操作系统将滚动、触摸事件传递给浏览器进程,再路由到合成线程。
  1. 内存管理
  • 纹理内存:GPU显存由操作系统分配,浏览器将合成层纹理存储在显存中,提升渲染效率。

四、深度思考:性能优化与挑战

  1. 层爆炸(Layer Explosion)
    • 过度使用absolute/fixed可能导致过多合成层,占用GPU内存。
    • 优化:使用will-change谨慎提示浏览器分层。
  2. 滚动性能
    • sticky依赖主线程计算阈值,复杂页面可能卡顿。
    • 优化:使用position: fixed + JavaScript手动控制。
  3. 跨平台差异
    • 移动端视口受软键盘影响,fixed定位可能失效。
    • 解决方案:使用position: absolute + 动态视口高度(vh单位)。

五、总结

  • 前端定位:通过CSS定义元素的布局模式,核心是控制文档流与坐标系。
  • 浏览器实现:依赖渲染引擎的分层、合成机制,结合GPU加速提升性能。
  • 操作系统角色:提供图形接口、进程调度和硬件资源管理,确保高效渲染。

理解这一链条有助于开发高性能Web应用,尤其是在复杂交互和动画场景中。

CSS三大特性、盒子模型

实现响应式自适应方案

rem与px怎么做转换

rempx 是两种常用的长度单位,它们的转换关系与 根元素(html 标签)的字体大小 直接相关。核心转换公式

rem(Root EM)是相对单位,其基准值为 根元素(html)的 font-size。转换公式如下:

1rem = 根元素的 font-size(单位:px)
目标 px 值 = 目标 rem 值 × 根元素 font-size(px)
目标 rem 值 = 目标 px 值 ÷ 根元素 font-size(px

浏览器渲染原理

记忆法:HTML 解析与 DOM 树构建=>CSS 解析与 CSSOM 树构建=>渲染树(Render Tree)构建(合成)=>布局=>绘制=>合成(GPU加速渲染)=>GUI线程

浏览器渲染页面是一个将 HTML、CSS、JavaScript 转化为可视化界面的过程,核心分为以下 5 个阶段,且通常按顺序执行(现代浏览器会优化为流水线并行处理):

  1. HTML 解析与 DOM 树构建

    • 浏览器解析 HTML 标签,生成 DOM(文档对象模型)树,每个标签对应树中的一个节点,描述页面的结构和内容。
  2. CSS 解析与 CSSOM 树构建

    • 解析 CSS 样式(包括内联、嵌入、外部样式),生成 CSSOM(CSS 对象模型)树,记录每个元素的样式规则(如颜色、尺寸、位置等)。
  3. 渲染树(Render Tree)构建

    • 结合 DOM 树和 CSSOM 树,生成

      渲染树:

      • 只包含可见元素(忽略 display: none 的元素、<head> 等无视觉效果的节点)。
      • 每个节点包含 DOM 信息和对应的样式信息,用于后续布局和绘制。
  4. 布局(Layout/Reflow)

    • 根据渲染树计算每个元素的

      几何信息

      (位置、尺寸、大小等),例如:

      • 确定元素在页面中的坐标(如 top: 10px)、宽高(如 width: 200px)。
      • 父元素的布局会影响子元素(如 padding 会改变子元素位置)。
    • 此阶段是计算元素位置和大小的关键步骤,耗时与元素数量正相关。

  5. 绘制(Paint/Repaint)

    • 根据布局结果,将元素的样式(如颜色、背景、阴影)绘制到屏幕上,生成像素点。
    • 绘制可按 “层” 进行(如 z-index 较高的元素单独成层),现代浏览器通过 合成层(Compositing Layers) 优化性能。
  6. 合成(Compositing)

    • 将多个绘制层合并为最终屏幕图像,处理层间关系(如重叠、透明度),并交给 GPU 加速渲染(避免 CPU 瓶颈)。

html

Html和html5的区别

总结对比表

特性HTMLHTML5
语义化标签缺乏,依赖<div>丰富的语义标签(<header>等)
多媒体支持依赖插件(如 Flash)原生<video><audio>
本地存储Cookie(4KB 限制)localStorage/sessionStorage
表单增强基本输入类型丰富的输入类型和属性
绘图能力依赖图片或插件Canvas/SVG
离线支持AppCache/Service Worker
文档类型声明复杂<!DOCTYPE html>
浏览器兼容性全兼容现代浏览器(IE9 + 部分支持)

何时使用 HTML5?

  • 新项目或重构旧项目时
  • 无需支持 IE9 及以下版本
  • 需要使用多媒体、离线应用、地理定位等现代功能
  • 注重代码可维护性和 SEO 优化

HTML5 是现代 Web 开发的标准,提供了更强大、更简洁的功能,推荐优先使用。对于需要兼容旧浏览器的场景,可以结合 Polyfill 和降级方案使用。

盒子模型有哪些,特点

在前端开发中,盒子模型(Box Model)是布局的基础概念,它描述了元素在页面中所占空间的计算方式。以下是关于盒子模型的详细介绍:

1. 标准盒子模型(W3C 盒子模型)

特点

  • 内容区(content):元素实际显示的内容(文本、图片等),由widthheight属性定义。
  • 内边距(padding):内容区与边框之间的距离,会增加元素的整体尺寸。
  • 边框(border):围绕内容区和内边距的线条,宽度由border-width定义。
  • 外边距(margin):元素与其他元素之间的距离,不影响元素自身尺寸,但影响元素在页面中的位置。

宽度计算

总宽度 = width + padding-left + padding-right + border-left + border-right
总高度 = height + padding-top + padding-bottom + border-top + border-bottom

示例代码

.box {width: 200px;       /* 内容区宽度 */padding: 10px;      /* 内边距:上下左右各10px */border: 2px solid;  /* 边框:宽度2px */margin: 15px;       /* 外边距:上下左右各15px */
}/* 实际占用宽度 = 200 + 10*2 + 2*2 = 224px */

2. IE 盒子模型(怪异盒子模型)

特点

  • 内容区(content):元素的内容区宽度包含了paddingborder,但不包含margin
  • 内边距(padding)和边框(border):包含在widthheight属性内,不会额外增加元素尺寸。
  • 外边距(margin):与标准模型相同,不影响元素自身尺寸。

宽度计算

总宽度 = width(包含padding和border) + margin-left + margin-right
总高度 = height(包含padding和border) + margin-top + margin-bottom

示例代码

.box {width: 200px;       /* 内容区+内边距+边框的总宽度 */padding: 10px;      /* 内边距:上下左右各10px */border: 2px solid;  /* 边框:宽度2px */margin: 15px;       /* 外边距:上下左右各15px */
}/* 内容区实际宽度 = 200 - 10*2 - 2*2 = 176px */

3. 盒子模型切换(box-sizing 属性)

  • 标准模型(默认值)

    box-sizing: content-box;
    
  • IE 模型(怪异模型)

    box-sizing: border-box;
    

应用场景

  • 响应式布局:使用border-box可避免因内边距导致布局溢出。
  • 统一设计:所有元素设置box-sizing: border-box可简化尺寸计算。
/* 全局设置所有元素使用IE模型 */
* {box-sizing: border-box;
}

4. 其他盒子模型相关概念

4.1 行内元素盒子模型

  • 内联元素(如<span><a>

    • widthheight无效,由内容撑开。
    • paddingborder水平方向有效(影响布局),垂直方向不影响布局。
    • margin水平方向有效,垂直方向可能无效。

4.2 替换元素(如<img><input>

  • 有固有尺寸,widthheight可控制。
  • 盒子模型规则与块级元素相同。

4.3 外边距合并(Margin Collapsing)

  • 相邻块级元素的垂直外边距会合并为较大的一个。
  • 父子元素之间也可能发生外边距合并。

5. 对比总结

特性标准盒子模型 (content-box)IE 盒子模型 (border-box)
width包含内容仅内容区内容区 + 内边距 + 边框
尺寸计算width + padding + borderwidth(已包含)
布局控制需额外计算 padding 和 border更直观,不易溢出
应用场景默认值,适合简单布局响应式设计、复杂布局

6. 最佳实践

  1. 使用 border-box

    * {box-sizing: border-box;
    }
    

    简化尺寸计算,减少布局错误。

  2. 避免内外边距混用
    使用padding控制元素内部空间,margin控制元素间距离。

  3. 注意行内元素特性
    行内元素的paddingborder可能影响布局但不占空间。

理解盒子模型是掌握 CSS 布局的基础,通过合理使用box-sizing和控制内外边距,可以更高效地实现各种复杂布局。

前端不同页面之间怎么通信

对比与选择建议

方案数据量跨域实时性实现难度适用场景
URL 参数支持同步简单一次性数据传递
localStorage需刷新简单持久化数据共享
postMessage支持异步中等跨窗口(源要相同) /iframe 通信
WebSocket不限支持实时复杂实时双向通信
IndexedDB异步复杂大量数据存储与共享
Service Worker实时复杂离线消息和跨标签通信
Broadcast Channel实时简单同源页面间广播

注意事项

  1. 安全性:敏感数据需加密处理(如 JWT 签名)。
  2. 兼容性:IE 等旧浏览器可能不支持部分 API(如BroadcastChannel)。
  3. 性能:避免频繁操作localStorage,可能导致页面卡顿。
  4. 内存管理WebSocketService Worker需正确关闭连接,防止内存泄漏。

TS

infer得作用

infer 用于在条件类型中提取类型信息(类似变量声明)。

interface与type的区别

在 TypeScript 中,interfacetype 都用于描述类型,但它们在语法和功能上存在一些关键区别。以下是它们的主要差异和适用场景:

  1. 基本语法与定义方式
  • interface:仅用于定义对象类型或类的接口,语法更简洁,专注于结构描述。

    interface User {name: string;age: number;
    }// 描述函数类型
    interface SayHello {(name: string): string;
    }
    
  • type:可定义任何类型(对象、基本类型、联合类型、交叉类型等),适用范围更广。

    typescript

    // 对象类型
    type User = {name: string;age: number;
    };// 基本类型别名
    type ID = string | number;// 联合类型
    type Status = 'active' | 'inactive' | 'pending';// 交叉类型
    type A = { x: number };
    type B = { y: string };
    type C = A & B; // { x: number; y: string }
    
  1. 扩展(继承)方式
  • interface:通过 extends 关键字扩展,支持多继承。

    interface Animal {name: string;
    }interface Dog extends Animal {bark(): void;
    }// 多继承
    interface Pet extends Animal, Dog {owner: string;
    }
    
  • type:通过交叉类型(&)实现扩展,不支持 extends 关键字。

    type Animal = {name: string;
    };type Dog = Animal & {bark(): void;
    };
    
  1. 合并声明(Declaration Merging)
  • interface:支持同名接口自动合并,属性和方法会被合并为一个接口。

    interface User {name: string;
    }interface User {age: number;
    }// 合并后:{ name: string; age: number }
    const user: User = { name: 'Alice', age: 20 };
    
  • type:不支持合并,同名 type 会报错。

    type User = { name: string };
    type User = { age: number }; // 报错:标识符“User”重复
    
  1. 其他关键差异
特性interfacetype
支持的类型仅对象、函数、类接口所有类型(包括基本类型、联合类型等)
计算属性不支持支持(如 `type Key = ‘a’‘b’`)
与类的结合可通过 implements 约束类的结构也可通过 implements 使用,但不如 interface 直观
声明提升完全支持(类似变量声明提升)部分支持(取决于具体场景)
  1. 适用场景建议
  • 优先使用 interface
    • 定义对象的结构(如 API 响应、配置项)。
    • 需要继承或被继承的类型。
    • 希望支持声明合并(如扩展第三方库的类型)。
    • 描述类的接口(配合 implements)。
  • 优先使用 type
    • 定义基本类型别名(如 type ID = string)。
    • 定义联合类型(type Status = 'on' | 'off')或交叉类型。
    • 使用映射类型(type Readonly<T> = { readonly [P in keyof T]: T[P] })。
    • 定义元组类型(type Point = [number, number])。

总结

interface 更适合描述对象结构和接口继承,强调 “契约”;type 更灵活,可描述任何类型,适合复杂类型组合。实际开发中两者可以混用,但保持一致性更重要。

如何交叉

&

如何取值

根据不同场景选择合适的方式:

  • 从对象类型中挑选属性Pick

  • 从对象类型中排除属性Omit

  • 从联合类型中保留特定类型Extract

  • 从联合类型中排除特定类型Exclude

  • 从数组 / 元组中提取元素类型 → 索引访问(T[number]

  • 根据不同场景选择合适的方式:

    • 从对象类型中挑选属性Pick
    • 从对象类型中排除属性Omit
    • 从联合类型中保留特定类型Extract
    • 从联合类型中排除特定类型Exclude
    • 从数组 / 元组中提取元素类型 → 索引访问(T[number]

ts 高级

TypeScript(TS)的高级特性是提升代码类型安全性、可维护性和复用性的关键。以下是一些核心高级特性的解析与示例:

一、泛型(Generics)

泛型用于创建可复用的组件,支持多种类型而不丢失类型信息。

基本用法

// 泛型函数:接收任意类型并返回相同类型
function identity<T>(arg: T): T {return arg;
}// 自动推断类型
const num = identity(123); // 类型:number
const str = identity("hello"); // 类型:string// 显式指定类型
const bool = identity<boolean>(true); // 类型:boolean

泛型约束(限制泛型范围)

// 约束泛型必须有length属性
interface Lengthwise {length: number;
}function logLength<T extends Lengthwise>(arg: T): T {console.log(arg.length); // 合法,因为T被约束为有lengthreturn arg;
}logLength("abc"); // 正确(string有length)
logLength([1, 2, 3]); // 正确(array有length)
logLength(123); // 错误(number无length)

泛型接口 / 类

// 泛型接口
interface Container<T> {value: T;getValue: () => T;
}const stringContainer: Container<string> = {value: "test",getValue: () => "test",
};// 泛型类
class Queue<T> {private data: T[] = [];push(item: T) { this.data.push(item); }pop(): T | undefined { return this.data.shift(); }
}const numberQueue = new Queue<number>();
numberQueue.push(1);

二、高级类型

1. 联合类型(Union Types)

表示多个类型中的一个,用 | 分隔。

type ID = string | number;function printID(id: ID) {console.log(id);
}printID("abc123"); // 正确
printID(456); // 正确

2. 交叉类型(Intersection Types)

表示合并多个类型,用 & 分隔(需同时满足所有类型)。

type User = { name: string };
type Contact = { phone: string };// 同时拥有User和Contact的属性
type UserWithContact = User & Contact;const user: UserWithContact = {name: "Alice",phone: "123456"
};

3. 类型守卫(Type Guards)

缩小联合类型的范围,让 TS 更精确地推断类型。

type Dog = { type: "dog"; bark: () => void };
type Cat = { type: "cat"; meow: () => void };
type Pet = Dog | Cat;// 自定义类型守卫:判断是否为Dog
function isDog(pet: Pet): pet is Dog {return pet.type === "dog";
}function makeSound(pet: Pet) {if (isDog(pet)) {pet.bark(); // 正确,TS知道此时pet是Dog} else {pet.meow(); // 正确,TS知道此时pet是Cat}
}

三、映射类型(Mapped Types)

通过遍历已有类型的属性创建新类型(内置的如 PartialReadonly 等)。

内置映射类型

interface Todo {title: string;content: string;
}// Partial<T>:将所有属性变为可选
type PartialTodo = Partial<Todo>; 
// { title?: string; content?: string }// Readonly<T>:将所有属性变为只读
type ReadonlyTodo = Readonly<Todo>;
// { readonly title: string; readonly content: string }

自定义映射类型

// 将类型T的所有属性变为number类型
type ToNumber<T> = {[K in keyof T]: number; // K遍历T的所有属性(keyof T获取属性名联合类型)
};interface Data {a: string;b: boolean;
}type NumberData = ToNumber<Data>; 
// { a: number; b: number }

四、条件类型(Conditional Types)

类似三元表达式,根据条件返回不同类型,语法:T extends U ? X : Y

基本用法

// 判断T是否是Array类型
type IsArray<T> = T extends Array<any> ? "yes" : "no";type A = IsArray<number[]>; // "yes"
type B = IsArray<string>; // "no"

提取类型(配合 infer

infer 用于在条件类型中提取类型信息(类似变量声明)。

// 提取数组元素类型
type ElementType<T> = T extends Array<infer E> ? E : T;type Arr = number[];
type El = ElementType<Arr>; // number// 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;type Fn = () => string;
type FnReturn = ReturnType<Fn>; // string

五、装饰器(Decorators)

用于修改类、方法、属性的行为(实验性特性,需开启 experimentalDecorators 配置)。

类装饰器

// 装饰器工厂:返回一个装饰器函数
function logClass(prefix: string) {return function (target: any) {console.log(`${prefix}: 类被定义了`, target);};
}@logClass("INFO") // 应用装饰器
class User {name: string;constructor(name: string) {this.name = name;}
}
// 输出:"INFO: 类被定义了 [class User]"

方法装饰器

function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {const originalMethod = descriptor.value;// 重写方法descriptor.value = function (...args: any[]) {console.log(`调用方法 ${propertyKey},参数:`, args);const result = originalMethod.apply(this, args);console.log(`方法 ${propertyKey} 返回:`, result);return result;};
}class Calculator {@logMethodadd(a: number, b: number) {return a + b;}
}const calc = new Calculator();
calc.add(1, 2); 
// 输出:
// "调用方法 add,参数: [1, 2]"
// "方法 add 返回: 3"

六、模块增强(Module Augmentation)

扩展已有模块的类型定义(例如给第三方库添加类型)。

// 扩展内置Array类型
declare global {interface Array<T> {// 添加自定义方法sum(): number;}
}// 实现方法
Array.prototype.sum = function () {return this.reduce((acc, val) => acc + (val as number), 0);
};// 使用
const nums = [1, 2, 3];
console.log(nums.sum()); // 6

这些高级特性是 TS 的核心竞争力,合理使用可以大幅提升代码的健壮性和开发效率。实际开发中,建议结合具体场景(如封装通用组件用泛型、处理复杂类型用映射 / 条件类型)灵活运用。

相关内容

写下自己求职记录也给正在求职得一些建议-CSDN博客

从初中级如何迈入中高级-其实技术只是“入门卷”-CSDN博客


文章转载自:

http://mObKsHzS.fqtzn.cn
http://gvVJAzLi.fqtzn.cn
http://w4Wu7XlH.fqtzn.cn
http://FJfiPK0x.fqtzn.cn
http://7bYMaZfJ.fqtzn.cn
http://FzbMWgNl.fqtzn.cn
http://0hqFLocp.fqtzn.cn
http://0Hpj1t9b.fqtzn.cn
http://eRXY16hb.fqtzn.cn
http://rLOZKJ5H.fqtzn.cn
http://sEgzOdn4.fqtzn.cn
http://SpKhMNJV.fqtzn.cn
http://yVREooNO.fqtzn.cn
http://6yG1aZAf.fqtzn.cn
http://TZQzrOYn.fqtzn.cn
http://TRpQBDWZ.fqtzn.cn
http://dYmiiH1s.fqtzn.cn
http://x7oUVjZP.fqtzn.cn
http://UdY9lz7M.fqtzn.cn
http://pdpNkRK4.fqtzn.cn
http://BNK6tHF8.fqtzn.cn
http://1SPkPOmC.fqtzn.cn
http://TUgaqDci.fqtzn.cn
http://wS0TrDPx.fqtzn.cn
http://2aEaUJfy.fqtzn.cn
http://V2aBZPwQ.fqtzn.cn
http://DcO6rtxm.fqtzn.cn
http://bG3OblVm.fqtzn.cn
http://QofiQuzl.fqtzn.cn
http://6tHTUMFb.fqtzn.cn
http://www.dtcms.com/a/383532.html

相关文章:

  • 文件查找 find
  • LeetCode 2110.股票平滑下跌阶段的数目
  • 解锁仓储智能调度、运输路径优化、数据实时追踪,全功能降本提效的智慧物流开源了
  • FPGA学习篇——Verilog学习MUX的实现
  • hadoop单机伪分布环境配置
  • Vue3 响应式失效 debug:Proxy 陷阱导致数据更新异常的深度排查
  • el-table的隔行变色不影响row-class-name的背景色
  • 【深度学习新浪潮】游戏中的agents技术研发进展一览
  • Condor 安装
  • 类和对象 (中)
  • [数据结构——lesson10.2堆的应用以及TopK问题]
  • 可可图片编辑 HarmonyOS(6)水印效果
  • 机器学习(四):支持向量机
  • 给定一个有序的正数数组arr和一个正数range,如果可以自由选择arr中的数字,想累加得 到 1~range 范围上所有的数,返回arr最少还缺几个数。
  • 《C++ 容器适配器:stack、queue 与 priority_queue 的设计》
  • Java 黑马程序员学习笔记(进阶篇8)
  • 无需标注的视觉模型 dinov3 自监督学习ssl
  • 多语言编码Agent解决方案(2)-后端服务实现
  • STM32F103C8T6通过SPI协议驱动74HC595数码管完全指南:从硬件原理到级联实现
  • 【系列文章】Linux中的并发与竞争[05]-互斥量
  • 海岛奇兵声纳活动的数学解答
  • 大模型入门实践指南
  • CSS 编码规范
  • Redis框架详解
  • Redis----缓存策略和注意事项
  • Redis的大key问题
  • 微服务学习笔记25版
  • 地址映射表
  • AI Agent 软件工程关键技术综述
  • 命令行工具篇 | grep, findstr