一起养成写作习惯!这是我参与「日新计划 4 月更文挑战」的第7天,点击查看活动详情。
前言
今天在网上看见这样这样一段代码,如下:
function fn() {
for (let i = 0; i < 4; i++) {
var timer = setTimeout(function(i) {
console.log(i);
clearTimeout(timer);
}, 10, i);
}
}
fn();
请问这段代码执行会输出什么?为什么?
结果是什么?
大家可以先在大脑里过一下这段代码,试着说出结果是什么?
我们在浏览器运字符间距行一下这段代码,结果是0、1、2。你答对了吗。
为什么这段代码输出的是 0、1、2,而不是0、1、2、3。
函数执行
问题就出在 timer 前面这个 var 上。
var 定义的变量,是不会生成“块级作用域”的。按照之前文章的思路(# 前端基石:函数的底层执行机制),我们来一步一步执行这段代码。
-
函数创建
- 一个函数的创建会在 Heap 堆内存中开辟一块空间来存储字符串是什么意思函数。对象在创建会在堆内存中存储对象的键值对,而函数在堆内存中会存储三部分东西:
- 作用域:[[scope]](在这里是window)
- 函数字符串
- 键值对
- 一个函数的创建会在 Heap 堆内存中开辟一块空间来存储字符串是什么意思函数。对象在创建会在堆内存中存储对象的键值对,而函数在堆内存中会存储三部分东西:
-
变量提升,fn 提升到最前面
-
代码执行(函数执变量的定义行)
-
函数执行
- 创建自己的私有上下文,函字符间距怎么加宽数一旦执变量名的命名规则行,就会创建一个全新的私有上下文「进栈」,函数每一次执行都是重新形成一个全新的私有上下文,和之前执行产生的上下文没有必然的联系,函数中的代码都是在私有上下文中进行执行的。函数进栈执行会创建一个私有变量对象 AO(Active Object),这里区分开 VO。AO 是 VO 的一个变量英语分支。在私用上下文中创建的变量都会存储在 AO 中,例如形参、变量提升和函数中定义的变量。
-
函数进行初始化操作
-
初始化作用域链<<自己的私有上下文,作用域>>。
-
初始化 this。
-
初始化 arguments (没有)。
-
形参赋值。
-
变量提升。
- for 循环中的 let 不进行提升
- for 循环内部的 var 进行变量提升,提升到 fn 的函数作用字符常量域变量之间的关系内,这其实就是在循环体实际只是定义了一个 timer 变量,每一次迭代执行,都是对这个 timer 进行重新赋值。
-
-
函数存储变量类型有哪些的字符串执行,将堆内存中存在的代码字符串从上往下的顺序进行执行。
-
出栈释放
-
函数代变量名码字符串的执行
我们接下来重点看看 fn 函数内部的执行,下面我用一段伪代码演示一下你这个代码执行后发变量泵生了什么:
注意点:
- for 循环中的 let 不进行提升,形成块级作用域。
- for 循环内部的 var 进变量泵行变量提升,提升到 fn 的函数作用域内,这其实就是在循环体实际只是定义了一个 timer字符常量 变量,每一次迭代执行,都是对这个 timer 进行重新赋值。
- setTimeout 是异步,会放入宏任务队列。前端和后端的区别
- setTimeout 的第三个参变量类型有哪些数会作为 setTimeou浏览器推荐t 的回调函数的参数传入。
第一次迭代:
fn: {
var timer = timer0
for: {
let i = 0
}
}
task(宏任务): [ timer0 ]
第二次迭代:
fn: {
var timer = timer1
for: {
let i = 1
}
}
task(宏任务): [ timer0, timer1 ]
第三次迭代:
fn: {
var timer = timer2
for: {
let i = 2
}
}
task(宏任务): [ timer0, timer1, timer2 ]
第四次迭代:
fn: {
var timer = timer3
for: {
let i = 3
}
}
task(宏任务): [ timer0, timer1, timer2, timer3 ]
同步代码执行完成,timer 由于被提升到 fn 的函数作用域,每一次循环执行,都是重置赋值 timer,所以,timer 最终指向的 timer3。这样还没有结束,同步代码执行完成,开始执行异步队列代码。
异字符间距步队列执行
异步代码会按照 task 的顺序依次执行。 当执行 timer0 的时候会执行 clearTimeout(ti前端开发mer3) ,把 timer3 从 task 列表里去掉,最终只有 timer0、timer1、timer2 三个得到了执行,因此只会前端开发需要掌握什么技术 console 出 0、1、2。
如何正常输入 0、1、2、3
那如果我们想要按照0、1、2、3 这样的输出方式进行输出,应该如何调整这段代码了。
问题的本质是出在 timer 前面这个 var 变浏览器历史记录设置量定义上。v字符间距加宽2磅ar前端 定义的变量,是不会生成“块级作用域”,变量被提升。同步代码执行完成之后,timer 最终指向了 timer3,导致在执行 task 的时候被清除。
要解决这个问题就不能让 timer 最后指向 timer3。
需要让每一次循环迭代,timer 都是一个独立的变量。
timer变量的定义 如果想浏览器数据如何恢复要是独立的变量,就需要每一次循环迭代变量英语都在一个独立的作用域中执行。并且不会进行变量提升
除函数和对象的大字符是什么括号之外,其他大括号如果出现了let、const、function、class 等关键字声明变量,则当前大括号会产生一个块级上下文。它的上级上下文就是所处的环境。var 声明不会产生块级上下文,也不受块级上下文的影响。所以在当前场景,使用 let/co浏览器网站删除了怎么恢复nst 声明 timer,形成块级作用域。
这里也很简单会议一下 let/const 和 var 的区别(# var、let、const前端被你忽略的区别):
- let/const 不存在变量提升,不允许在声明之前使用
- let/const 不允许重复声明
- let/const 不会污染全局
- let/const 会生成块级上下文
- let / cons变量名t 会形成暂时性死区
- 形参重新声明(易被忽略)
function fn() {
for (let i = 0; i < 4; i++) {
// let、const 都可以
let timer = setTimeout(function(i) {
console.log(i);
clearTimeout(timer);
}, 10, i);
}
}
fn();
总结
看似简单浏览器数据如何恢复的一段代码,隐藏字符常量了很多的前端开发需要学什么知识点:
- 「变量提升」
- 「函数执行机制」
- 「块级作用域」
- 「异步任务队列」
- let/const 和 var 的区别
等… 但这些知识点本身不难,只是可能隐藏比较深,让我们一不小心就掉坑了。所以简单的代码,越容易有坑。本文也通过一步一步浏览器的分析找到问题的原因,然后找出解决办法。
以上就是本文的全部内容,如果喜欢帮忙点个赞吧,谢谢。
参考
- juej字符串是什么意思in.cn/post/6变量名的命名规则95387…
- /post/708389…变量类型有哪些
- /post/708352…
- www.zhihu.com/question/4浏览器网站删除了怎么恢复3…
- /post/浏览器708556…