壹 "htmlcode">
function f1() { console.log('听风是风'); }; f1(); //echo function f1() { console.log('echo'); }; f1(); //echo
按照代码书写顺序,应该先输出 听风是风,再输出 echo才对,很遗憾,两次输出均为 echo;如果我们将上述代码中的函数声明改为函数表达式,结果又不太一样:
var f1 = function () { console.log('听风是风'); }; f1(); //听风是风 var f1 = function() { console.log('echo'); }; f1(); //echo
这说明代码在执行前一定发生了某些微妙的变化,JS引擎究竟做了什么呢?这就不得不提JS执行上下文的了。
全局对象window上预定义了大量的方法和属性,我们在全局环境的任意处都能直接访问这些属性方法,同时window对象还是var声明的全局变量的载体。我们通过var创建的全局对象,都可以通过window直接访问。
2.函数执行上下文
函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文;需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。
说到这你是否会想,上下文种类不同,而且创建的数量还这么多,它们之间的关系是怎么样的,又是谁来管理这些上下文呢,这就不得不说说执行上下文栈了。
叁 "htmlcode">
function f1() { f2(); console.log(1); }; function f2() { f3(); console.log(2); }; function f3() { console.log(3); }; f1();//3 2 1
我们通过执行栈与上下文的关系来解释上述代码的执行过程,为了方便理解,我们假象执行栈是一个数组,在代码执行初期一定会创建全局执行上下文并压入栈,因此过程大致如下:
//代码执行前创建全局执行上下文 ECStack = [globalContext]; // f1调用 ECStack.push('f1 functionContext'); // f1又调用了f2,f2执行完毕之前无法console 1 ECStack.push('f2 functionContext'); // f2又调用了f3,f3执行完毕之前无法console 2 ECStack.push('f3 functionContext'); // f3执行完毕,输出3并出栈 ECStack.pop(); // f2执行完毕,输出2并出栈 ECStack.pop(); // f1执行完毕,输出1并出栈 ECStack.pop(); // 此时执行栈中只剩下一个全局执行上下文
那么到这里,我们解释了执行栈与执行上下文的存储规则;还记得我在前文提到代码执行前JS引擎会做准备创建执行上下文吗,具体怎么创建呢,我们接着说。
肆 "htmlcode">
ExecutionContext = { // 确定this的值 ThisBinding = <this value>, // 创建词法环境组件 LexicalEnvironment = {}, // 创建变量环境组件 VariableEnvironment = {}, };
如果你有阅读其它关于执行上下文的文章读到这里一定有疑问,执行上下文创建过程不是应该解释this,作用域与变量对象/活动对象才对吗,怎么跟别的地方说的不一样,这点我后面解释。
1.确定this
官方的称呼为This Binding,在全局执行上下文中,this总是指向全局对象,例如浏览器环境下this指向window对象。
而在函数执行上下文中,this的值取决于函数的调用方式,如果被一个对象调用,那么this指向这个对象。否则this一般指向全局对象window或者undefined(严格模式)。
2.词法环境组件
词法环境是一个包含标识符变量映射的结构,这里的标识符表示变量/函数的名称,变量是对实际对象【包括函数类型对象】或原始值的引用。
词法环境由环境记录与对外部环境引入记录两个部分组成。
其中环境记录用于存储当前环境中的变量和函数声明的实际位置;外部环境引入记录很好理解,它用于保存自身环境可以访问的其它外部环境,那么说到这个,是不是有点作用域链的意思?
我们在前文提到了全局执行上下文与函数执行上下文,所以这也导致了词法环境分为全局词法环境与函数词法环境两种。
全局词法环境组件:
对外部环境的引入记录为null,因为它本身就是最外层环境,除此之外它还记录了当前环境下的所有属性、方法位置。
函数词法环境组件:
包含了用户在函数中定义的所有属性方法外,还包含了一个arguments对象。函数词法环境的外部环境引入可以是全局环境,也可以是其它函数环境,这个根据实际代码而来。
这里借用译文中的伪代码(环境记录在全局和函数中也不同,全局中的环境记录叫对象环境记录,函数中环境记录叫声明性环境记录,说多了糊涂,下方有展示):
// 全局环境 GlobalExectionContext = { // 全局词法环境 LexicalEnvironment: { // 环境记录 EnvironmentRecord: { Type: "Object", //类型为对象环境记录 // 标识符绑定在这里 }, outer: < null > } }; // 函数环境 FunctionExectionContext = { // 函数词法环境 LexicalEnvironment: { // 环境纪录 EnvironmentRecord: { Type: "Declarative", //类型为声明性环境记录 // 标识符绑定在这里 }, outer: < Global or outerfunction environment reference > } };
3.变量环境组件
变量环境可以说也是词法环境,它具备词法环境所有属性,一样有环境记录与外部环境引入。在ES6中唯一的区别在于词法环境用于存储函数声明与let const声明的变量,而变量环境仅仅存储var声明的变量。
我们通过一串伪代码来理解它们:
let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30);
我们用伪代码来描述上述代码中执行上下文的创建过程:
//全局执行上下文 GlobalExectionContext = { // this绑定为全局对象 ThisBinding: <Global Object>, // 词法环境 LexicalEnvironment: { //环境记录 EnvironmentRecord: { Type: "Object", // 对象环境记录 // 标识符绑定在这里 let const创建的变量a b在这 a: < uninitialized >, b: < uninitialized >, multiply: < func > } // 全局环境外部环境引入为null outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // 对象环境记录 // 标识符绑定在这里 var创建的c在这 c: undefined, } // 全局环境外部环境引入为null outer: <null> } } // 函数执行上下文 FunctionExectionContext = { //由于函数是默认调用 this绑定同样是全局对象 ThisBinding: <Global Object>, // 词法环境 LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // 声明性环境记录 // 标识符绑定在这里 arguments对象在这 Arguments: {0: 20, 1: 30, length: 2}, }, // 外部环境引入记录为</Global> outer: <GlobalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // 声明性环境记录 // 标识符绑定在这里 var创建的g在这 g: undefined }, // 外部环境引入记录为</Global> outer: <GlobalEnvironment> } }
不知道你有没有发现,在执行上下文创建阶段,函数声明与var声明的变量在创建阶段已经被赋予了一个值,var声明被设置为了undefined,函数被设置为了自身函数,而let const被设置为未初始化。
现在你总知道变量提升与函数声明提前是怎么回事了吧,以及为什么let const为什么有暂时性死域,这是因为作用域创建阶段JS引擎对两者初始化赋值不同。
上下文除了创建阶段外,还有执行阶段,这点大家应该好理解,代码执行时根据之前的环境记录对应赋值,比如早期var在创建阶段为undefined,如果有值就对应赋值,像let const值为未初始化,如果有值就赋值,无值则赋予undefined。
伍 "color: #ff0000">陆 ❀ 总结
1.全局执行上下文一般由浏览器创建,代码执行时就会创建;函数执行上下文只有函数被调用时才会创建,调用多少次函数就会创建多少上下文。
2.调用栈用于存放所有执行上下文,满足FILO规则。
3.执行上下文创建阶段分为绑定this,创建词法环境,变量环境三步,两者区别在于词法环境存放函数声明与const let声明的变量,而变量环境只存储var声明的变量。
4.词法环境主要由环境记录与外部环境引入记录两个部分组成,全局上下文与函数上下文的外部环境引入记录不一样,全局为null,函数为全局环境或者其它函数环境。环境记录也不一样,全局叫对象环境记录,函数叫声明性环境记录。
5.你应该明白了为什么会存在变量提升,函数提升,而let const没有。
6.ES3之前的变量对象与活动对象的概念在ES5之后由词法环境,变量环境来解释,两者概念不冲突,后者理解更为通俗易懂。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
RTX 5090要首发 性能要翻倍!三星展示GDDR7显存
三星在GTC上展示了专为下一代游戏GPU设计的GDDR7内存。
首次推出的GDDR7内存模块密度为16GB,每个模块容量为2GB。其速度预设为32 Gbps(PAM3),但也可以降至28 Gbps,以提高产量和初始阶段的整体性能和成本效益。
据三星表示,GDDR7内存的能效将提高20%,同时工作电压仅为1.1V,低于标准的1.2V。通过采用更新的封装材料和优化的电路设计,使得在高速运行时的发热量降低,GDDR7的热阻比GDDR6降低了70%。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]