前言:作用域和执行上下文之间最大的区别是: 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。
作用域
-
定义:指的是代码在运行过程中能够发生作用的范围。其在代码编写时就已经确定。
-
作用:隔离变量,让变量不会向外暴露出去的区域。不同作用域下,同名变量不会冲突。
-
分类:全局作用域、函数作用域、块级作用域。
(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
- 变量提升
- 定义:指变量或者方法会被提升到最顶部。表现为可以在声明变量之前使用该变量。
- 分类: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、创建词法环境、创建变量环境。其中创建变量环境会将所有的变量定义到一个变量对象中,变量的赋值还是在执行语句的时候,即执行上下文时进行。因此,在创建变量环境时就发生了变量提升。
执行阶段:该阶段执行上下文栈会参与,每当调用一个函数时,该函数对应的函数执行上下文都会进行入栈,随后进行出栈执行。
执行上下文
-
定义:指的是代码执行时候的环境,是在运行时确定。其包含了作用域,在代码执行前会创建执行上下文,其通过执行上下文栈来进行管理。
-
分类:全局执行上下文、函数执行上下文、eval函数执行上下文。
(1)全局执行上下文:执行全局代码时,会创建全局执行上下文
(2)函数执行上下文:执行含函数时,会创建函数执行上下文
(3)eavl函数执行上下文:特别的,eavl函数在执行时,会创建一个特殊的执行上下文,称之为eva函数执行上下文。
- 生命周期(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页关闭或浏览器关闭时才会出栈并等待垃圾回收机制回收。