前言
📫 大家好,我是南木元元,热爱技术和分享,欢迎大家交流,一起学习进步!
🍅 个人主页:南木元元
目录
作用域(Scope)
全局作用域
函数作用域
块级作用域
作用域链(Scope Chain)
执行上下文
执行上下文的类型
执行上下文栈
创建执行上下文
(1)创建阶段
(2)执行阶段
作用域与执行上下文区别
解释阶段
执行阶段
变量提升
本质原因
小练习
结语
作用域(Scope)
在JavaScript中,作用域(Scope)是指代码中变量、函数和对象的可访问性范围,决定了变量或函数在何处可以被访问或引用。
JavaScript中有3种类型的作用域,包括:
- 全局作用域
- 函数作用域
- 块级作用域(ES6新增)
全局作用域
在全局作用域中定义的变量在代码中任何地方都能访问到,常见的就是在最外层函数外面定义一个变量,该变量拥有全局作用域。
var globalVar = "I am global";
function test() {
console.log(globalVar); // "I am global"
}
test();
console.log(globalVar); // "I am global"
除了上述情形外,还有几种情况拥有全局作用域:
- 所有未定义直接赋值的变量自动声明为全局作用域
- 所有window对象的属性拥有全局作用域,如窗口名字window.name、页面URL地址window.location等
全局作用域有很大的弊端,过多的全局作用域变量会污染全局命名空间,容易引起命名冲突。
函数作用域
函数作用域是指变量和函数在函数内部定义时,只在函数内部可见和可访问,函数外部无法访问函数内部的变量和函数(每当调用一个函数时,都会创建一个新的函数作用域)。
function localFunction() {
var localVar = "I am local";
console.log(localVar); // "I am local"
}
localFunction();
console.log(localVar); // ReferenceError: localVar is not defined
块级作用域
使用ES6中新增的let和const指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中创建(由一对花括号{}包裹的代码片段),使用var
声明的变量不会有块级作用域。
if (true) {
let blockVar = "I am in block";
console.log(blockVar); // "I am in block"
}
console.log(blockVar); // ReferenceError: blockVar is not defined
作用域链(Scope Chain)
某个变量在当前作用域中找不到时,就会往它的上⼀级寻找,如果找到全局作用域还没找
到,就放弃寻找(返回 undefined),这种层级关系就是作用域链。
作用域链的作用就是保证对变量和函数的有序访问。
var globalVar = "I am global";
function outerFunction() {
var outerVar = "I am outer";
function innerFunction() {
var innerVar = "I am inner";
console.log(globalVar); // "I am global"
console.log(outerVar); // "I am outer"
console.log(innerVar); // "I am inner"
}
innerFunction();
}
outerFunction();
在上述代码中,innerFunction可以访问globalVar和outerVar,因为它们在外部作用域中。
需要注意的是,js采用的是静态作用域(词法作用域),函数的作用域在函数定义时就确定了(这里不再详细展开,推荐阅读JavaScript深入之词法作用域和动态作用域)
执行上下文
当JavaScript代码执行一段可执行代码时,会创建对应的执行上下文,执行上下文就是代码在执行过程中的环境信息。
执行上下文的类型
1.全局执行上下文:这是默认的执行上下文,当JavaScript代码开始运行时首先创建。全局执行上下文中的代码在全局作用域中执行,并且只有一个全局执行上下文。
2.函数执行上下文:每当一个函数被调用时,会创建一个新的执行上下文。每个函数都有自己的执行上下文,函数执行上下文在函数调用时被创建,并在函数执行完毕后被销毁。
3.eval函数执行上下文:eval
函数内部代码会创建一个独立的执行上下文,但不推荐使用eval
。
执行上下文栈
JavaScript引擎使用执行上下文栈来管理执行上下文。
当JavaScript执行代码时,首先遇到全局代码,会创建一个全局执行上下文并且压入执行栈中,每当遇到一个函数调用,就会为该函数创建一个新的执行上下文并压入栈顶,引擎会执行位于执行上下文栈顶的函数,当函数执行完成之后,执行上下文从栈中弹出,继续执行下一个上下文。当所有的代码都执行完毕之后,从栈中弹出全局执行上下文。
示例:
function f(a) {
console.log(a);
}
function func(a) {
f(a);
}
func(1);
假设用ESP指针来保存当前的执行状态,在系统栈中会产生如下的过程:
-
调用func, 将 func 函数的上下文压栈,ESP指向栈顶。
-
执行func,又调用f函数,将 f 函数的上下文压栈,ESP 指针上移。
-
执行完 f 函数,将ESP 下移,f函数对应的栈顶空间被回收。
-
执行完 func,ESP 下移,func对应的空间被回收。
图示如下:
创建执行上下文
创建执行上下文有两个阶段:创建阶段和执行阶段。
(1)创建阶段
在 JavaScript 代码执行前,执行上下文将经历创建阶段。主要包括:
- 创建变量对象(Variable Object, VO)
在ES5中称为变量对象,在ES6及以后称为词法环境组件,这个对象包含了执行环境中所有变量和函数。
对全局执行上下文,变量对象是全局对象;对函数执行上下文,变量对象包含函数的参数、局部变量和函数声明。
提升函数和变量声明:将函数声明提升到当前作用域的顶部,并存储在变量对象中;将变量声明提升到当前作用域的顶部,但不会初始化(初始化在执行阶段进行)。
- 建立作用域链
创建作用域链,包含当前执行上下文的变量对象和其父级执行上下文的变量对象,直到全局对象。
作用域链本质上是一个指向变量对象的指针列表,作用域链的前端始终都是当前执行上下文的变量对象,全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。
- 确定
this
绑定
在全局执行上下文中,this指向全局对象;在函数执行上下文中,this取决于函数的调用方式。
(2)执行阶段
完成对变量的分配,在变量声明阶段被提升的变量,现在会被赋值,最后执行代码。
作用域与执行上下文
JavaScript属于解释型语言,其执行分为解释和执行两个阶段。
解释阶段
- 词法分析:即分词,它的工作就是将一行行的代码分解成一个个token
- 语法分析:将上一步生成的token数据,根据一定的语法规则转化为AST
- 作用域规则确定
执行阶段
- 创建执行上下文
- 执行函数代码
- 垃圾回收
JavaScript解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。
作用域和执行上下文之间最大的区别是: 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。
变量提升
变量提升是指使用var声明的变量以及函数声明会被提升到作用域顶部, 变量提升的结果,就是可以在变量初始化之前访问该变量(返回undefined),在函数声明前可以调用该函数。
console.log(a); // undefined
var a = 5;
console.log(a); // 5
在上述代码中,尽管console.log(a)在var a = 5之前调用,但代码不会报错,因为在执行代码之前,变量已经被提升。
同样,函数声明也会被提升:
foo(); // "Hello, world!"
function foo() {
console.log("Hello, world!");
}
上述代码中,即使foo在函数声明之前调用,代码依然可以正常执行。
本质原因
变量提升的本质原因:js 引擎在代码执行前有一个解析的过程,创建了执行上下文,初始化了一些代码执行时需要用到的对象。当访问一个变量时,会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。
简单来说就是在执行JS代码之前,需要先解析代码。解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来,变量先赋值为undefined,函数先声明好可使用。这一步执行完了,才开始正式的执行程序。
注意点:
1.
let和const声明的变量不会被提升。如果在声明前使用它们,会报错。
2.函数声明会被提升,函数表达式不会被提升。
小练习
说出下面代码的执行结果。
var foo = function () {
console.log("foo1")
}
foo()
var foo = function () {
console.log("foo2")
}
foo()
function foo() {
console.log("foo1")
}
foo()
function foo() {
console.log("foo2")
}
foo()
这是一道关于变量提升的题,具体就不展开了,可以看这道面试题真的很变态吗?
结语
🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~