十万个Web前端面试题之作用域与上下文

171 阅读4分钟

本来想写闭包的,写着写着,突然想到闭包是和作用域相关的,那应该先写个作用域的。

概念

作用域,就是指在一个变量或函数,可以在指定的范围内被执行,这个范围就叫做这个变量或函数的作用域。

语言的作用域通常分为静态作用域和动态作用域

  • 静态作用域:静态作用域是指在代码在定义时,通过语法分析阶段就确定了,不会改变。

  • 动态作用域:动态作用域是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。

JavaScript是属于静态作用域。为什么?我们来看一个非常经典的作用域测验:

var animal = 'is cat'
function cat() {
  console.log(animal)
}
function dog() {
  var animal= 'is dog'
  cat()
}
dog()
// 结果是啥呢?

大家可以在浏览器里面跑一下,上面的结果是is cat,怎么理解呢?

如果它是静态作用域,它执行dog()方法后,进入里面执行cat()方法,在cat方法里面,打印animal时,它先在cat作用域里面找animal,发现没有定义,就直接去全局作用域里面找animal了,于是打印is cat

如果它是动态作用域名,在cat方法执行时没找到animal的话,会从调用函数的作用域查找,也就是dog里面找,所以输出会是is dog

作用域(Scope)

JavaScript的作用域有三个,全局作用域、局部作用域、块级作用域

1、全局作用域

这个就是说全局可访问,在浏览器下,就是直接挂载在window对象下面的属性,都是全局作用域的

// 全局作用域
var name = '张三' // 定义全局变量name
console.log(name)
function showName() {
  console.log(name) // 可以访问全局变量
}
showName() // 张三

2、局部作用域

一般在函数内容通过变量声明的,都是局部作用域,一个函数定义,就重新定义了一个作用域,不过父函数的局部变更是可以被子函数使用的。

全局和局部作用域,就相关于国家法律和公司法规,公司里面,肯定还是要在国家法律的规定内执行的,但A公司的公司法规,就不能放到B公司执行了。

function callLi() {
  var name = '李四'
  console.log(name)
}
function callZh() {
  console.log(name)
}
callLi() // 李四
callZh() // undefined

3、块级作用域

这个其实是ES6引入let和const关键字后加的,使用let和const关键字声明的变量,就会形成一个块级作用域

if (true) {
  let name = '张三'
  const sex = '男'
}
console.log(name) // name is not defined
console.log(sex) // sex is not defined

上下文(Context)

很多人把作用域和上下文误解为相同的概念,但事实并非如此。上下文是用来指定代码某些特定部分中 this 的值。作用域是指变量的可访问性,上下文是指 this 在同一作用域内的值。

反正,你记得,那个上下文本就是this在同一作用域内的值。

当然,在ES6之前,由于函数调用后作用域的变化,会导致上下文指向的变化,所以一般会使用call(),apply(),bind()来改变上下文本,但处理过程比较不友好,所以ES6中,我们直接使用箭头函数来处理。

如果你感觉上下文你总是记不住调用了哪个对象,那你坚持一个原理:this是在执行时确认,且永远指向最后调用它的那个对象

下图是网络上找的一张上下文指导图,大家可以参考下:

执行上下文(Execution Context)

JavaScript引擎在执行每个函数实例时,会创建一个执行上下文,执行期上下文有创建和代码执行的两个阶段。

1、创建阶段

  • 创建激活对象

  • 创建作用域链

  • 设置上下文值this

2、执行阶段

即代码执行

执行上下文栈

而多个执行上下文,又形成了执行上下文栈,这边从网上找了一张图,更生动

作用域链(Scope Chain)

在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JavaScript 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量或其他任何资源为止。作用域链可以简单地定义为包含其自身执行上下文的变量对象的对象,以及其父级对象的所有其他执行期上下文,一个具有很多其他对象的对象。

这边通过一个简单的函数说明下作用域链

function add(num1, num2) { 
  var sum = num1 + num2
  return sum
 }