JavaScript作用域

前言

在我们了解作用域之前,首先要知道,作用域并不是 JavaScript 独有的概念,在许多编程语言中都有作用域的概念,如:C/C++、C#、Python、Java、JavaScript、Pascal、Emacs Lisp、Common Lisp……

作用域模型分为两种

静态作用域(词法作用域)

静态作用域又叫词法作用域,采用词法作用域的变量叫做词法变量。静态作用域是根据变量/函数定义的位置来确定变量/函数的可见、可访问区域。
大多数程序设计语言都是采用静态作用域规则,如 C/C++、C#、Python、Java、JavaScript……

动态作用域

动态作用域,采用动态作用域的变量叫做动态变量。动态作用域是根据函数调用的位置来确定函数的可见、可访问区域。
采用动态作用域的语言有 Pascal、Emacs Lisp、Common Lisp(兼有静态作用域)、Perl(兼有静态作用域)。C/C++ 是静态作用域语言,但在宏中用到的名字,也是动态作用域。

作用域(scope)

JavaScript 中采用的是静态作用域(词法作用域)
JavaScript 中的作用域是根据变量或函数定义的位置的词法环境来确定的。(this 的指向是在运行时决定的。)
作用域是在解释阶段确定的,并且不会改变。执行上下文是在运行阶段确定的,随时可能改变。
作用域: 当前的执行上下文及其闭包。变量和函数可见或可被引用的上下文。如果变量或函数不在当前作用域范围中,则无法使用。变量/函数可见和可被有效访问的区域,这个范围就是作用域。
作用域有三种:

  • 全局作用域。
  • 函数作用域(局部作用域)。
  • 块作用域。

全局作用域

全局作用域是包含所有其他作用域的作用域,是根作用域。在全局作用域中声明的变量称为全局变量,可以在任何其他作用域(函数作用域、块作用域)中访问和更改。
父级作用域中定义的变量可以在子级作用域中访问,但父级不能访问其子级作用域的变量。
.js 文件或 script 标签中的代码默认都是在全局作用域中的。
定义在全局作用域中的函数,可以访问定义在全局作用域中的所有变量。
全局作用域在浏览器环境下指向的是 window,在 nodejs 环境叫做 global。

// global scope
function fn() {
  console.log('hello');
}

var a = 1;

不使用 var let const 关键字声明的变量会被默认为属于全局作用域的全局变量。

function fn() {
  animal = 'dog';
}
fn();
console.log(animal); // dog

函数作用域(局部作用域)

每个函数都有自己的作用域,并且在每次调用函数时都有不同的作用域,其中的变量存在于当前作用域中,只能在当前作用域内对变量进行赋值、获取、操作,外部访问不到。
局部作用域中的变量叫做局部变量。
在函数中定义的函数,可以访问其父函数与父函数所能访问的所有变量及全局变量。

function fn() {
  // 函数的局部作用域,这里的a是局部变量,在外部访问不到
  var a = 1;
  console.log(a); // 1
}
console.log(a); // a is not defined

块作用域

在 ES6 之前 JavaScript 是没有块作用域的,像 if、for、while、switch 语句中的块语句部分 {} 定义的变量是可以被外部访问到的,相当于定义在了全局作用域中。
在 ES6 中引入了块作用域的概念,使用 let、const 在块语句中会形成块作用域。
let 将变量的作用域限制在块语句内,var 声明的变量的作用域是在全局作用域或函数内。

{
  // 使用块语句{}来形成块级作用域,只有用let、const声明的变量在块语句中才会形成块级作用域
  let a = 1;

  // var声明的变量是不会形成块级作用域的,可以被外部访问
  var b = 2;
}
console.log(a); // a is not defined
console.log(b); // 2

模块化中的作用域

在 Node.js CommonJS modules 以及原生 ECMAScript modules 中,每个模块都有自己的作用域。顶层变量被声明在模块(module)的作用域内,而不是以属性的形式被添加到全局对象中。

