JS今日份练习题
今日份练习题
1、异步执行顺序
1 |
|
? 什么是setTimeout
Web API,window的方法,设置一个定时器,定时器到期,就会执行一个函数或者指定的代码片段。
setTimeout(function, delay, arg1, arg2, …);
如果省略delay或者delay为0,意味着立即执行(被放入事件队列,在下一个事件循环执行)
(实际延长可能比预期长,因为同步代码优先,事件循环处理事件队列)
? 宏任务与微任务
代码执行顺序:同步代码 - 微任务队列 - 宏任务队列
微任务:Promise的回调、queueMicrotask()专门把回调放入微任务队列、MutationObserver监听DOM变化
宏任务:setTimeout、setInterval、I/O 操作、script、requestAnimationFrame、用户交互事件
解答
答案是1 4 3 2。
同步执行阶段
- 执行
console.log(1)
,输出 1。- 执行
setTimeout
,将回调函数放入宏任务队列(虽然延时为 0,但仍属于宏任务)。- 执行
Promise.resolve().then(...)
,将回调函数放入微任务队列。- 执行
console.log(4)
,输出 4。微任务执行阶段
- 同步代码执行完毕后,立即执行微任务队列中的任务,输出 3。
宏任务执行阶段
- 最后执行宏任务队列中的
setTimeout
回调,输出 2。
2、变量提升
1 |
|
? 变量提升 hoisting
var 变量声明会被提升到函数作用域 / 全局作用域的顶部。
变量声明会提升,赋值不会被提升,默认为undefined。
? 原理
当 JavaScript 引擎解析代码时,它会先扫描代码中的变量声明和函数声明,并将它们提升到其作用域的顶部。
1、变量声明的提升:
- var : 变量声明会被提升到作用域的顶部(函数作用域/全局作用域),被初始化为undefined。赋值操作保留在原位置。
- let/const:变量声明会被提升到作用域的顶部(块级作用域),不会被初始化,处于暂时性死区,访问会报错。
2、函数声明的提升:
函数声明,完全提升,函数体也会被提升到作用域的顶部。可以在声明之前调用它。
1
2
3
4console.log(myFunc()); // 输出 "Hello, World!",因为函数声明被完全提升
function myFunc() {
return "Hello, World!";
}函数表达式,只有变量声明会被提升,函数表达式的赋值操作不会提升。
1
2
3
4
5console.log(myFunc()); // 报错:TypeError: myFunc is not a function
console.log(myFunc); // undefined
var myFunc = function() {
return "Hello, World!";
};
? JS引擎是什么
1、解析代码:把JS代码解析为抽象语法树(AST)
2、编译代码:AST转换为可执行的字节码或者机器码
3、执行代码:运行编译后的代码
4、管理内存、垃圾回收
5、优化代码(JIT技术)
6、提供运行时环境(支持JS内置对象、API和事件循环机制)
常见的有V8(Chrome 浏览器、Node.js)
现代 JavaScript 引擎通过多种技术优化性能:
- 即时编译(JIT):动态编译代码,优化热点代码。
- 内联缓存:缓存函数调用的结果,减少重复计算。
- 增量垃圾回收:避免长时间的垃圾回收暂停,提高响应速度。
- 代码缓存:缓存编译后的代码,减少重复编译。
解答
答案是undefined 20
3、闭包与作用域
1 |
|
? 执行要点
1、createIncrement函数执行:message是字符串模版,定义时”Count is 0”
2、外部调用:count经过两次调用,值变为2
3、log函数执行:输出定义好的message(message在createIncrement函数执行时就已经确定)
? 闭包的作用
increment、log函数都访问了createIncrement函数作用域的变量
这些变量被闭包捕获,即使createIncrement函数已经执行完毕,但是仍然可以访问这些变量
总结:闭包允许函数访问其创建时所在的作用域链中的变量。
解答
答案是0
4、for循环与var的作用域
1 |
|
?怎么会
闭包捕获的是变量的引用,因此回调函数访问的是变量的当前值。
所有通过定时器注册的回调函数在执行时,访问的是同一个作用域中的i,而i在循环结束后已经是3
? 怎么解决
1、使用let定义变量,每次循环创建新的i
属于单独的块级作用域
2、使用立即执行函数IIFE,创建独立的作用域,把i
传给定时器
解答
答案是3 3 3
5、反转字符串
1 |
|
解答
1、字符串没有reverse方法,数组有reverse方法。
2、因此,考虑使用split方法将字符串转化为数组。
3、再将数组进行反转,使用join方法连接为字符串。
1 |
|
6、数组去重
1 |
|
解答
1、去重,首先考虑使用Set数据结构,因为Set存储唯一的值。
Set的特性:唯一性、但具有无序性,可动态添加元素,有以下方法:add/delete/clear/has/size;存储任意类型
2、把Set类型转换为数组:
- 使用扩展运算符(可迭代对象)
- 使用Array.from方法(类数组对象、可迭代对象)
- 使用for of循环手动遍历Set并把值添加到数组
1 |
|
7、深拷贝
1 |
|
? 深拷贝和浅拷贝是什么意思
浅拷贝:只复制对象的第一层属性,不递归复制嵌套的对象。如果属性值是引用类型,新对象和原对象会指向同一个内存地址。
1、扩展运算符 …
2、
Object.assign()
3、数组的
slice()
或concat()
深拷贝:递归复制所有层级的属性,确保新对象和原对象完全独立,修改新对象不会影响原对象。
1、
JSON.parse(JSON.stringify())
,但是不支持函数、特殊对象、undefined2、手写递归
3、库loadsh
? 什么是递归
允许函数调用自身,将复杂问题分解为更小的子问题来解决
解答
终止条件:如果是null或者不是对象类型都直接返回
考虑情况:
特殊对象问题:Date或者RegExp对象,需要实例化后返回
循环引用问题:使用WeakMap记录
接收初始化问题:要考虑是数组还是普通对象
1 |
|
8、防抖函数
1 |
|
? 思路是
1、防抖函数的目标是:在短时间内频繁触发目标函数,但只在最后一次触发事件等待延迟时间delay后执行。
2、防抖函数的作用是:把目标函数做防抖处理,返回一个新函数,内部封装了防抖逻辑,用户只需要调用新函数并且正常传参。
3、防抖函数内部需要先定义一个变量timer,存储定时器的引用。返回新函数与timer构成了闭包,使得timer被共享和更新。
4、新函数需要接收参数,并且在每次触发事件前,清除定时器,使得频繁的触发情况下不会生效。设置指定延迟的定时器,显示指定this调用目标函数,保持上下文的正确绑定。
解答
1 |
|