JavaScript模块作用域

JavaScript模块化与作用域

介绍JavaScript作用域,包括全局作用域、函数作用域和块级作用域,以及ES6+新增的let、const和block scope等特性。

作用域和作用域链 - 静态

  • 作用域:一个代码段所在的区域
  • 作用:绑定变量在这个作用域,隔离变量,不同作用域下同名变量不会有冲突。
  • 作用域链:多个作用域嵌套,就近选择,先在自己作用域找,然后去就近的作用域找。

全局作用域
在代码的任何地方都能访问到的变量被定义在全局作用域。在浏览器环境中,全局变量被定义在window对象上。

函数作用域
在函数内部定义的变量只能在函数内部访问,这就是函数作用域。这意味着,如果你在一个函数内部定义了一个变量,那么这个变量在函数外部是不可见的。

块级作用域
ES6引入了两种新的声明方式:let和const,它们与var相比,最大的区别就是它们具有块级作用域。块级作用域是指变量在最近的{}代码块内有效。

let和const
let和const都是块级作用域,它们的作用范围被限制在最近的一对花括号{}内。let允许你重新赋值,而const定义的是一个常量,一旦赋值就不能改变。

执行上下文
抽象当前JavaScript的执行环境,包括变量、this指向等信息。每当JavaScript开始执行时,都在指向上下文中运行。

执行上下文的两个阶段:创建阶段和执行阶段
在创建阶段JavaScript将var和function声明移到顶层。
在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象。在全局上下文(window)确定后,将其添加到栈中(压栈)。在函数执行上下文创建之后,将其添加到栈中(压栈)。当前函数执行完成后,将栈顶的对象移除(出栈)。当所有的代码执行完成后,栈中只剩下window。

变量提升
通俗来说,变量提升是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的行为。变量被提升后,会给变量设置默认值为 undefined。

在 ECMAScript6 之前,JS 引擎用 var 关键字声明变量。在 var 时代,不管变量声明是写在哪里,最后都会被提到作用域的顶端。 下面在全局作用域中声明一个num 变量,并在声明之前打印它:

console.log(num)
var num = 1
// 输出undefined,因为变量的声明被提升了

除此之外,在函数作用域中也存在变量提升;

function getNum(){
  console.log(num)
  var num = 1
}
getNum()
// 输出undefined,因为函数内部的变量声明会被提升至函数作用域的顶端。

函数提升:

// 函数声明式:
function foo(){}
// 变量形式声明:
var fn = function() {}

变量提升导致的问题
由于变量提升的存在,使用JavaScript来编写的其他语言逻辑代码,都有可能会导致不一样的执行结果。主要有两种.
① 变量被覆盖

var name = 'JavaScript'
function showName(){
  consloe.log(name)
  if(0){
    var name = "CSS"
  }
}
showName()

这里会输出undefined,并没有输出“JavaScript”?
首先,当刚执行 showName 函数调用时,会创建 showName 函数的执行上下文。之后,JavaScript 引擎便开始执行 showName 函数内部的代码。首先执行的是:consloe.log(name)。执行这段代码需要使用变量 name,代码中有两个 name 变量:一个在全局执行上下文中,其值是JavaScript;另外一个在 showName 函数的执行上下文中,由于if(0)永远不成立,所以 name 值是 CSS。应该先使用函数执行上下文中的变量。 因为在函数执行过程中,JavaScript 会优先从当前的执行上下文中查找变量,由于变量提升的存在,当前的执行上下文中就包含了if(0)中的变量 name,其值是 undefined,所以获取到的 name 的值就是 undefined。

这里输出的结果和其他支持块级作用域的语言不太一样,比如 C 语言输出的就是全局变量,所以这里会很容易造成误解。

② 变量没有被销毁

function foo() {
  for(var i =0;i<5;i++){}
  console.log(i);
}
foo()

使用其他的大部分语言实现类似代码时,在 for 循环结束之后,i 就已经被销毁了,但是在 JavaScript 代码中,i 的值并未被销毁,所以最后打印出来的是 5。这也是由变量提升而导致的,在创建执行上下文阶段,变量 i 就已经被提升了,所以当 for 循环结束之后,变量 i 并没有被销毁。

禁用变量提升
为了解决上述问题,ES6 引入了 let 和 const 关键字,从而使 JavaScript 也能像其他语言一样拥有块级作用域。let 和 const 是不存在变量提升的。下面用 let 来声明变量:

console.log(num)
letnum =1
// 输出结果:Ucaught ReferenceError:numis not defined。 const 声明,也会是一样的结果——用 let 和 const 声明的变量,它们的声明生效时机和具体代码的执行时机保持一致。

ES6 是如何通过块级作用域来解决上面的问题:

function fn(){
  var num = 1;
  if(true) {
    var num = 2;
    console.log(num); // 2
  }
  console.log(num); // 2
}
fn()

在这段代码中,有两个地方都定义了变量 num,函数块的顶部和 if 的内部,由于 var 的作用范围是整个函数,所以在编译阶段只生成了一个变量 num,函数体内所有对 num 的赋值操作都会直接改变变量环境中的 num 的值。
把 var 关键字替换为 let 关键字,看看效果:

function fn(){
  let num = 1;
  if(true) {
    let num = 2;
    console.log(num); // 2
  }
  console.log(num); // 1
}
fn()
// 因为 let 关键字是支持块级作用域的,所以,在编译阶段 JavaScript 引擎并不会把 if 中通过 let 声明的变量存放到变量环境中,这也就意味着在 if 中通过 let 声明的关键字,并不会提升到全函数可见。

JS如何支持块级作用域

  1. 创建执行上下文
    通过 var 声明的变量,在编译阶段会被存放到变量环境中。通过 let 声明的变量,在编译阶段会被存放到词法环境中。在函数作用域内部,通过 let 声明的变量并没有被存放到词法环境中。
  2. 执行代码
    当作用域块执行结束之后,其内部定义的变量就会从词法环境的栈顶弹出。块级作用域就是通过词法环境的栈结构来实现的,而变量提升是通过变量环境来实现,通过这两者的结合,JavaScript 引擎就同时支持了变量提升和块级作用域。

作用域链
当你在一个函数内部尝试访问一个变量时,JavaScript会首先在当前的作用域查找。如果没有找到,它会去外层的作用域查找,直到找到为止。这就是作用域链。

var outerVar = "I'm outer!";
function innerFunction() {
  console.log(outerVar); // 输出 "I'm outer!"
}
innerFunction();

闭包
闭包是JavaScript中一个重要的概念。当一个函数能够记住并访问所在的词法作用域,即使该函数在词法作用域外部执行,这就产生了闭包。

functionouterFunction() {
  var outerVar = "I'm outer!";
  function innerFunction() {
    console.log(outerVar); // 输出 "I'm outer!"
  }
  return innerFunction;
}

var myFunction = outerFunction();
myFunction(); // 即使在outerFunction()执行完后,innerFunction()仍然可以访问outerVar,这就是闭包
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

简 。单

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值