双语网站建设网站,亚马逊 wordpress,如何用小米路由器做网站,网站推广怎么做才有效果参考资料
极客时间课程《浏览器工作原理与实践》 – 李兵
一、js代码执行过程
#xff08;一#xff09;javascript代码的执行流程
浏览器执行javascript代码的流程如下图所示#xff1a; javascript的执行机制是#xff1a;先编译#xff0c;再执行。在编译阶段生成了…参考资料
极客时间课程《浏览器工作原理与实践》 – 李兵
一、js代码执行过程
一javascript代码的执行流程
浏览器执行javascript代码的流程如下图所示 javascript的执行机制是先编译再执行。在编译阶段生成了执行上下文、可执行代码。执行上下文由变量环境、词法环境构成它为可执行代码在执行时提供运行环境。
在编译阶段javascript引擎将变量的声明部分和函数的声明部分提升到代码开头并且给被提升的变量设置默认值undefined同时这些被提升的变量将被放入到变量环境对象中。
二变量提升Hoisting
所谓变量提升就是js代码在编译阶段js引擎将变量的声明部分和函数的声明部分提升到代码开头的一种行为。在js代码执行前会先进行编译在编译阶段出现了变量提升关于编译阶段为什么会出现变量提升、变量提升带来的问题、ES6如何修正变量提升将在 变量提升 中进行深入了解。下面先对变量提升做初步了解
下面举例分析一下
例1常见的变量提升
//示例代码
console.log(fun1, fun1);
showName()
console.log(myname,myname);
var myname aaa;
function showName () {console.log(执行showName函数);
}
var fun1 function () {console.log(另一种函数声明方式);
}//变量提升
function showName () {console.log(执行showName函数)
}
var myname undefined;
var fun1 undefined;//可执行代码
console.log(fun1, fun1);
showName()
console.log(myname,myname);
myname aaa;
fun1 function () {console.log(另一种函数声明方式);
}//执行结果
fun1 undefined
执行showName函数
myname undefined该例子中的变量环境对象大致如下表所示对于函数声明function声明函数体一起被提升并存在变量环境对象中。当函数被调用时函数体会被编译并创建函数执行上下文为函数运行提供环境。当函数执行完毕函数执行上下文将被销毁。
变量名值showNamefunction () { console.log(“执行showName函数”); }mynameundefinedfun1undefined
例2变量与函数同名
//示例代码showName()
/** 函数表达式等同变量声明处理函数声明在前变量声明在后 **/
function showName () {console.log(1);
}
/** 重新赋值 **/
var showName function () {console.log(2);
}
showName()//变量提升
/** 函数提升优先级高于变量提升先被提升 **/
function showName () {console.log(1);
}
var showName undefined;//可执行代码
showName()
showName function () {console.log(2);
}
showName()//执行结果
1
2分析变量提示时存在变量showName与函数showName同名此时函数提升比变量提升的优先级要高且不会被变量声明覆盖但是会被变量赋值之后覆盖。简单来说就是在进行变量提升时先提升函数声明再提升变量声明当有变量名与函数名相同时函数声明不会被变量声明覆盖但在运行阶段可以通过赋值语句对该变量进行重新赋值。所以上述代码运行结果与下面代码运行结果一致
//示例代码
showName()
/** 函数表达式等同变量声明处理变量声明在前函数声明在后 **/
/** 重新赋值 **/
var showName function () {console.log(2);
}
function showName () {console.log(1);
}
showName()//变量提升
/** 函数提升优先级高于变量提升先被提升 **/
function showName () {console.log(1);
}
var showName undefined;//可执行代码
showName()
showName function () {console.log(2);
}
showName()//执行结果
1
2例3函数与函数同名js编译阶段会选择最后声明的那个
总结变量提升只提升声明不提升赋值针对变量声明与函数声明进行提升。函数声明主要有函数声明、函数表达式。其中函数声明会将方法体也提升而函数表达式同变量提升一样只会提升声明。当变量提升遇到ES6的let/const会出现暂时性死区效果就像没有提升。另外要注意在ES6的块级作用域中var变量能够穿透块提升到全局。
二、调用栈
栈是一种后进先出的数据结构。在执行js代码时可能会存在多个执行上下文这些执行上下文可能是编译全局代码创建的全局执行上下文全局唯一、编译函数体创建的函数执行上下文、编译eval函数创建的执行上下文。这些执行上下文之间也可能存在相互调用关系js引擎采用栈来对这些执行上下文进行管理。通常将这种用于管理执行上下文的栈称为调用栈也叫做执行上下文栈。 通过代码示例分析调用栈如何管理执行上下文 创建全局执行上下文生成可执行代码并将上下文压入栈底 a. 更新全局上下文a 2; b. 调用addAll 开始编译addAll函数体生成可执行代码并将上下文入栈 a. 更新函数上下文d 10; b. 调用add 开始编译add函数体生成可执行代码并将上下文入栈 a. 执行函数返回return b c ; // 9 add函数上下文被弹出栈更新addAll函数上下文result 9; addAll函数继续执行 a. 执行函数返回return a result d; // 21 add函数上下文被弹出栈无需更新全局上下文 全局上下文继续执行 执行完毕
总结js引擎通过执行上下文的变量环境来支持变量提升。
三、变量提升
在深入了解变量提升前需要先了解一下什么是作用域以及ES6出现的块级作用域。
一作用域
作用域是指在程序中定义变量的区域该位置决定了变量的生命周期。简单来说作用域就是变量与函数的可访问范围它控制着变量和函数的可见性和生命周期决定了变量在何处能够被访问在何时会被销毁。在ES6之前js只有全局作用域和函数作用域在ES6时为了解决变量提升带来的一系列问题引入了块级作用域。以下是在各个作用域中定义的变量的访问范围和生命周期
全局作用域 访问范围代码中的任何地方生命周期伴随页面的生命周期 函数作用域 访问范围在函数内部生命周期在函数执行时存在执行结束后被销毁 块级作用域 访问范围在{}代码块内部生命周期在执行{}代码块时存在执行结束后被销毁 二变量提升带来的问题
变量容易在不被察觉的情况下被覆盖掉
/** 示例代码 **/
var myname 1;
function showName () {console.log(myname);if (0) {var myname 2;}console.log(myname);
}
showName()/** 全局执行上下文 **/
//变量提升
function showName () {console.log(myname);if (0) {var myname 2;}console.log(myname);
}
var myname undefined;
//可执行代码
myname 1;
showName()/** 函数执行上下文 **/
//变量提升
var myname undefined;
//可执行代码
console.log(myname);
if (0) {myname 2;
}
console.log(myname);/** 输出结果 **/
undefined
undefined分析该代码在ES6出现前调用函数showName时开始编译showName函数体if条件中声明的myname由于没有块级作用域而被提升至函数顶部并放入函数执行上下文中的变量环境对象。在执行可执行代码时js优先从当前的执行上下文中查找变量。由于当前执行上下文中包含了变量myname值为undefined所以输出结果均为undefined。该段代码在有块级作用域的其他语言中输出结果应当均为1。
本应被销毁的变量没有被销毁
/** 示例代码 **/
function foo () {for (var i 0; i 7; i) {}console.log(i);
}
foo()/** 全局执行上下文 **/
//变量提升
function foo () {for (var i 0; i 7; i) {}console.log(i);
}
//可执行代码
foo()/** 函数执行上下文 **/
//变量提升
var i undefined
//可执行代码
for (i 0; i 7; i) {}
console.log(i);/** 输出结果 **/
7分析在创建函数执行上下文时变量i被提升当for循环结束后变量i并没有被销毁它存在函数执行上下文的变量环境对象中。这导致输出结果为7。而在有块级作用域的其他语言中for循环完毕变量应当被销毁。
三ES6如何修正变量提升
在最初设计js语言时设计者没有打算把这门语言设计得太复杂只是引入函数作用域和全局作用域忽略了一些块级作用域。这样如果变量或者函数在if块、while块里面因为它们没有作用域所以在编译阶段可以直接把这些变量或者函数提升到开头这样大大降低了设计语言的复杂性但也埋下了混乱的种子。通过上面变量提升带来的问题可以了解到在需要有块级作用域的时候js的运行结果与c、java等拥有块级作用域语言的运行结果截然不同。随着JavaScript的流行这门最初只为了让网页动起来而设计的语言逐渐暴露出更多的问题最终为了能够解决这些问题推出了ES6。
ES6在语言层面做了很大的调整但为了保持向下兼容就必须在支持旧规则的同时实现新的规则。针对变量提升来说我们知道在编译阶段会将变量、函数提升并存放于执行上下文的变量环境对象中在执行阶段通过在调用栈管理的一个个上下文环境的变量环境对象中查找目标变量/函数。可以说变量环境对象调用栈支持了变量提升。在ES6中为了保持兼容选择在词法环境中自行维护一个类似调用栈的小型栈来管理块级作用域。在编译阶段针对let、const关键字声明的变量都将被放置到词法环境中而var声明的变量或函数将被放置到变量环境对象中。
四从执行上下文角度分析块级作用域
通过代码示例分析ES6如何结合let关键字支持块级作用域 调用函数时开始编译函数体只编译一次 进行变量提升将a、c放入变量环境对象将处于函数内部且非处于块级作用域内的变量压入词法环境维护的小型栈作为栈底。 对于{}块级作用域对b、d进行提升并初始化为undefined形成的对象暂不压入栈中 foo函数体生成可执行代码如下 a 1;
b 2;
{b 3;c 4;d 5;console.log(a);console.log(b);
}
console.log(b);
console.log(c);
console.log(d);执行可执行代码更新变量环境对象a 1更新词法环境小型栈栈底b 2; 执行到{}代码块将块级作用域编译阶段形成的对象压入栈中作为栈顶。 更新词法环境小型栈栈顶b 3 从词法环境小型栈栈顶向下查找变量c找不到则进入变量环境对象查找c。更新变量环境对象c 4; 更新词法环境小型栈栈顶d 5 先查找词法环境再查找变量环境对象查找a打印值 打印b查找方式同10 {}代码块执行完毕将块级作用域编译阶段形成的对象从栈顶弹出。 继续执行函数体打印b先进入词法环境查找再进入对象环境查找。 打印c 打印d时因查找不到变量而抛出异常 最终输出结果1 3 2 4 抛出异常Uncaught ReferenceError: d is not defined
总结ES6通过在词法环境中维护一个类似调用栈的小型栈来支持块级作用域。当函数执行时会将函数最外层通过let、const声明的变量并且不处于块级作用域内的变量进行变量提升初始化为undefined然后压入栈底。当运行带{}代码块时再将该代码块通过let、const声明的变量追加入栈代码块执行结束时又将它弹出栈顶销毁。当需要查找变量时先从词法环境开始查找自栈顶向下查找若未命中目标将进入变量环境查找。
五暂时性死区
在编译阶段已经有词法环境变量也已经默认初始化为undefined但在let声明该变量前使用将会出现暂时性死区。如下
function foo () {//debuggerconsole.log(a, a);let a 1;
}
foo()通过断点可以观察到在编译阶段变量a被提升到函数顶并初始化为undefined但上述代码最终执行结果是抛出异常Uncaught ReferenceError: Cannot access ‘a’ before initialization而不是打印出undefined。这是js引擎做的一个控制通过let声明的变量即使在编译阶段被提升并且初始化为undefined了但就是不允许在代码执行到let声明前被访问。
总结暂时性死区是语法规定虽然通过let声明的变量在代码执行阶段已经存在词法环境中但在执行到对应的let声明前访问该变量js引擎就会抛出一个错误。