JavaScript 作用域与执行上下文

JavaScript的作用域(scope)和执行上下文(execution context)总是纠缠不清,以至于网上出现了大量文章来区分这两个概念。

MDN中是这样描述scope的:
Scope
The current context of execution. The context in which values and expressions are “visible” or can be referenced. If a variable or other expression is not “in the current scope,” then it is unavailable for use. Scopes can also be layered in a hierarchy, so that child scopes have access to parent scopes, but not vice versa.
Scope(作用域)
当前的执行上下文。值 (en-US)和表达式在其中 “可见” 或可被访问到的上下文。如果一个变量 (en-US)或者其他表达式不 “在当前的作用域中”,那么它就是不可用的。 作用域也可以根据代码层次分层,以便子作用域可以访问父作用域,通常是指沿着链式的作用域链查找,而不能从父作用域引用子作用域中的变量和引用。

MDN文档认为Scope就是当前的执行上下文。

而在stackoverflow中,相关问题中赞同数最高的回答是这样的:

A context of a function is the value of this for that function
函数的执行上下文就是该函数this的值。

最后我还是接受了@Bernie维尼的观点@余光作用域执行上下文相关观点,并结合了自己的理解,欢迎大家讨论。

1. 作用域

作用域,即某变量是可被获取的一个区域。一个作用域,包含了该区域中的变量,常量,函数等定义信息和赋值信息,以及这个区域内代码书写的结构信息。
JavaScript采用的作用域是词法作用域(lexical scoping),也就是静态作用域:

  • 函数的作用域在函数定义时就决定了。
    与之对应的还有一个动态作用域:
  • 即函数的作用域是在函数调用时才决定的。

观察以下代码:
在这里插入图片描述

  • global scope包含一个变量name1和一个函数sayName,所以在global scope中,name1和sayName是可用的;
  • sayName scope也包含了一个变量name2和一个函数say,所以在sayName scope中,name2和say是可用的;
  • say scope包含了一个变量name3,在say scope中,name3是可用的。

我们可以说name1的作用域为global scope

当使用到一个变量时,会首先在当前作用域中查找该变量,如果在当前作用域没有找到该变量,会向上级作用域递归查找,直到global scope,如果仍未找到,那么返回undefined

假如我们在say scope中需要使用name1,首先会在当前作用域say scope查找name1,没有找到后,在上级作用域sayName scope中查找,仍然没有找到,继续向上查找,最后在global scope中找到了name1。如果我们在say scope中需要使用name4,由于在say scope->sayName scope->global scope都没有找到,那么返回undefined

形如say scope->sayName scope->global scope这样的链状形式我们称之为作用域链,值得注意的是,作用域链是由下级到上级的单向结构,所以,如果我们在global scope中需要使用name2或者name3,将得到undefined

而在上文中的global scope我们称为全局作用域,全局作用域中的变量称之为全局变量。
注意: 所有未定义而直接复制的变量自变量也是全局变量

var name1 = 'Niall'
function sayNmae(){
  let name2 = 'Horan'
  function say(){
    let name3 = 'Adam'
    name4 = 'Jams'
  }
}
console.log(name4)

输出:

Jams

在上文中我们提到过,每个函数在定义之时,其作用域就以及确定了,随之确定还有该函数的作用域链
观察下面的函数:

var name = "Niall"
function sayName(){
  var name = 'Jams'
  function say(){
    console.log(name)
  }
  return say
}
var sayJams = sayName()
sayJams()

输出:

Jams

分析:

  • global scope含有一个变量name
  • 执行sayName()返回了say函数,并将say函数赋值给sayJams
  • 调用sayJams(),其内容为console.log(name),由于sayJams作用域内不存在name变量,所以会沿着作用域链递归查找name。
  • 而一个函数在定义时,其作用域链就已经被决定了,所以sayJamssay的作用域链一样(因为是将say赋值给了sayJams),而say的作用域链为say scope->sayName scope->globel scope,在sayName scope中找到了name为Jams。
  • 最后输出Jams

2. 执行上下文(execution context)

执行上下文中包含了一个重要的值,那就是this的指向。
什么是执行上下文?

  • 每一段代码块(被封装成函数的代码)都对应着一个执行上下文,而“全局”也被视作一段代码块。
  • 每当程序进入某个代码块时,一个新的执行上下文就会被创建,并被装入到执行上下文栈中,当程序退成该代码块,对应的执行上下文就被弹出。
  • 当程序在某段代码块中运行到某个点需要转到了另一个代码块时(调用了另一个函数),那么当前的可执行上下文的状态会被置为挂起,然后生成一个新的可执行上下文放入 stack 的顶部。
    在这里插入图片描述
    观察下面代码:
console.log(1);

function father() {
    console.log(2);
    (function child() {
        console.log(3);
    }());
    console.log(4);
}
father();

console.log(5);//会依次输出1,2,3,4,5

分析它的执行栈尽力了什么:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值