作用域可以嵌套

作用域是可以嵌套的,我们可以在 foo 函数内创建一个 bar 函数或创建一个块语句。当存在嵌套作用域时,内部作用域可以访问外部作用域中的变量。但是外部作用域无法访问内部作用域中的变量。
下面例子中,foo 函数的作用域是 bar 函数的外部作用域,bar 函数可以访问到 foo 函数中的变量和函数。

function foo() {
  var a = 'hello';
  function bar() {
    var b = '你好';
    console.log(`${a}-${b}`); // hello-你好
  }
}

作用域链(scope chain)

作用域链是 JavaScript 查找变量的方式,是由多个作用域对象连续引用形成的链式结构。
作用域链是在执行上下文的创建阶段的变量对象之后创建的,作用域链本身包含变量对象。
JavaScript 在查找变量的时候只会向上查找,不会向下或横向查找。

作用域链: 当访问一个变量/函数时,会先从当前所处的作用域中查找。如果没有的话,会往父级作用域查找。如果还没有就继续往父级的父级查找,一直找到全局作用域。如果找到就返回变量的值/调用函数,如果还没有找到的话就抛出异常 xx is not defined / xx is not function。像这样链式查找的过程就形成了作用域链。

下边例子中,会首先从 bar 函数自身的作用域中去查找变量 a ,自身没有就去上级作用域 foo 函数中找,foo 中也没有找到,最后在全局作用域中找到了变量 a,即输出 a 。
像这样:bar作用域 -> foo作用域 -> Global作用域,逐级向上查找。

var a = 'scope chain';

function foo() {
  return function bar() {
    console.log(a);
  }
}

foo()(); // scope chain;

关于块语句{}

块语句通常用于流程控制,如 if、for、while、switch 等。
在 ES6 之前,JavaScript 没有块作用域。块语句中声明的变量可以被外部访问到。块语句不定义范围。
在 ES6 中引入了块作用域的概念。var是没有块作用域的,在块语句中声明的变量属于全局变量,所以在外边可以访问到。在块语句中使用 let、const 声明的变量会在块语句中形成块作用域,此时的变量是局部变量,外部不可访问。

{
  var a = 1;
  
  let b = 2;
}
console.log(a); // 1
console.log(b); // b is not defined

解析下面的例子:

  • {} 并不是 if 的一部分,在这里表达的是语句块。(MDN 文档有说明,{} 不是必须的。如果要执行多个语句,需要将多个语句放在 {} 里。具体可以文章末尾关于 if…else 的 MDN 文档链接)。
    • var 是形成不了块作用域的,所以语句块中的变量 a 可以被外部访问到。
    • 这里如果把 var 换成 let,let 形成了块作用域,外部就访问不到变量 a 了。
  • {} 是函数定义的一部分,function name(){} 组成了函数的整体结构。
    • 函数有自己的局部作用域。外部访问不到内部的变量。
// 为什么 if 这里的 a 在外边可以访问到
if(true) {
  var a = 1;
}
console.log('a--', a); // 1

// function 里边的 b 在外边不能访问到
function fn() {
  var b = 2;
}
console.log('b--', b); // b is not defined

扩展(for语句)

for语句的括号(){},如果使用 let 声明变量 i ,那么就会在块语句 {} 中形成块作用域。
这个例子也证明了:let 将变量的作用域限制在块语句内,var 声明的变量的作用域是在全局作用域或函数内。

for(var i=0; i<5; i++) {
  var dog = 'Shiba Inu';
}
console.log(i) // 5
console.log(dog) // Shiba Inu

/** -------- */

for(let i=0; i<5; i++) {
  var dog = 'Shiba Inu';
}
console.log(i) // error: i is not defined
console.log(dog) // Shiba Inu

相关链接

作用域 - MDN
Scope_(computer_science) - 维基百科
作用域-维基百科中文
静态作用域与动态作用域
Control_flow_and_error_handling
if…else
Empty 空语句
function函数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值