建站教程

建站教程

Products

当前位置:首页 > 建站教程 >

面试中教你绕过关于 JavaScript 作用域的 5 个坑(javascript执行机制是怎样的)

GG网络技术分享 2025-03-18 16:13 0


面试中教你绕过关于 JavaScript 作用域的 5 个坑


作者:疯狂的技术宅

转发链接:https://mp.weixin.qq.com/s/XkEvgyhJ8S0xQS2kOY67Qw

前言

在 JavaScript 中,代码块、函数或模块为变量创建作用域。例如 if 代码块为变量 message创建作用域:

1if(true){

2constmessage='Hello';

3console.log(message);//'Hello'

4}

5console.log(message);//throwsReferenceError

在 if 代码块作用域内可以访问 message。但是在作用域之外,该变量不可访问。以上是作用域的简短介绍。

好东西,都在文章底部,不妨拉倒底部看看惊喜

以下是 5 种有趣的情况,其中 JavaScript 作用域的行为与你预期的不同。你可能会研究这些案例以提高对作用域的了解,或者只是为面试做准备。

1. for 循环内的 var 变量

思考以下代码片段:

1constcolors=['red','blue','white'];

2

3for(leti=0,varl=colors.length;i<l;i++){

4console.log(colors[i]);//'red','blue','white'

5}

6console.log(l);//???

7console.log(i);//???

当你打印 l 和 i 变量时会发生什么?

答案

console.log(l) 输出数字 3 ,而 console.log(i) 则抛出 ReferenceError。

l 变量是使用 var 语句声明的。你可能已经知道,var 变量仅受函数体作用域限制而并非代码块。

相反,变量 i 使用 let 语句声明。因为 let 变量是块作用域的,所以 i 仅在 for 循环作用域内才可访问。

修复

把 l 声明从 var l = colors.length 改为 const l = colors.length。现在变量 l 被封装在 for 循环体内。

2. 代码块中的函数声明

在以下代码段中:

1//ES2015env

2{

3functionhello(){

4return'Hello!';

5}

6}

7

8hello();//???

调用 hello() 会怎样?(代码段在 ES2015 环境中执行)

答案

因为代码块为函数声明创建了作用域,所以在 ES2015 环境中调用 hello() 会引发 ReferenceError: hello is not defined。

有趣的是,在 ES2015 之前的环境中,在执行上述代码段时不会抛出错误。你知道为什么吗?请在下面的评论中写下你的答案!

3. 你可以在哪里导入模块?

你可以在代码块中导入模块吗?

1if(true){

2import{myFunc}from'myModule';//???

3myFunc();

4}

答案

上面的脚本将触发错误:'import' and 'export' may only appear at the top-level。

你只能在模块文件的最顶级作用域(也称为模块作用域)中导入模块。

修复

始终从模块作用域导入模块。另外一个好的做法是将 import 语句放在源文件的开头:

1import{myFunc}from'myModule';

2

3if(true){

4myFunc();

5}

ES2015 的模块系统是静态的。通过分析 JavaScript 源代码而不是执行代码来确定模块的依赖关系。所以在代码块或函数中不能包含 import 语句,因为它们是在运行时执行的。

4. 函数参数作用域

思考以下函数:

1letp=1;

2

3functionmyFunc(p=p+1){

4returnp;

5}

6

7myFunc();//???

调用 myFunc() 会发生什么?

答案

当调用函数 myFunc() 时,将会引发错误:ReferenceError: Cannot access 'p' before initialization。

发生这种情况是因为函数的参数具有自己的作用域(与函数作用域分开)。参数 p = p + 1 等效于 let p = p + 1。

让我们仔细看看 p = p + 1。

首先,定义变量 p。然后 JavaScript 尝试评估默认值表达式 p + 1,但此时绑定 p 已经创建但尚未初始化(不能访问外部作用域的变量 let p = 1)。因此抛出一个错误,即在初始化之前访问了 p。

修复

为了解决这个问题,你可以重命名变量 let p = 1 ,也可以重命名功能参数 p = p + 1。

让我们选择重命名函数参数:

1letp=1;

2

3functionmyFunc(q=p+1){

4returnq;

5}

6

7myFunc();//=>2

函数参数从 p 重命名为 q。当调用 myFunc() 时,未指定参数,因此将参数 q 初始化为默认值 p + 1。为了评估 p +1,访问外部作用域的变量 p:p +1 = 1 + 1 = 2。

5. 函数声明与类声明

以下代码在代码块内定义了一个函数和一个类:

1if(true){

2functiongreet(){

3//functionbody

4}

5

6classGreeter{

7//classbody

8}

9}

10

greet();//???

newGreeter();//???

是否可以在块作用域之外访问 greet 和 Greeter?(考虑 ES2015 环境)

答案

function 和 class 声明都是块作用域的。所以在代码块作用域外调用函数 greet() 和构造函数 new Greeter() 就会抛出 ReferenceError。

6. 总结

必须注意 var 变量,因为它们是函数作用域的,即使是在代码块中定义的。

由于 ES2015 模块系统是静态的,因此你必须在模块作用域内使用 import 语法(以及export)。

函数参数具有其作用域。设置默认参数值时,请确保默认表达式内的变量已经用值初始化。

在 ES2015 运行时环境中,函数和类声明是块作用域的。但是在 ES2015 之前的环境中,函数声明仅在函数作用域内。

希望这些陷阱能够帮你巩固作用域知识!

