Products
GG网络技术分享 2025-03-18 16:13 0
作者:疯狂的技术宅
转发链接: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 作用域的行为与你预期的不同。你可能会研究这些案例以提高对作用域的了解,或者只是为面试做准备。
思考以下代码片段:
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 循环体内。
在以下代码段中:
1//ES2015env
2{
3functionhello(){
4return'Hello!';
5}
6}
7
8hello();//???
调用 hello() 会怎样?(代码段在 ES2015 环境中执行)
因为代码块为函数声明创建了作用域,所以在 ES2015 环境中调用 hello() 会引发 ReferenceError: hello is not defined。
有趣的是,在 ES2015 之前的环境中,在执行上述代码段时不会抛出错误。你知道为什么吗?请在下面的评论中写下你的答案!
你可以在代码块中导入模块吗?
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 语句,因为它们是在运行时执行的。
思考以下函数:
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。
以下代码在代码块内定义了一个函数和一个类:
1if(true){
2functiongreet(){
3//functionbody
4}
5
6classGreeter{
7//classbody
8}
9}
10
greet();//???
newGreeter();//???是否可以在块作用域之外访问 greet 和 Greeter?(考虑 ES2015 环境)
function 和 class 声明都是块作用域的。所以在代码块作用域外调用函数 greet() 和构造函数 new Greeter() 就会抛出 ReferenceError。
必须注意 var 变量,因为它们是函数作用域的,即使是在代码块中定义的。
由于 ES2015 模块系统是静态的,因此你必须在模块作用域内使用 import 语法(以及export)。
函数参数具有其作用域。设置默认参数值时,请确保默认表达式内的变量已经用值初始化。
在 ES2015 运行时环境中,函数和类声明是块作用域的。但是在 ES2015 之前的环境中,函数声明仅在函数作用域内。
希望这些陷阱能够帮你巩固作用域知识!
《前端开发规范:命名规范、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
js是单线程的,为什么可以执行异步操作呢?
这归结与浏览器(js的宿主环境)通过某种方式使得js具备了异步的属性。
区分进程和线程:
进程:正在运行中的应用程序。每个进程都自己独立的内存空间。例如:打开的浏览器就是一个进程。
线程:进程的子集,是独立的。线程在共享的内存空间中运行。
浏览器是多进程的。如下图:
并且每打开一个页面就创建了一个独立的进程。进程内有自己的多线程。如果浏览器是单进程的,那么某个页面崩了,就会影响整个浏览器。
浏览器有哪些进程:
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)去处理,处理完放到任务队列里,当主线程(执行栈)执行完毕时,如果任务队列里有任务,就执行。
这也就是为什么下面代码会先输出b,然后是a的原因。settimeout的函数会放到任务队列中,而console.log(\'b\')是主线程。
setTimeout(() => { console.log(\'a\'); }, 0); console.log(\'b\'); |
以上就是浅谈javascript执行机制的详细内容,更多请关注网站的其它相关文章!
Demand feedback