Skip to main content

词法作用域链、函数作用域、块级作用域

danger

此笔记有很多问题, 导致笔者有些混乱, 待笔者有空Review并重新整理

什么是变量?

变量是存储数据值的容器。在 JavaScript 中,对象和函数也是变量。

全局变量

函数之外声明的变量,会成为全局变量

全局变量的作用域是全局的:网页的所有脚本和函数都能够访问它。

局部变量

JavaScript 函数中声明的变量,会成为函数的局部变量

局部变量的作用域是局部的:只能在函数内部访问它们。

Note:

JavaScript 变量的有效期始于其被创建时。

局部变量会在函数完成时被删除。

全局变量会在您关闭页面是被删除。

什么是作用域?

变量和函数的可访问范围,即作用域控制着变量和函数的 可见性生命周期。词法环境就是作用域。

ES6之前, Javascript只有 全局作用域局部作用域, 之后新增了 块级作用域, 具体从let const中体现。

全局作用域(Global Scope)

不在任何函数内定义的变量就具有全局作用域。

  • window浏览器的全局对象,globalNode环境下的全局对象
  • 不在任何一个函数内定义的变量拥有全局作用域

局部作用域(Local Scope)

JavaScript的作用域是通过函数来定义的,所以每一个在函数内定义的变量只能拥有这个函数内的局部作用域 (除闭包外, 详见Javascript 闭包)。

块级作用域(block Statement)

用于组合零个或多个语句,该块由一对大括号{}界定, 具体从let const中体现。

关于 var let const的生命周期请移步 var let const function 生命周期

暂存死区

在讲let const这两个关键字前,我们先讲一个概念 TDZ(Temporal Dead Zone), 又称暂存死区

定义: 在相同的函数或块作用域内重新声明同一个变量会引发SyntaxError; 也称 TDZ(Temporal dead zone)。

作用: 在声明变量或常量之前使用它, 会引发ReferenceError,即不能在初始化之前,使用变量。

let

let允许你声明一个作用域被限制在 级中的变量、语句或者表达式。与 var 关键字不同的是, var声明的变量只能是全局或者整个函数块的。 varlet 的不同之处在于后者是在编译时才初始化(见下面)。

Note:

  • let has hoisting?
    • 提升是变量在作用域顶部的耦合声明和初始化阶段,let生命周期分离声明和初始化阶段, 产生了暂存死区, 所以此时变量无法访问。

const

常量是块级作用域,很像使用 let 语句定义的变量。常量的值不能通过重新赋值来改变,并且不能重新声明。

下面我们将边通过babelvar let const关键字的解析来理解babel对块级作用域的解析。反派总是死于话多,话不多说,我们直接上代码

let profile = ['Rain120', 'https://github.com/rain120'];
var i = 'global';

function testVar() {
// test the key about var
var i = 0;
for (var i = 1; i < profile.length; i++) {
console.log('var', profile[i]);
}
console.log('var', i);
}

function testLet() {
// test the key about let
let i = 0;
for (let i = 1; i < profile.length; i++) {
console.log('let', i, profile[i]);
}
console.log('let i', i);
}

function testConst() {
// test the key about const
const i = 0;
for (const i = 1; i < profile.length; i++) {
console.log('const', i, profile[i]);
}
console.log('const i', i);
}

// testVar();
// testLet();
// testConst();
// console.log('global', i);

babel解析

"use strict";

function _readOnlyError(name) {
throw new Error('"' + name + '" is read-only');
}

var profile = ["Rain120", "https://github.com/rain120"];
var i = "global";

function testVar() {
// test the key about var
var i = 0;

for (var i = 1; i < profile.length; i++) {
console.log("var", profile[i]);
}

console.log("var", i);
}

function testLet() {
// test the key about let
var i = 0;

for (var _i = 1; _i < profile.length; _i++) {
console.log("let", _i, profile[_i]);
}

console.log("let i", i);
}

function testConst() {
// test the key about const
var i = 0;

for (var _i2 = 1; _i2 < profile.length; _readOnlyError("i"), _i2++) {
console.log("const", _i2, profile[_i2]);
}

console.log("const i", i);
} // testVar();
// testLet();
// testConst();
// console.log('global', i);

运行结果

  • testVar

    scope-test-var.png

  • testLet

    scope-test-let.png

  • testConst

    scope-test-const.png

更多详情请移步 babeljs playground

通过上述代码,我们知道块级作用域的作用范围只能在它定义的{}内部使用。

函数作用域

在函数内声明的所有变量在函数体内始终是可见的,可以在整个函数的范围内使用及复用。

什么是作用域链?

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

tip

作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

我们知道执行上下文的生命周期如下图示

execution-context-lifecycle.png

更多关于执行上下文、变量对象等上述的相关问题请转到 Javascript 执行上下文 阅读。

作用域链是由当前环境与上层环境的一系列变量对象组成,它保证对执行环境有权访问的所有变量和函数的有序访问。

词法作用域

tip

词法作用域 就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。 -- 参考

参考资料

ECMA 262: sec-global-environment-records

what-is-lexical-scope

作用域 - Wiki

块级作用域

const

let

Execution context and the call stack — visually illustrated by a slice of tasty cake

Scope

JavaScript深入之作用域链

前端基础进阶(四):作用域与作用域链