推荐JavaScript经典实例学习资料文章

《前端开发规范:命名规范、html规范、css规范、js规范》

《【规范篇】前端团队代码规范最佳实践》

《100个原生JavaScript代码片段知识点详细汇总【实践】》

《关于前端174道 JavaScript知识点汇总(一)》

《关于前端174道 JavaScript知识点汇总(二)》

《关于前端174道 JavaScript知识点汇总(三)》

《几个非常有意思的javascript知识点总结【实践】》

《都2020年了,你还不会JavaScript 装饰器?》

《JavaScript实现图片合成下载》

《70个JavaScript知识点详细总结(上)【实践】》

《70个JavaScript知识点详细总结(下)【实践】》

《开源了一个 JavaScript 版敏感词过滤库》

《送你 43 道 JavaScript 面试题》

《3个很棒的小众JavaScript库,你值得拥有》

《手把手教你深入巩固JavaScript知识体系【思维导图】》

《推荐7个很棒的JavaScript产品步骤引导库》

《Echa哥教你彻底弄懂 JavaScript 执行机制》

《一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧》

《深入解析高频项目中运用到的知识点汇总【JS篇】》

《JavaScript 工具函数大全【新】》

《从JavaScript中看设计模式(总结)》

《身份证号码的正则表达式及验证详解(JavaScript,Regex)》

《浏览器中实现JavaScript计时器的4种创新方式》

《Three.js 动效方案》

《手把手教你常用的59个JS类方法》

《127个常用的JS代码片段,每段代码花30秒就能看懂-【上】》

《深入浅出讲解 js 深拷贝 vs 浅拷贝》

《手把手教你JS开发H5游戏【消灭星星】》

《深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】》

《手把手教你全方位解读JS中this真正含义【实践】》

《书到用时方恨少,一大波JS开发工具函数来了》

《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》

《手把手教你JS 异步编程六种方案【实践】》

《让你减少加班的15条高效JS技巧知识点汇总【实践】》

《手把手教你JS开发H5游戏【黄金矿工】》

《手把手教你JS实现监控浏览器上下左右滚动》

《JS 经典实例知识点整理汇总【实践】》

《2.6万字JS干货分享,带你领略前端魅力【基础篇】》

《2.6万字JS干货分享,带你领略前端魅力【实践篇】》

《简单几步让你的 JS 写得更漂亮》

《恭喜你获得治疗JS this的详细药方》

《谈谈前端关于文件上传下载那些事【实践】》

作者:lzg9527

作者:疯狂的技术宅

转发链接:https://mp.weixin.qq.com/s/XkEvgyhJ8S0xQS2kOY67Qw

javascript执行机制是怎样的

js是单线程的,为什么可以执行异步操作呢?

这归结与浏览器(js的宿主环境)通过某种方式使得js具备了异步的属性。

区分进程和线程:

进程:正在运行中的应用程序。每个进程都自己独立的内存空间。例如:打开的浏览器就是一个进程。

线程:进程的子集,是独立的。线程在共享的内存空间中运行。

浏览器是多进程的。如下图:

javascript执行机制是怎样的 (https://www.wpmee.com/) javascript教程 第1张

并且每打开一个页面就创建了一个独立的进程。进程内有自己的多线程。如果浏览器是单进程的,那么某个页面崩了,就会影响整个浏览器。

浏览器有哪些进程:

1、Browser(浏览器):浏览器的主进程(负责协调,主控)只有一个,作用有:

•负责浏览器界面显示,与用户交互。如前进,后退等

•负责各个页面的管理,创建和销毁其他进程

•将Renderer(渲染器)进程得到的内存中的Bitmap,绘制到用户界面上

•网络资源的管理,下载等

2、第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建

3、GPU进程:最多一个,用于3D绘制等

4、浏览器渲染进程(浏览器内核)(Renderer(渲染器),内部是多线程的)默认每个Tab页面一个进程,互不影响。主要作用 :页面渲染,脚本执行,事件处理等

浏览器渲染进程(浏览器内核)包含的线程:

1、GUI渲染线程

• 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。

• 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行

• 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

2、JS引擎线程(

“JavaScript 引擎”通常被称作一种 虚拟机。也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)

• JS引擎线程负责解析Javascript脚本,运行代码。

• JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序

• 同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

3、事件触发线程

• 归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)

• 当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中

• 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理

• 注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

4. 定时触发器线程

• 传说中的setInterval与setTimeout所在线程

• 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)

• 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)

• 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。

5、异步http请求线程

• 在XMLHttpRequest在连接后是通过浏览器新开一个线程请求

• 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。再由JavaScript引擎执行。

GUI渲染线程与JS引擎线程互斥:

由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起,GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。

js执行机制:js是单线程的,每当执行函数就把函数推入栈中,但有异步的操作就让浏览器的线程(webAPI)去处理,处理完放到任务队列里,当主线程(执行栈)执行完毕时,如果任务队列里有任务,就执行。

javascript执行机制是怎样的 (https://www.wpmee.com/) javascript教程 第2张

这也就是为什么下面代码会先输出b,然后是a的原因。settimeout的函数会放到任务队列中,而console.log(\'b\')是主线程。

setTimeout(() => {

console.log(\'a\');

}, 0);

console.log(\'b\');

以上就是浅谈javascript执行机制的详细内容,更多请关注网站的其它相关文章!

标签:

提交需求或反馈

Demand feedback