JS执行上下文和作用域

四糸乃赛高 Lv.5 冰封大地
62 2021/7/27笔记


作用域

作用域是变量可访问的范围,主要作用是减少命名冲突。

JS中有全局作用域、函数作用域、块作用域(ES6)。


全局作用域:

直接在script标签或单独的js文件中最顶层写的代码,都属于全局作用域。全局作用域在页面打开时创建,页面关闭时销毁。全局作用域中的对象可以在任意位置访问。

// index.js
var a = '该变量属于全局作用域'

function myFun(){
   console.log('该函数也属于全局作用域')
}


函数作用域:

在函数内部的代码属于函数作用域,函数作用域内的变量只能在该函数内访问。调用函数时会创建该作用域,当函数执行完毕后会销毁该作用域,每次创建的作用域都独立存在,互不干扰。闭包比较特殊,后面解释。

// index.js
var a = '该变量属于全局作用域'

function myFun(){
   console.log('该函数也属于全局作用域')
   var b = '但该函数内的变量属于函数作用域'
}
myFun()
console.log(b) // 报错:Uncaught ReferenceError: b is not defined


块作用域:

ES6中新增的概念,块作用域由 { } 包括,if语句和for语句里面的{ }属于块作用域,使用let关键字可定义块作用域,使用let关键字定义的变量只在定义的作用域有效,

例1

{
  var a = "var"
  let b = "let"
}
console.log(a)
console.log(b)

// 结果:
// var
// Uncaught ReferenceError: b is not defined


// 该循环输出10个10
for(var i = 0;i<10;i++){
    setTimeout(()=>console.log(i))
}

// 该循环输出0-9
for(let i = 0;i<10;i++){
    setTimeout(()=>console.log(i))
}

例3

// 运行结果:
// test
// ReferenceError: test1 is not defined
if(true){
    var test = 'test'
}
console.log(test)

if(true){
    let test1 = 'test1'
}
console.log(test1)



上下文

执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。执行上下文的生命周期包括三个阶段:创建阶段→执行阶段→回收阶段。 执行上下文有且只有三类,全局执行上下文,函数上下文,eval上下文。
创建阶段(当函数被调用,但未执行任何其内部代码之前)会做以下三件事:创建变量对象:首先初始化函数的参数arguments,提升函数声明和变量声明。创建作用域链、确定this指向。
一个函数执行的时候,会产生一个属于自己的执行环境。环境里面有一个变量对象variable object(VO),OA里面存放着环境中定义的所有变量和函数,作用域链(scope chain),this。
函数执行,环境产生被推入环境栈,函数执行完,环境出栈并被销毁(闭包例外),把控制权返回给之前的执行环境。

全局上下文:在浏览器中为window对象,this指向window,在全局上下文中定义的变量都能通过windows.变量名访问,setTimeout、setInterval、console等常用方法,都属于全局上下文。
函数上下文:当函数被调用时会创建一个函数上下文,函数运行结束后该上下文会销毁,多次调用同一个函数会创建多个函数上下文。


函数调用栈

栈遵循"先进后出,后进先出"的规则,或称LIFO ("Last In First Out") 规则。


程序执行进入一个执行环境时,它的执行上下文就会被创建,并被推入执行栈中(入栈);程序执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文。因为JS执行中最先进入全局环境,所以处于"栈底的永远是全局环境的执行上下文"。而处于"栈顶的是当前正在执行函数的执行上下文",当函数调用完成后,它就会从栈顶被推出(理想的情况下,闭包会阻止该操作)。"全局环境只有一个,对应的全局执行上下文也只有一个,只有当页面被关闭之后它才会从执行栈中被推出,否则一直存在于栈底。"

这里就不码字直接搬运大佬的案例了。

function foo () { 
    function bar () {        
      return 'I am bar';
    }
    return bar();
}
foo();

出栈入栈图解:



执行上下文可存在多个,虽然没有明确的数量限制,但如果超出栈分配的空间,会造成堆栈溢出。

常见于递归调用,没有终止条件造成死循环的场景。可使用尾递归进行优化,防止栈溢出


个人的理解:

作用域和上下文区别:

作用域相当于一个范围,规定变量只能在这个范围中使用。

而上下文则是定义了变量,变量属于这个上下文。

关于闭包:

function func(){
    let count = 0;
    return function(){
        return ++count;
    }
}

let f = func()

console.log(f()) // 1
console.log(f()) // 2

上面的例子使用函数func创建了一个闭包,返回一个函数,赋值给了f,该函数返回内部变量count自增长的结果。

由于f属于全局作用域,只有全局作用域被销毁时才会销毁,又因为func返回的函数调用了fanc作用域中的count变量,所以func执行完后作用域不会销毁。


参考文章:https://www.jianshu.com/p/6f8556b10379





# JavaScript

评论

后参与评论
还没有评论,来说点什么吧~