摘要:在中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈。而处于栈顶的是当前正在执行函数的执行上下文,当函数调用完成后,它就会从栈顶被推出理想的情况下,闭包会阻止该操作,闭包后续文章深入详解。
写在开篇
已经不敢自称前端小白,曾经吹过的牛逼总要一点点去实现。
正如前领导说的,自己喝酒吹过的牛皮,跪着都得含着泪去实现。
那么没有年终完美总结,来个新年莽撞开始可好。
进击巨人系列开篇,不忘初心,砥砺前行。
理解执行上下文执行上下文(Execution Context): 函数执行前进行的准备工作(也称执行上下文环境)
运行JavaScript代码时,当代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会在你运行代码前做一些准备工作,如确定作用域,创建局部变量对象等。
具体做了什么先按下不表,先来看下JavaScript执行环境有哪些?
JavaScript中执行环境全局环境
函数环境
eval函数环境 (已不推荐使用)
那么与之对应的执行上下文类型同样有3种:
执行上下文的类型全局执行上下文
函数执行上下文
eval函数执行上下文
JavaScript运行时首先会进入全局环境,对应会生成全局上下文。程序代码中基本都会存在函数,那么调用函数,就会进入函数执行环境,对应就会生成该函数的执行上下文。
先插播一个知识点:"JS是单线程"! "单线程"! "单线程"!
简单理解下单线程,就是同个时间段只能做一件任务,完成之后才可以继续下一个任务。正如女朋友只有一个,各位面向对象的小伙伴们你们说对不对?有女票的必须说没毛病。
既然是这样,必须要有一个排队机制,不然就会出现几个流氓霸着车道不让过,"还有王法么?"
JS中管理多个执行上下文函数编程中,代码中会声明多个函数,对应的执行上下文也会存在多个。在JavaScript中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈(Call Stack)。
在说明执行栈前,先来补下"栈数据结构"知识点。
栈数据结构
借助前端大神的例子,用乒乓球盒子来理解栈的存取方式。(这个例子让我彻底记住了栈数据结构)
栈遵循"先进后出,后进先出"的规则,或称LIFO ("Last In First Out") 规则。
如图所示,我们只能从栈顶取出或放入乒乓球,最先放进盒子的总是最后才能取出。
栈中"放入/取出",也可称为"入栈/出栈"。
总结栈数据结构的特点:
后进先出,先进后出
出口在顶部,且仅有一个
执行栈(函数调用栈)理解完栈的存取方式,我们接着分析JavaScript中如何通过栈来管理多个执行上下文。
程序执行进入一个执行环境时,它的执行上下文就会被创建,并被推入执行栈中(入栈);
程序执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文。
因为JS执行中最先进入全局环境,所以处于"栈底的永远是全局环境的执行上下文"。而处于"栈顶的是当前正在执行函数的执行上下文",当函数调用完成后,它就会从栈顶被推出(理想的情况下,闭包会阻止该操作,闭包后续文章深入详解)。
"全局环境只有一个,对应的全局执行上下文也只有一个,只有当页面被关闭之后它才会从执行栈中被推出,否则一直存在于栈底"
文字太多不如上代码系列 ——》代码 + 图,一览无遗:
function foo () { function bar () { return "I am bar"; } return bar(); } foo();执行上下文的生命周期
执行上下文的生命周期有两个阶段:
创建阶段(进入执行上下文)
执行阶段(代码执行)
创建阶段:函数被调用时,进入函数环境,为其创建一个执行上下文,此时进入创建阶段
执行阶段:执行函数中代码时,此时执行上下文进入执行阶段
创建阶段的操作
创建变量对象
函数环境会初始化创建Arguments对象(并赋值)
函数声明(并赋值)
变量声明,函数表达式声明(未赋值)
确定this指向(this由调用者确定)
确定作用域(词法环境决定,哪里声明定义,就在哪里确定)
执行阶段的操作
变量对象赋值
变量赋值
函数表达式赋值
调用函数
顺序执行其它代码
看到这里,我们不经会问变量对象是什么鬼,它与代码中常见的函数声明,变量声明有神马关系???
变量对象和活动对象的区别:当进入到一个执行上下文后,这个变量对象才会被激活,所以叫活动对象(AO),这时候活动对象上的各种属性才能被访问。
"创建阶段对函数声明做赋值,变量及函数表达式仅做声明,真正的赋值操作要等到执行上下文代码执行阶段"。
代码例子1:变量提升
function foo() { console.log(a); // 输出undefined var a = "I am here"; // 赋值 } foo(); // 实际执行过程 function foo() { var a; // 变量声明,var初始化undefined console.log(a); a = "I am here"; // 变量重新赋值 }
代码例子2:函数声明优先级
function foo() { console.log(bar); var bar = 20; function bar() { return 10; } var bar = function() { return 30; } } foo(); // 输出bar()整个函数声明函数声明,变量声明,函数表达式的优先级
函数声明,如果有同名属性,会替换掉
变量,函数表达式
函数声明优先 > 变量,函数表达式
执行上下文的数量限制(堆栈溢出)执行上下文可存在多个,虽然没有明确的数量限制,但如果超出栈分配的空间,会造成堆栈溢出。常见于递归调用,没有终止条件造成死循环的场景。
// 递归调用自身 function foo() { foo(); } foo(); // 报错: Uncaught RangeError: Maximum call stack size exceeded文末总结
JavaScript是单线程
栈顶的执行上下文处于执行中,其它需要排队
全局上下文只有一个处于栈底,页面关闭时出栈
函数执行上下文可存在多个,但应避免递归时堆栈溢出
函数调用时就会创建新的上下文,即使调用自身,也会创建不同的执行上下文
参考文档:
执行上下文详细图解
理解JavaScript 中的执行上下文和执行栈
这一次,彻底弄懂 JavaScript 执行机制
本文首发Github,期待Star!
https://github.com/ZengLingYong/blog
作者:以乐之名
本文原创,有不当的地方欢迎指出。转载请指明出处。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/101009.html
摘要:中有三种数据结构栈堆队列。前端进击的巨人一执行上下文与执行栈,变量对象中解释执行栈时,举了一个乒乓球盒子的例子,来演示栈的存取方式,这里再举个栗子搭积木。对于基本类型,栈中存储的就是它自身的值,所以新内存空间存储的也是一个值。 面试经常遇到的深浅拷贝,事件轮询,函数调用栈,闭包等容易出错的题目,究其原因,都是跟JavaScript基础知识不牢固有关,下层地基没打好,上层就是豆腐渣工程,...
摘要:进击的巨人第三篇,本篇就作用域作用域链闭包等知识点,一一击破。在此我们遵照的方式,暂且称是闭包。所以,一名合格的前端,除了会用闭包,还要正确的解除闭包引用。 进击的巨人第三篇,本篇就作用域、作用域链、闭包等知识点,一一击破。 showImg(https://segmentfault.com/img/bVburWd?w=1280&h=854); 作用域 作用域:负责收集并维护由所有声明的...
摘要:自执行函数闭包实现模块化以乐之名程序员产品经理对作用域,以及闭包知识还没掌握的小伙伴,可回阅前端进击的巨人三从作用域走进闭包。参考文档利用闭包实现模块化翻译浅谈中的高阶函数系列更文请关注专栏前端进击的巨人,不断更新中。。。 系列更文前三篇文章,围绕了一个重要的知识点:函数。函数调用栈、函数执行上下文、函数作用域到闭包。可见不理解函数式编程,代码都撸不好。 showImg(https:/...
摘要:有关函数柯里化的详解,请回阅前端进击的巨人五学会函数柯里化。构造函数中的通过操作符可以实现对函数的构造调用。在了解构造函数中的前,有必要先了解下实例化对象的过程。 showImg(https://segmentfault.com/img/bVburMp?w=800&h=600); 常见this的误解 指向函数自身(源于this英文意思的误解) 指向函数的词法作用域(部分情况) th...
摘要:隆重请出主角防抖与节流。防抖与节流的异同相同都是防止某一时间段内,函数被频繁调用执行,通过时间频率控制,减少回调函数执行次数,来实现相关性能优化。参考文章分钟理解的节流防抖及使用场景函数防抖和节流 showImg(https://segmentfault.com/img/bVburM8?w=800&h=600); 本篇课题,或许早已是烂大街的解读文章。不过春招系列面试下来,不少伙伴们还...
阅读 1291·2021-09-27 13:56
阅读 2303·2019-08-26 10:35
阅读 3481·2019-08-23 15:53
阅读 1824·2019-08-23 14:42
阅读 1216·2019-08-23 14:33
阅读 3534·2019-08-23 12:36
阅读 1931·2019-08-22 18:46
阅读 956·2019-08-22 14:06