前端面试-JS基础篇
- 1、JS基础类型和复杂类型
- 2、相等运算符`==` 和 严等运算符`===`的区别
- 3、var / let / const 定义的变量有什么区别?
- 4、ES6的新特性
- 5、讲述一下ES6中新增的数据结构
- 6、Map数据结构跟普通对象的区别?
- 7、箭头函数与普通函数的区别
- 8、JS中null和undefined的区别和判断方法
- 9、JavaScript判断变量类型的方法
- 10、typeof 和 instanceof 的区别
- 11、数组去重的方法
- 12、hasOwnProperty方法
- 13、伪数组和数组的区别
- 14、操作数组的常用方法
- 15、操作对象的常用方法
- 16、如何区分数组和对象
- 17、浅拷贝和深拷贝
- 18、使用JSON.parse(JSON.stringify())实现深拷贝会存在什么问题
- 19、JS原型链
- 20、闭包
- 21、 this的指向(普通函数 / 箭头函数)
- 22、call / apply / bind的作用和区别
- 23、JS继承的方法和优缺点
- 24、new关键字
- 25、说一说defer和async区别
- 26、`cookie、session、sessionStorage、localStorage`的区别
- 27、如何删除过期的localstorage数据
- 28、token 能放在cookie中吗
- 29、宏任务和微任务
- 30、setTimeout延迟时间设置为0,会在什么时候触发
- 31、JS如何实现多线程
- 32、const和readonly的区别
- 33、原生JS怎么通过JSON坐标文件绘制图形
- 34、JS的作用域和作用域链
- 35、介绍一下JS的事件代理(事件委托)
- 36、字符串转化为数组的方法
- 37、常见的可迭代对象有哪些
1、JS基础类型和复杂类型
- 7种基本类型:Number、String、Boolean、BigInt(长整型)、Symbol、Null、Undefined
- 3种引用类型:Object、Array、Function
- Symbol是ES6新出的一种数据类型,这种数据类型的特点就是没有重复的数据,可以作为object的键值
- BigInt是ES6新出的用于表示任意精度的整数的数据类型
2、相等运算符==和 严等运算符===的区别
- 相等运算符 会发生类型转换 值发生转换后相等返回true
- 严等运算符 不会进行类型转换 比较两个值类型相同且值相等返回true
3、var / let / const 定义的变量有什么区别?
- var 用于声明变量,作用域内存在变量提升,声明前访问会获得undefined, 允许重复声明,没有块级作用域,只有函数作用域/全局作用域
- let 用于声明变量, 有变量提升但存在暂时性死区, 在声明前访问会报错,不允许重复声明,有块级作用域
- const 用于声明常量, 有变量提升但存在暂时性死区,在声明前访问会报错,不允许重复声明,有块级作用域
4、ES6的新特性
- 变量声明:let 声明块级变量 const 声明常量
- 箭头函数:更简洁的函数定义方式,箭头函数没有自己的this, 不能当做构造函数使用, 只能从外部上下文去获取
- 模块字符串 使用反引号包裹内容
- 解构赋值 从数组或者对象中提取值并赋值给变量
- 类 使用class关键字声明类
- 模块导入和导出 使用export进行导出 , 使用import进行引入
- Promise 处理异步操作
- 新的数据结构: Set 、Map、WeakMap 和WeakSet
- Symbol类型
- Proxy代理 和 Reflect反射
- Proxy用于拦截和自定义对象的基本操作
- Reflect 提供了一系列的用于操作对象的方法
5、讲述一下ES6中新增的数据结构
Map数据结构
- Map数据结构的键是强引用的,可以是任何数据类型
- Map数据结构按照插入顺序保存键值对
- Map数据结构查找、插入和删除操作的时间复杂度为 O(1)
- Map数据结构是可迭代的,可以使用for…of和forEach方法进行遍历
Map常用操作方法
- map.set(key, value) 向 Map 中添加一个键值对
- map.get(key) 根据键获取对应的值
- map.has(key) 检查 Map 中是否存在指定的键
- map.delete(key) 删除 Map 中的指定键值对
- map.clear() 清空 Map 中的所有键值对
- map.size 获取 Map 中键值对的数量
WeakMap数据结构
- 只接受对象作为键
- 数据的键是弱引用的(当键对象没有其他引用时,条目会被自动回收)
- 不可遍历 无法通过for…of遍历 也无法通过keys(), values()和 entries()方法获取键值对
- 没有size属性获取键值对数量
WeakMap常用操作方法
- WeakMap.set(key, value) 向 WeakMap 中添加一个键值对
- WeakMap.get(key) 根据键获取对应的值
- WeakMap.has(key) 检查 WeakMap 中是否存在指定的键
- WeakMap.delete(key) 删除 WeakMap 中的指定键值对
- WeakMap.clear() 清空 WeakMap 中的所有键值对
Set数据结构
- Set数据结构的值是唯一的,不会重复
- Set数据结构的值是强引用的,可以是任何数据类型
- Set数据结构按照插入顺序保存值
- 查找、插入和删除操作的时间复杂度为 O(1)
- Set数据结构是可迭代的,可以使用for…of和forEach方法进行遍历
Set常用操作方法
- new Set() 创建一个新的 Set 对象
- set.add(value) 向 Set 中添加一个值
- set.has(value) 检查 Set 中是否存在指定的值
- set.delete(value) 删除 Set 中的指定值
- set.clear() 清空 Set 中的所有值
- set.size 获取 Set 中值的数量
WeakSet数据结构
- 只接受对象作为值
- 数据的值是弱引用的(当值对象没有其他引用时,条目会被自动回收)
- 不可遍历 无法通过for…of遍历 也无法通过keys(), values()和 entries()方法获取键值对
- 没有get方法获取值,也没有size属性获取值的数量
WeakSet常用操作方法
- new WeakSet() 创建一个新的 WeakSet 对象
- WeakSet.set(key, value) 向 WeakSet 中添加一个值
- WeakSet.has(key) 检查 WeakSet 中是否存在指定的值
- WeakSet.delete(key) 删除 WeakSet 中的指定值
- WeakSet.clear() 清空 WeakSet 中的所有值
6、Map数据结构跟普通对象的区别?
- 普通对象(Object)
- 普通对象的键只能是字符串或符号(Symbol), 使用其他类型会被自动转换为字符串
- 普通对象(Object)查找、插入和删除操作的时间复杂度为 O(1),但在某些情况下(如键非常多时)可能会退化为 O(n)
- 普通对象遍历顺序不保证,尤其是当键是数字时,可能会按照数字顺序而不是插入顺序遍历
- 使用 for…in 遍历时,会遍历对象的所有可枚举属性,包括原型链上继承的属性
- 普通对象默认包含一些原型链上的属性,可能会导致意外行为
- Map数据结构
- Map数据结构的键可以是任何类型,包括对象、函数、数组等
- Map查找、插入和删除操作的时间复杂度为 O(1),性能更稳定,内部实现优化,适合处理大量数据
- 使用for…of 或 Map.prototype.forEach 遍历时,会按照插入顺序遍历键值对
- Map数据结构没有默认的原型链,不包含任何默认属性,更适合用作数据结构,避免原型链污染
7、箭头函数与普通函数的区别
- 箭头函数相当于匿名函数,简化了函数定义
- 箭头函数简洁语法规则: 如果没有参数 , 直接写一个空括号即可 , 只有一个参数 , 可以省去参数的括号,有多个参数 , 用逗号分割, 如果函数体返回值是单条语句时, 可以省略大括号
- 箭头函数没有自己的this, 只能从函数外部去继承, 所以箭头函数中的this指向在它定义时已经确定了, 之后不会改变
- 箭头函数继承的this指向永远不会改变,call()、apply()、bind()等方法也不能改变箭头函数中this的指向
- 由于箭头函数没有自己的this,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用
- 箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
- 箭头函数没有prototype
- 注意事项:
- arguments 相对于一种 “老式可变参数桶”,用来在 function 关键字函数里获取全部实参
- prototype是函数自带属性,是函数独有的“公共仓库”——把方法/属性挂上去,所有实例都能共享,是实现 JavaScript 的“原型继承”核心机制
8、JS中null和undefined的区别和判断方法
- undefined:语义上表示没有值
- 应用场景
- 声明变量的初始状态值
- 调用函数时没有提供实参,函数形参就等于undefined
- 访问对象不存在的某个属性,获取到的是undefined
- 函数没有返回值,默认返回undefined
- 语义上表示无对象 变量当前不引用任何的对象地址
- 应用场景
- 声明对象类型的值 可以使用null初始化变量
undefined和null的区别
undefined通过typeof判断的类型是undefined, null通过typeof判断的类型是’object’null == undefined输出truenull === undefined输出false 严等运算符会比较数据类型,undefined和null是两种不同的数据类型
- 声明对象类型的值 可以使用null初始化变量
9、JavaScript判断变量类型的方法
typeof、instanceof、Object.prototype.toString.call()(对象原型链判断方法)、constructor (用于引用数据类型)
Object.prototype.toString 方法接受一个参数,并返回一个字符串,该字符串描述了参数类型
10、typeof 和 instanceof 的区别
- 使用 typeof 判断类型会返回对应的类型,typeof 可以判断基本数据类型,但无法区分数组和对象
- 使用方式如下
typeof 42 → 'number'typeof ' hello world ' → 'string'typeof [] → 'object'typeof null → 'object' - 使用instanceof判断类型会返回true/false,instanceof 不能判断基本数据类型, 但可以通过检测对象原型链来区分对象、函数、数组、日期、正则这些引用类型
- 使用方式如下
42 instanceof Numebr → false[] instanceof Array ' → true{} instanceof Object → true(() => {}) instanceof Function → true - 注意:null 是原型链的终点,没有内部原型([[Prototype]]),所以任何 instanceof 测试都会返回 false
11、数组去重的方法
- 双重循环 外层循环元素,内层循环找重复
functionunique(arr){varres=[];for(vari=0;i<arr.length;i++){varhas=false;for(varj=0;j<res.length;j++){// 内层扫描已存结果if(res[j]===arr[i]){has=true;break;}}if(!has)res.push(arr[i]);}returnres;}// 测试console.log(unique([3,5,3,2,5,8]));// [3, 5, 2, 8] - reduce累计遍历 + includes
constuniqueArray=originalArray.reduce((acc,current)=>{if(!acc.includes(current)){acc.push(current)}returnacc},[])// 第二个参数 [] 是累加器(accumulator)的初始值 - filter过滤遍历 + indexOf
constarr=[3,5,3,2,5,8];constunique=arr.filter((item,index)=>arr.indexOf(item)===index)// 等价于:当前元素第一次出现的位置 === 当前下标console.log(unique);// [3, 5, 2, 8] - new Set() 利用Set数据结构没有重复数据项
// 按 id 去重constdata=[{id:1,name:'Tom'},{id:2,name:'Jerry'},{id:1,name:'Tom Clone'}];constunique=Array.from(newMap(data.map(item=>[item.id,item])).values());console.log(unique);//[{id:1,name:'Tom Clone'},{id:2,name:"Jerry"}]
12、hasOwnProperty方法
- hasOwnProperty 是 JavaScript 中一个对象内置的方法,用于检查对象自身是否具有指定的属性,并且不是通过原型链继承的(hasOwnProperty方法只能检查对象自身的属性,不能检查对象的原型链上的属性)
- 使用场景:
- 检查属性是否存在:当你需要确定一个对象是否包含某个特定的属性时,使用 hasOwnProperty 方法可以避免属性名冲突或错误地访问未定义的属性。
- 遍历对象自身的属性:在遍历对象属性时,使用 hasOwnProperty 可以确保只遍历对象自身的属性,忽略从原型链继承的属性
- 使用例子:
obj.hasOwnProperty(prop)
obj:要检查的对象
prop:要检查的属性名 - 注意:hasOwnProperty 方法是对象的一个标准方法,继承自 Object.prototype,因此大多数对象都有这个方法。但是如果对象的原型链被修改,或者对象的原型链上没有 hasOwnProperty 方法,那么直接调用这个方法可能会引发错误。为了安全起见,可以使用 Object.prototype.hasOwnProperty.call(obj, prop) 的方式调用,这样可以确保对象的原型链被修改,也能正确调用 hasOwnProperty 方法
13、伪数组和数组的区别
- 伪数组的类型是Object,伪数组可以使用的length属性查看长度,也可以使用[index]索引去获取某个元素,但是不能使用数组的其他方法,也不能改变长度,遍历使用for in方法
- 数组的类型是Array,数组可以使用length属性查看长度,使用[index]索引去获取某个元素,遍历数组使用for of方法和其他操作数组的常用方法
- Array.from(arr) ` 可以将类数组对象(伪数组)或可迭代对象转为真正数组
14、操作数组的常用方法
在JavaScript中,提供了一系列与数组操作相关的方法,可以用来操纵数组,如添加、删除、查找、排序和遍历元素等。以下是一些常用的数组方法
- 添加元素
push() 在数组的末尾添加一个或多个元素,并返回新的长度
unshift() 在数组的开头添加一个或多个元素,并返回新的长度 - 删除元素
pop() 删除数组的最后一个元素,并返回那个元素
shift() 删除数组的第一个元素,并返回那个元素
splice() 通过删除、替换或添加新元素来改变数组的内容 - 查找元素
indexOf() 查找元素在数组中的索引
lastIndexOf() 从数组的末尾开始查找元素
find() 查找第一个满足条件的元素
findIndex() 查找第一个满足条件的元素的索引 - 排序和翻转
sort() 对数组元素进行排序
reverse() 反转数组中元素的顺序 - 遍历和过滤
for of 遍历可迭代对象(包括数组、Set、Map和字符串等)
forEach() 对数组的每个元素执行一次提供的函数
map() 创建一个新数组,其结果是该数组中的每个元素都调用一次提供的函数后的返回值
filter() 创建一个新数组,包含通过所提供函数实现的测试的所有元素
reduce() 对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值
reduceRight() 类似reduce(),但是从数组的末尾开始累加 - 归并数组
concat(): 合并两个或多个数组
slice(): 提取原数组的一部分,返回一个新数组 - 其它方法
join() 将数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串
toString() 将各种类型的值转换为字符串
split() 把字符串转换成数组
toLocaleString() 返回一个表示数组内容的本地化字符串
15、操作对象的常用方法
- Object.assign 用于合并多个源对象,同键覆盖
- 使用方式
Object.assign(obj, {name:'Tom'})
- 使用方式
- delete 运算符 彻底移除对象的键值,会触发代理钩子
- 使用方式
delete obj.age
- 使用方式
- 对象解构
- 使用方式
const {age, ...rest} = obj==> 生成age变量和不含 age属性的新对象rest
- 使用方式
- 对象遍历
| 方法 | 示例 | 说明 |
|---|---|---|
| for…in | for(const k in obj){...} | 遍历的键含原型链上的可枚举属性,需hasOwnProperty过滤 |
| Object.keys | Object.keys(obj).forEach(k=>...) | 只遍历自身可枚举键,不遍历原型链上的键 |
| Object.entries | Object.entries(obj).forEach(([k,v])=>...) | 同时拿到键和值 |
| Object.values | Object.values(obj).forEach(v=>...) | 只遍历值 |
- 对象元编程
| 方法 | 示例 | 说明 |
|---|---|---|
| 冻结 | Object.freeze(obj) | 让整个对象不可增删改(常量对象) |
| 密封 | Object.seal(obj) | 允许改值,禁止增删键 |
| 阻止扩展 | Object.preventExtensions(obj) | 只能改现有键,不能新增键 |
| 创建无原型链的对象 | Object.create(null) | 生成纯净字典,无toString等继承键 |
| 属性描述符 | Object.defineProperty(obj,'a',{value:1,writable:false}) | 精准控制读写、枚举、配置 |
| 合并深层 | structuredClone(obj) | ES2021,深拷贝对象(含循环引用) |
- 注意:
Object.create主要用于创建指定原型的新对象,传入的第一个参数用于指定原型,第二个参数用于定义新建对象的属性和配置属性特性(可修改、可删除和可枚举)
16、如何区分数组和对象
- Array.isArray()方法可以区分对象和数组
Array.isArray(obj)返回true时obj为数组, 返回false时obj为对象 - instanceof 操作符可以区分对象和数组,
obj instanceof Array语句返回true时obj为数组,返回false时obj为对象 - 通过Object.prototype.toString方法区分数组和对象
- 通过对象和数组的constructor属性可以区分数组和对象 (constructor属性可被修改)
obj.constructor === Object为ture时,obj为对象obj.constructor === Array为ture时,obj为数组 - 使用 hasOwnProperty 方法检查是否具有length长度属性,数组具有length属性返回true
对象没有length属性返回false
17、浅拷贝和深拷贝
- 浅拷贝: 只复制指向对象的指针,不复制对象本身,新旧变量指向同一对象地址,修改其中一个会影响另一个
- 深拷贝:递归复制对象的所有层级的属性,新旧变量指向不同对象地址,修改互不影响,相互独立
- 深拷贝主要通过
JSON.parse(JSON.stringify())或手动递归实现
18、使用JSON.parse(JSON.stringify())实现深拷贝会存在什么问题
- JSON.stringify 和 JSON.parse 是用于序列化和反序列化 JSON 数据的方法,也可以用来实现对象深拷贝
- 无法处理函数属性
- 无法处理循环引用
- 无法处理特殊类型(如Date、Map、Set等对象类型以及Symbol类型)
- JSON.stringify和JSON.parse会导致对象丢失原型链
- 性能问题 JSON.stringify和JSON.parse涉及字符串序列化和反序列化,处理大型数据时导致性能问题
- 数据精度丢失和类型错误, NaN,Infinity,-Infinity等特殊数据会被转换成null ,BigInt类型数据会被抛出TypeError错误
19、JS原型链
- prototype:所有的函数都有原型prototype属性,这个属性指向函数的原型对象
- proto,这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型
- constructor: 每个prototype原型都有一个constructor属性,指向该关联的构造函数
- 原型链:获取对象时,如果这个对象上本身没有这个属性时,它就会去它的原型__proto__上去找,如果还找不到,就去原型的原型上去找,直到找到
原型链最顶层(Object.prototype)为止,Object.prototype对象也有__proto__属性,值为null - null 是原型链的终点,没有内部原型([[Prototype]])
- Object.create(null) 可以创建一个没有原型链的纯净对象
20、闭包
- 闭包 是指一个函数内部定义了另一个函数,并内部函数使用了外部函数的参数或变量,函数执行时把内部函数返回到外部环境中就会形成闭包
- 闭包的影响
- 内存泄漏: 由于闭包会持续访问外部函数的变量,这可能导致内存泄漏。因此,当不再需要闭包时,应该适当地释放闭包所占用的资源。
- 性能问题: 闭包可能会对性能产生影响,因为它们需要维护额外的作用域链。在性能敏感的应用中,应谨慎使用闭包。
- 闭包的应用场景:数据隐藏、单例模式中封装私有变量、创建函数工厂、防抖和节流函数等
21、 this的指向(普通函数 / 箭头函数)
- this存在的场景有三种全局执行上下文和函数执行上下文和eval执行上下文,eval这种不讨论
- 在全局执行环境中无论是否在严格模式下,(在任何函数体外部)
this都指向全局对象 - 当在普通函数调用时,如果处于非严格模式之中将会是指向window,严格模式指向undefined
- 在对象之中调用,将会是指向当前的对象。通过new创建出来的对象将会是指向当前的新对象 如果是使用call、bind、apply修改了指向,将会指向绑定后的this
- 在箭头函数之中将会指向函数的外层执行上下文,当函数定义之后将会确定当前的this
22、call / apply / bind的作用和区别
- call、apply、bind的作用都是改变函数运行时的this指向
fn.call (newThis,params)在改变this指向时会立即执行fn函数, call函数的第一个参数是this的新指向,后面依次传入函数fn要用到的参数fn.apply (newThis,paramsArr)在改变this指向时立即执行fn函数, apply函数的第一个参数是this的新指向,第二个参数是fn要用到的参数数组fn.bind (newThis,params)在改变this指向的时候,返回一个已经改变执行上下文的fn函数,不会立即执行函数。bind函数的第一个参数是this的新指向,后面的参数可以直接传递,也可以按数组的形式传入。 bind方法只能改变一次fn函数的指向,后续再用bind更改无效
23、JS继承的方法和优缺点
- 原型链继承
- 核心原理:让子类原型指向父类的实例
- 优点:写法简单、容易理解
- 缺点:①引用类型的值会被所有实例共享;②在子类实例对象创建时,不能向父类传参
- 构造函数继承
- 核心原理:在子类中调用父类构造函数
- 优点:①避免了引用类型的值会被所有实例共享;②在子类实例对象创建时,可以向父类传参
- 缺点:无法继承父类原型上的方法
- 组合继承
- 核心原理:原型链 + 构造函数组合
- 优点:既可继承属性和方法,也可传参和避免共享问题
- 缺点:父类构造函数会被调用两次,一是创建子类原型对象时,二是子类构造函数内部
- Class 继承
- 语法糖 本质是原型继承
- 优点: 语法清晰易读 支持super关键字
- 缺点: 本质是原型继承,存在相同的原型链限制
24、new关键字
- new 操作符后面必须跟一个构造器,这个构造器可以是一个类(class),也可以是一个普通函数。
如果后面跟的是普通函数:该函数会作为构造函数执行
如果后面跟的是原始字面量(如 1、“abc”、true):会触发语法错误(SyntaxError),因为字面量不是合法的构造器。 - new 关键字会进行如下操作
- 创建了一个新对象
- this指向了这个对象
- 构造函数的属性和方法都赋给了这个对象
- 把新对象返回到全局
25、说一说defer和async区别
- 一般情况下,HTML文件都是按顺序执行的,浏览器在解析文档时遇到script标签就会停止解析HTML,先加载JS文件,加载完后立即执行,执行完毕后才能继续解析文档,这样会阻塞文档解析
- 在script标签中写入defer属性时,就会使JS文件异步加载,即HTML执行遇到script标签时,JS加载和文档解析同时进行,不阻塞文档解析,在JS加载和文档解析完成后再执行JS脚本
- 在script标签中写入async属性时,在HTML执行遇到script标签时,JS加载和文档解析同时进行,不阻塞文档解析,并且在JS加载完成后立即执行JS脚本
26、cookie、session、sessionStorage、localStorage的区别
| 方案 | 存储位置 | 生命周期 | 容量 | 优点 | 缺点 | 典型场景 |
|---|---|---|---|---|---|---|
| Cookie | 客户端浏览器 | 由服务器控制会话或持久级 | 4 KB | 同域跨页共享;安全策略丰富 | 体积过小;每次请求自动携带,增加流量与延迟 | 用户令牌(token)、会话跟踪、个性化设置 |
| Session | 服务器端 | 由服务器控制会话或持久级 | 取决于数据库 | 同域跨页共享;数据不落地浏览器,防 XSS 泄露 | 增加服务器内存/DB 负担;需解决分布式会话一致性问题 | 用户会话管理、登录状态、购物车 |
| SessionStorage | 客户端 | 会话级(标签页或 iframe 关闭即清) | 5-10 MB | 大容量;API 简洁;不受请求携带影响 | 仅限当前标签页/iframe;无法跨窗口共享 | 表单过滤信息、分页状态、页面临时数据 |
| LocalStorage | 客户端 | 持久级(手动清理) | 5-10 MB | 大容量;持久化;同域跨页共享 | 永不过期可能累积垃圾;XSS 可被读取;不随请求发送 | 主题皮肤、离线缓存、JWT 刷新令牌、用户偏好 |
27、如何删除过期的localstorage数据
- 惰性删除:惰性删除是指获取数据的时候,拿到存储的时间和当前时间做对比,如果超过过期时间就清除Cookie。
- 定时删除:每隔一段时间执行一次删除操作,并通过限制删除操作执行的次数和频率,来减少删除操作对CPU的长期占用。 LocalStorage清空应用场景:token存储在LocalStorage中,要清空
28、token 能放在cookie中吗
- token一般是用来判断用户是否登录的,它内部包含的信息有:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
- token一般存储在sessionStorage/localStorage里面,不建议存放到
Cookie中,容易产生CSRF问题。token的出现就是为了解决用户登录后的鉴权问题,如果采用cookie+session的鉴权方式,则无法有效地防止CSRF攻击。同时,如果服务端采用负载均衡策略进行分布式架构,session也会存在一致性问题,需要额外的开销维护session一致性 token可以存放在Cookie中,由后端来判断token是否过期,无需前端来判断,所以token存储在cookie中无需设置cookie的过期时间,如果token失效,就让后端在接口中返回固定的状态表示token失效,需要重新登录,然后重新设置cookie中的token- token认证流程
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端签发一个 token ,并把它发送给客户端
- 客户端接收 token 以后会把它存储起来,比如放在 cookie 里或者 localStorage 里
- 客户端每次发送请求时都需要带着服务端签发的 token(把 token 放到 HTTP 的 Header 里)
- 服务端收到请求后,需要验证请求里带有的 token ,如验证成功则返回对应的数据
29、宏任务和微任务
- 宏任务(Macrotask)
宏任务是指在当前执行栈清空后,由事件循环触发依次处理的宏任务队列的任务。常见宏任务包括:
Promise 的状态改变(如从 pending 到 fulfilled 或 rejected)属于宏任务
setTimeout
setInterval
用户交互事件(如点击、键盘输入等) - 微任务
微任务是指在当前执行栈清空后,在下一个宏任务之前优先执行的微任务队列的任务。常见微任务包括:
Promise.then/catch/finally方法注册的回调函数
MutationObserver(DOM 变化观察器)在当前宏任务同步代码执行完毕后,会一次性执行所有收集到变化的回调函数
30、setTimeout延迟时间设置为0,会在什么时候触发
- 在JS文件执行时,首先会形成一个全局任务(主任务)进入任务队列中等待执行。在全局任务执行时遇到延迟时间为0的定时器,会把定时器交给计时线程处理,因为延迟时间为0,计时线程会立刻执行定时器,把定时器内部的回调函数包装成任务放进任务队列等待执行,当执行栈为空,并且没有优先级更高的其他任务时,就会把回调函数押入执行栈执行
31、JS如何实现多线程
- JavaScript本身是单线程的,但是可以通过实现多线程来提高性能和用户体验。多线程允许JavaScript在等待用户交互或网络请求时,执行其他任务,从而提高页面加载速度和响应速度。
- JavaScript实现多线程的方式
JavaScript有多种实现多线程的方式,包括Web Workers、SharedArrayBuffer、WebAssembly等。其中,Web Workers允许在后台线程中运行JavaScript代码,而SharedArrayBuffer和BufferSource API则允许在多个线程之间共享数据。 - 使用Web Workers实现多线程的方式
使用Web Workers实现多线程需要创建一个新的worker线程,并将需要执行的代码作为字符串传递给worker。worker线程可以访问全局对象messageChannel的postMessage方法来发送消息,主线程可以使用onmessage方法来接收消息并执行相应的操作 - 如何保证多线程安全
多线程存在的安全问题主要包括数据竞争和死锁等。为了解决这些问题,需要使用同步机制,如使用Promise、async/await等异步编程方式,或者使用事件循环、共享内存等机制来保证数据的一致性和安全性。 - 多线程应用场景
在实际应用中,多线程可以用于提高页面加载速度和响应速度,例如在电商网站中,可以使用Web Workers在后台线程中加载和处理商品图片,从而提高页面加载速度和用户体验。同时,多个并发请求也可以使用Web Workers并行处理,提高系统性能和响应速度
32、const和readonly的区别
- 相同点
- const和readonly都可以用于限制变量修改
- 不同点
- const是编译期常量,定义时必须初始化且值(地址)不可变(引用类型的const常量可以修改内部的属性),任何修改都会导致编译错误,主要用于修饰常量数据和类的静态字段等
- readonly是运行期只读,在运行时确定值,赋值后就不能再修改,主要用于修饰类的实例字段和静态字段,引用类型的readonly字段只能保证其地址不会发生改变,但可以修改对象内部属性
33、原生JS怎么通过JSON坐标文件绘制图形
使用HTML5的Canvas API 绘制图形的具体流程如下
1、准备画布,在HTML中创建一个canvas元素
2、使用fetch API异步加载JSON文件
3、解析绘制 在获取到数据后 使用Canvas 2D上下文getContext(‘2d’)的绘图方法
4、为加载和解析过程添加错误处理
34、JS的作用域和作用域链
- 作用域可以分为全局作用域(浏览器/node环境)、函数作用域和块级作用域
- 全局作用域:在所有函数、代码块之外定义的变量/函数,整个程序都能访问
- 函数作用域:在函数内部定义的变量/函数,仅该函数内部可以访问
- 由
{}(如if、for、while和let、const声明)创建的作用域,变量仅在块内有效
- 作用域链
访问变量时,JS会先在当前作用域查找,找不到就会向上级作用域查找,直到全局作用域,都找不到则报错
35、介绍一下JS的事件代理(事件委托)
- 事件代理是利用 JS 事件冒泡机制,把子元素事件监听器统一绑定到父元素,由父元素代理内部所有子元素的事件监听。减少 DOM 操作、提升性能、支持动态元素
- 优点:
- 减少子元素监听器的内存占用和通过JS代码对DOM的操作
- 支持动态新增、删除子元素,无需重新绑定子元素的事件监听器
- 集中管理,易于维护
<ul id="list"><li data-id="1">苹果</li><li data-id="2">橙子</li><li data-id="3">香蕉</li></ul><script>// 只在父元素绑定一次document.getElementById('list').addEventListener('click',function(e){// 通过>constid=e.target.dataset.idif(id)console.log('选中水果',id)})</script>36、字符串转化为数组的方法
split(separator, limit)split方法可以按指定分隔符分割字符串,返回数组- separator 必选参数,可传入字符串或正则表达式,如 ‘ ’可将字符串的每个字符拆分
- limit 可选参数 限制返回数组的最大长度
- 示例:
'hello'.split(' ') ==> ['h', 'e', 'l', 'l', 'o']
Array.from(str, mapFn, thisArg)将类数组对象或可迭代对象转为真正数组- str 必选参数,可传入可迭代对象或类数组对象
- mapFn 可选参数 类似于Array.prototype.map对每个元素进行处理后再放入新数组
- thisArg 可选参数第二个参数映射函数mapFn的this指向仅在mapFn为非箭头函数时有效
- 示例:
Array.from('test') ==> ['t', 'e', 's', 't']
- 扩展运算符
[...str]: 可以简洁地将字符串拆分为单个字符的数组,对Unicode字符兼容性好- 示例: […‘abc’] ==> [‘a’, ‘b’, ‘c’]
37、常见的可迭代对象有哪些
- 常见的可迭代对象主要包括原生内置对象和自定义实现了迭代器接口的对象(部署了Symbol.iterator方法的对象),常见的可迭代对象如下:
- 原生字符串
- 集合对象(Set/Map)Set数据迭代其值,Map迭代其键值对
- 类数组对象 如NodeList(DOM节点集合)、HTMLCollection(存储页面中一组特定的HTML元素)、有length属性的普通对象
- 数组(Array)
- 生成器对象(由function*生成的对象,自动实现迭代器接口),生成器示例如下
function*generateId(){letid=1while(true){// 理论上无限循环,但在生成器函数中可以暂停循环执行,直到下一次调用yieldid++;//暂停并返回当前ID}}constidGenerator=generateId()//生成器对象console.log(idGenerator.next().value)// 1console.log(idGenerator.next().value)// 2