这样学,你就知道什么是作用域和执行上下文了

103 阅读6分钟

前言:作用域和执行上下文之间最大的区别是: 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。

作用域

  1. 定义:指的是代码在运行过程中能够发生作用的范围。其在代码编写时就已经确定。

  2. 作用:隔离变量,让变量不会向外暴露出去的区域。不同作用域下,同名变量不会冲突。

  3. 分类:全局作用域、函数作用域、块级作用域。

    (1)全局作用域:定义在最外层的函数、变量具有全局作用域。

function foo(params) {
  var name = '小明' // 函数作用域,只在当前函数内部生效
  hobby = 'coding' // 全局作用域,会被挂载到window对象上面
}
foo();
console.log('name', name) // undefined
console.log('hobby', hobby) // coding

(2)函数作用域:函数内部就是函数作用域。

(3)局部作用域:let、const定义的变量,能够影响的范围便是块级作用域。如

// 此处的i时全局作用域
for (var i = 0; i < 10; i++) {
  let count = i; // 使用let声明的变量具有块级作用域
  var countNext = i + 1; // 此处的countNext时全局作用域
}
console.log('i', i) // 10
// console.log('count', count) // ReferenceError: count is not defined
console.log('countNext', countNext) // 10
  1. 变量提升
  • 定义:指变量或者方法会被提升到最顶部。表现为可以在声明变量之前使用该变量。
  • 分类:var定义的变量,let、const定义的变量,函数表达式、函数申明

(1)var定义的变量:会进行变量提升,提升的结果是函数声明被提升到最顶端,赋值还是在原语句处执行。

function foo() {
    console.log(name); // undefined,由于变量提升,name声明被提升到了最顶端,即var name = undefined;因此打印结果为undefined
    var name = '小明'; // 真正的赋值还是在此处进行
}
foo();

(2)let、const定义的变量:不会进行变量提升,具有块级作用域,存在暂时性死区(即无法在定义语句之前使用)。特别的,const定义的变量不能在被重新赋值。

// let和const具有暂时性死区
console.log(name); // name is not defined
console.log(age; // age is not defined
let name = 'xiaoming'
const age = 19;

// let和const定义的变量具有块级作用域
for (let i = 0; i < 10; i++) {
	const count = i;
}
console.log(count); // count is not defined

if (true) {
	let age = 19;
}
console.log(age); // age is not defined

(3)函数声明:函数声明会进行整体提升。表现为可以在函数声明之前调用函数。

foo();
// 函数声明会整体提升到函数最顶部,因此可以在声明之前执行函数。
function foo() {
	console.log('foo');
}

(4)函数表达式:提升规则和var定义的变量提升规则相同。在语句之前无法调用函数。

foo(); // foo is not function
// 函数表达式提仅将声明替身,而不是像函数声明一样整体提升
var foo = function() {
	console.log('foo);
}
  • 变量提升的原因:js代码运行的时候会生成执行上下文,执行上下文就是代码运行时候的环境。执行上下文运行期间分为两个阶段:创建阶段和执行阶段。

    创建阶段:该阶段会主要做3件事情:绑定this、创建词法环境、创建变量环境。其中创建变量环境会将所有的变量定义到一个变量对象中,变量的赋值还是在执行语句的时候,即执行上下文时进行。因此,在创建变量环境时就发生了变量提升。

    执行阶段:该阶段执行上下文栈会参与,每当调用一个函数时,该函数对应的函数执行上下文都会进行入栈,随后进行出栈执行。

执行上下文

  1. 定义:指的是代码执行时候的环境,是在运行时确定。其包含了作用域,在代码执行前会创建执行上下文,其通过执行上下文栈来进行管理。

  2. 分类:全局执行上下文、函数执行上下文、eval函数执行上下文。

(1)全局执行上下文:执行全局代码时,会创建全局执行上下文

(2)函数执行上下文:执行含函数时,会创建函数执行上下文

(3)eavl函数执行上下文:特别的,eavl函数在执行时,会创建一个特殊的执行上下文,称之为eva函数执行上下文。

  1. 生命周期(3个阶段):创建阶段、执行阶段、回收阶段

(1) 创建阶段:创建阶段一般会做3件事。绑定this、创建词法环境、创建变量环境。我们常说的变量提升就是发生在创建变量这个阶段中。

  • 绑定this:始终指向当前函数的调用者。通过call、apply、bind可以进行修改。

tips:确定this的方法: 函数调用:this指向window对象; 方法调用:this指向当前调用对象; 若通过new生成:this指向当前的实例对象; call、apply、bind修改:this指向当前传入的对象; 箭头函数:始终指向上下文对象

  • 创建词法环境:一种包含标识符-》变量的一种映射结构。主要包含环境记录(Environment Record)和外部引用(Outer)两部分。
  • 创建变量环境:也是一种词法环境。他和词法环境的区别在于前者用于let和const的绑定,后者主要用于var定义的变量绑定。

(2)执行阶段:执行阶段会涉及到执行上下文执栈操作,全局执行上下文首先会入栈,接着是被调用的函数执行上下文入栈。每次代码执行时,会从栈顶弹出对应的执行上下文。执行阶段主要进行变量赋值及代码逻辑执行。

(3)回收阶段:回收阶段主要是将执行上下文出栈,随后等待垃圾回收器回收变量及函数。全局执行上下文只会在退出浏览器或关闭tab页面的时候出栈。

总结

本文主要讲述了作用域及执行上下文,两者最大的区别在于作用域在变量、方法定义时就已经确定,而执行上下文在代码执行时确定。

对于作用域而言,分为全局、函数、局部作用域,特别地注意for循环当中var定义的变量享有外层作用域。const、let定义的变量具有块级作用域,并且不存在变量提升、具有暂时性死区、const定义的变量不能重新赋值等特点。函数申明与函数表达式提升的区别,前者会将整体都进行提升,后者只是进行申明提升。同名的函数声明及变量,函数申明的提升优先级要高于变量申明。

对于执行上下文而言,其主要分为全局、函数及eval函数执行上下文3种。其生命周期包含创建、执行、回收3个阶段。其中创建阶段主要做了3件事情:绑定this、创建词法环境、创建变量环境。而词法环境又包括环境记录及外部引用两部分。变量组件其实也是词法环境,区别在于两者记录不同关键字定义的变量。执行阶段需要执行上下文栈参与。全局执行上下文只有在tab页关闭或浏览器关闭时才会出栈并等待垃圾回收机制回收。