基础拾遗:Javascript的执行机制

132 阅读5分钟

JavaScript执行流程

变量和函数在代码里的位置是不会改变的,而是在编译阶段被JavaScript引擎放入内存中

showName()
console.log(myname)
myname = '极客时间'

编译之后生成执行上下文(context)和可执行代码。

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

  • JavaScript 代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为 JavaScript 代码在执行之前需要先编译。
  • 在编译阶段,变量和函数会被存放到变量环境中,变量的默认值会被设置为 undefined;在代码执行阶段,JavaScript
  • 引擎会从变量环境中去查找自定义的变量和函数。 如果在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖掉之前定义的。

Javascript中的栈

  • 当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份。
  • 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。
  • 当使用 eval 函数的时候,eval 的代码也会被编译,并创建执行上下文。
var a = 2
function add(){
var b = 10
return  a+b
}
add()

当执行add函数时就会有两个执行上下文---全局执行上下文和add函数执行上下文。这就意味着JS运行的同时可能会有多个上下文。

Javascript通过栈来管理执行上下文,成为执行上下文栈也叫调用栈。特点:后进先出

var a = 2
function add(b,c){
  return b+c
}
function addAll(b,c){
var d = 10
result = add(b,c)
return  a+result+d
}
addAll(3,6
  1. 创建全局上下文,将其压入栈底
  2. 调用addAll函数
  3. 当执行到 add 函数调用语句时,同样会为其创建执行上下文,并将其压入调用栈
  4. add函数返回

开发中查看函数调用栈关系

用trace函数查看栈关系

栈溢出

调用栈是有大小的,当入栈的执行上下文超出一定数目,JavaScript就会报栈溢出。尤其是写递归的时候。

function division(a,b){
    return division(a,b)
}
console.log(division(1,2)

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

let和var

function foo(){
    var a = 1
    let b = 2
    {
      let b = 3
      var c = 4
      let d = 5
      console.log(a)
      console.log(b)
    }
    console.log(b) 
    console.log(c)
    console.log(d)
}   
foo()

第一步,编译并执行上下文

  • 函数内部通过 var 声明的变量,在编译阶段全都被存放到变量环境里面了。
  • 通过 let 声明的变量,在编译阶段会被存放到词法环境(Lexical Environment)中。
  • 在函数的作用域内部,通过 let 声明的变量并没有被存放到词法环境中。

在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出,这就是词法环境的结构。需要注意下,我这里所讲的变量是指通过 let 或者 const 声明的变量。

闭包



在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。

this

当函数作为对象的方法调用时,函数中的 this 就是该对象; 当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window; 嵌套函数中的 this 不会继承外层函数的 this 值。

this是和执行上下文绑定的

三种方式绑定执行上下文的值

  1. 通过函数的 call 方法设置
  2. 通过对象调用方法设置
var myObj = {
  name : " 极客时间 ", 
  showThis: function(){
    console.log(this)
  }
}
myObj.showThis()

相当于

myObj.showThis.call(myObj)

注意

var myObj = {
  name : " 极客时间 ",
  showThis: function(){
    this.name = " 极客邦 "
    console.log(this)
  }
}
var foo = myObj.showThis
foo()
  • 在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window。
  • 通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。
  1. 通过构造函数中设置
function CreateObj(){
  this.name = " 极客时间 "
}
var myObj = new CreateObj()
// 相当于
 var tempObj = {}
  CreateObj.call(tempObj)
  return tempObj