手机如何建立网站:阅读器中 JavaScript 的执行机制
本文摘要: 目录变量提高调用栈作用域链闭包this变量提高实践上变量和函数声明在代码里的方位是不会变的,并且是在编译阶段被 JavaScript 引擎放入内存中,一段 JavaScript 代码在执行之前需要被 JavaScript 引擎编译,编译完成之后,才会进

目录

  • 变量提高
  • 调用栈
  • 作用域链
  • 闭包
  • this

变量提高


实践上变量和函数声明在代码里的方位是不会变的,并且是在编译阶段被 JavaScript 引擎放入内存中,一段 JavaScript 代码在执行之前需要被 JavaScript 引擎编译,编译完成之后,才会进入执行阶段。大致流程为:JavaScript 代码片段 ——> 编译阶段 ——> 执行阶段—>

编译阶段,每段执行代码会分为两部分,第一部分为变量提高部分的代码,第二部分为执行部分的代码。通过编译后,生成执行上下文(Execution context)和 可执行代码


执行上下文 是 JavaScript 执行一段代码时的运转环境,比如调用一个函数,就会进入函数的执行上下文,从而确定该函数执行期间用到的如 this、变量、对象以及函数等。

执行上下文由 变量环境(Variable Environment) 和 **词法环境(Lexical Environment)**对象 组成,变量环境保存了代码中变量提高的内容,包括 var 界说和 function 界说的变量。而词法环境保存 let 和 const 界说块级作用域的变量。


块级作用域就是通过词法环境的栈结构来完成的,而变量提高是通过变量环境来完成,通过这两者的结合,JavaScript 引擎也就同时支撑了变量提高和块级作用域了

变量查找过程:沿着词法环境的栈顶向下查询,假如在词法环境中的某个块中查找到了,就直接返回给 JavaScript 引擎,假如没有查找到,那么继续在变量环境中查找。

变量声明提高补充:
  • var的创建和初始化被提高,赋值不会被提高。
  • let的创建被提高,初始化和赋值不会被提高。
  • function的创建、初始化和赋值均会被提高。

调用栈


调用栈是用来管理函数调用关系的一种数据结构。在函数调用的时分,JavaScript 引擎会创建函数执行上下文,而全局代码下又有一个全局执行上下文,这些执行上下文会使用一种叫的数据成果来管理。

所以 JavaScript 的调用栈,其实就是 执行上下文栈 。举例代码执行,入栈如图所示:
var a = 2function add(b,c){  return b+c}function addAll(b,c){var d = 10result = add(b,c)return  a+result+d}addAll(3,6)


调用栈既然是一种数据结构,所所以存在巨细的,超出了栈巨细就会呈现栈溢出报错,比如斐波那契数列,执行10000次,超过了最大栈调用巨细(Maximum call stack size exceeded)。
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {  if( n <= 1 ) {return ac2};  return Fibonacci2 (n - 1, ac2, ac1 + ac2);}Fibonacci2(10000) // Maximum call stack size exceeded

该函数是递归的,虽然只有一种函数调用,可是仍是会一直创建执行上下文压入调用栈中,导致超过最大调用栈巨细报错,可以通过 Chrome 调式看到 Call Stack 的状况



总结:
  • 每调用一个函数,JavaScript 引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后 JavaScript 引擎开始执行函数代码。
  • 假如在一个函数 A 中调用了另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶。
  • 其时函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈。
  • 当分配的调用栈空间被占满时,会引发“仓库溢出”问题。

所以,斐波那契数列函数优化的手法就是使用循环来减少函数调用,从而减少函数执行上下文的创建压入栈的状况,就能够解决栈溢出的报错了。(递归尾部优化无法解决问题,Chrome阅读器仍是栈溢出),使用蹦床函数来解决:
function runStack (n) {  if (n === 0) return 100;  return runStack.bind(null, n- 2); // 返回本身的一个版本}// 蹦床函数,防止递归function trampoline(f) {  while (f && f instanceof Function) {    f = f();  }  return f;}trampoline(runStack(1000000))


可以看到,调用栈中一直是坚持3个执行上下文罢了,多余的都及时的pop掉了。

作用域链


每一个执行上下文的变量环境中,都包括了一个外部引用,用来指向外部的执行上下文,我们把这个外部的引用称为 outer

当一段代码使用一个变量是,JavaScript 引擎首要会在“其时的执行上下文”中查找该变量,假如找不到就会继续在 outer 所指向的执行上下文中查找。我们把这个查找的链条就称为作用域链

词法作用域


词法作用域就是指作用域是由代码中函数声明的方位来抉择的,所以词法作用域是静态的作用域,通过它就可以够猜测代码在执行过程当中怎么查找标识符。词法作用域是代码阶段抉择好的,和函数是怎么调用的没有关系。

块级作用域中的变量查找

  • 从其时执行上下文的词法环境,自顶向下查找(栈中的内存块),然后再从其时执行向下文中的变量环境中查找;
  • 查找不到,则继续在outer指向的执行上下文继续顺次先从词法环境,再到变量环境查找。

闭包


有词法作用域的规则可以知道,内部函数总是可以拜访他们的外部函数中的变量,当外部函数执行完毕后,pop stack了,遗留下了外部环境构成的闭包 Closure 环境,该环境内存中还保存着那些可以拜访的变量,类似一个专属背包,除了内部函数拜访,气氛方式无法拜访该专属背包,我们就包这个背包称为外部函数的闭包(那些内部函数引用外部函数的变量仍然保存在内存中,我们把这些变量的集合称为闭包)。

闭包是怎么回收的


假如引用闭包的函数是一个全局变量,那么闭包会一直存在知道页面关闭;假如这个闭包今后不再使用的话,就会形成内存泄漏。

假如引用闭包的函数是一个部分变量,等函数毁掉后,下次 JavaScript 引擎执行废物回收时,判断闭包这块内容假如不再被使用了,那么 JavaScript 引擎的废物回收器就会回收这块的内存。

使用闭包的原则:假如闭包会一直使用,那么它可以作为全局变量而存在;但假如使用频率不高,并且占用内存有比较大的话,那就尽量让它成为一个部分变量。

this

let a = { name: 'this解释' }function foo() {  console.log(this.name)}foo.bind(a)() // => 'this解释''






参考资源:《阅读器的工作原理与实践》极客时间-李兵

【免责声明】本文仅代表作者或发布者个人观念,不代表(www.lmnkf.cn)及其所属公司官方发声,对文章观念有疑义请先联络作者或发布者自己修正,若内容触及侵权或违法信息,请先联络发布者或作者删除,若需我们协助请联络平台管理员,Emailcxb5918(本平台不支撑其他投诉反馈渠道,谢谢合作)。若需要学习以上相关常识请到巨推学院观看视频教程,网站地址www.tsllg.cn。

相关内容