Javascript执行上下文

一、JavaScript的执行机制:先编译,再执行

1、JavaScript代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为JavaScript代码在执行之前需要先编译。

2、在编译阶段,变量和函数会被存放到变量环境中,变量的默认值会被设置为undefined;在代码执行阶段,JavaScript引擎会从变量环境中去查找自定义的变量和函数。

举例如下:编译时候函数和变量的初始化,执行阶段的赋值和执行

showName()
function showName(){
console.log(1)
}
var showName=function(){
console.log(2)
}
showName()

//编译阶段,showName函数体存入变量环境
//showName变量存入变量环境,赋值undefined
//执行阶段,第一个showName()查找到函数体直接执行
//输出结果:1
//执行到第二个showName()时,showName变量已经赋值了function(){console.log(2)}
//输出结果:2

二、调用栈就是用来管理函数调用关系的一种数据结构

1、当JavaScript执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份

2当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束之后,创建的函数执行上下文会被销毁。

3、当使用eval函数的时候,eval的代码也会被编译,并创建eval执行上下文

chrome 查看调用栈“call stack”

Javascript执行上下文_第1张图片

 

 

 除了通过断点来查看调用栈,你还可以使用console.trace()来输出当前的函数调用关系

 

三、块状作用域

正是由于JavaScript存在变量提升这种特性,从而导致了很多与直觉不符的代码,这也是JavaScript的一个重要设计缺陷。(var作用域 scope)

作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。 

在ES6之前,ES的作用域只有两种:全局作用域和函数作用域。

  • 全局作用域中的对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。
  • 函数作用域就是在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁。

块级作用域就是使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域。

//if块
if(1){}

//while块
while(1){}

//函数块
function foo(){
 
//for循环块
for(let i = 0; i<100; i++){}

//单独一个块
{}

在函数执行过程中,JavaScript会优先从当前的执行上下文中查找变量。

 

ES6引入了let和const关键字,从而使JavaScript也能像其他语言一样拥有了块级作用域。

使用let关键字声明的变量是可以被改变的,而使用const声明的变量其值是不可以被改变的。  

 

JavaScript是如何支持块级作用域的

在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出,这就是词法环境的结构。

 Javascript执行上下文_第2张图片

 

 

由于JavaScript的变量提升存在着变量覆盖、变量污染等设计缺陷,所以ES6引入了块级作用域关键字来解决这些问题。

 let和const要在声明之后才能使用,很多人称这个为“暂时性死区”!

 

每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为outer,我们把这个查找的链条就称为作用域链。JavaScript执行过程中,其作用域链是由词法作用域决定的。

 

词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。

词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系。

Javascript执行上下文_第3张图片

 

 

 

 

 

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

换句话说,JavaScript中跟闭包对应的概念就是“函数”,它绑定环境,环境是执行环境,它需要处理处理this、变量声明、with等等一系列的复杂语法问题。

在最新的ES2018中,执行上下文又变成了这个样子,this值被归入lexical environment,但是增加了不少内容。

  • lexical environment:词法环境,当获取变量或者this值时使用。
  • variable environment:变量环境,当声明变量时使用
  • code evaluation state:用于恢复代码执行位置。
  • Function:执行的任务是函数时使用,表示正在被执行的函数。
  • ScriptOrModule:执行的任务是脚本或者模块时使用,表示正在被执行的代码。
  • Realm:使用的基础库和内置对象实例。
  • Generator:仅生成器上下文有这个属性,表示当前生成器。

Javascript执行上下文_第4张图片

 

 

 

如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。

如果引用闭包的函数是个局部变量,等函数销毁后,在下次JavaScript引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么JavaScript引擎的垃圾回收器就会回收这块内存。

 

this机制

在对象内部的方法中使用对象内部的属性是一个非常普遍的需求。但是JavaScript的作用域机制并不支持这一点,基于这个需求,JavaScript又搞出来另外一套this机制。

希望你能区分清楚作用域链和this是两套不同的系统,它们之间基本没太多联系。

Javascript执行上下文_第5张图片

 

 

 this是和执行上下文绑定的,也就是说每个执行上下文中都有一个this。

全局执行上下文中的this是指向window对象的。

 

 

使用对象来调用其内部的一个方法,该方法的this是指向对象本身的。

其实,你也可以认为JavaScript引擎在执行myObject.showThis()时,将其转化为了:myObj.showThis.call(myObj)

 

通过构造函数中设置

function CreateObj(){
  this.name = "极客时间"
}
var myObj = new CreateObj()

其实,当执行new CreateObj()的时候,JavaScript引擎做了如下四件事:

  • 首先创建了一个空对象tempObj;
  • 接着调用CreateObj.call方法,并将tempObj作为call方法的参数,这样当CreateObj的执行上下文创建时,它的this就指向了tempObj对象;
  • 然后执行CreateObj函数,此时的CreateObj函数执行上下文中的this指向了tempObj对象;
  • 最后返回tempObj对象。

为了直观理解,我们可以用代码来演示下:

 var tempObj = {}
  CreateObj.call(tempObj)
  return tempObj

1. 嵌套函数中的this不会从外层函数中继承

 第一个 this指向myobj对象,第二个函数里的this指向是window对象

var myObj = {
  name : "极客时间", 
  showThis: function(){
    console.log(this)
    function bar(){console.log(this)}
    bar()
  }
}
myObj.showThis()

你可以通过一个小技巧来解决这个问题,在showThis函数中声明一个变量 that用来保存this,然后在bar函数中使用that,其实,这个方法的的本质是把this体系转换为了作用域的体系。

你也可以使用ES6中的箭头函数来解决这个问题

var myObj = {
  name : "极客时间", 
  showThis: function(){
    console.log(this)
    var bar = ()=>{
      this.name = "极客邦"
      console.log(this)
    }
    bar()
  }
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)

这是因为ES6中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的this取决于它的外部函数。

 

 

首先,在使用this时,为了避坑,你要谨记以下三点:

 

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

 

最后,我们还提了一下箭头函数,因为箭头函数没有自己的执行上下文,所以箭头函数的this就是它外层函数的this。

 

你可能感兴趣的:(Javascript执行上下文)