词法作用域链、函数作用域、块级作用域
此笔记有很多问题, 导致笔者有些混乱, 待笔者有空Review并重新整理
什么是变量?
变量是存储数据值的容器。在 JavaScript
中,对象和函数也是变量。
全局变量
函数之外声明的变量,会成为全局变量。
全局变量的作用域是全局的:网页的所有脚本和函数都能够访问它。
局部变量
在 JavaScript
函数中声明的变量,会成为函数的局部变量。
局部变量的作用域是局部的:只能在函数内部访问它们。
Note:
JavaScript
变量的有效期始于其被创建时。
局部变量会在函数完成时被删除。
全局变量会在您关闭页面是被删除。
什么是作用域?
变量和函数的可访问范围,即作用域控制着变量和函数的 可见性 和 生命周期。词法环境就是作用域。
ES6
之前, Javascript
只有 全局作用域 和 局部作用域, 之后新增了 块级作用域, 具体从let
const
中体现。
全局作用域(Global Scope)
不在任何函数内定义的变量就具有全局作用域。
window
是浏览器的全局对象,global
是Node
环境下的全局对象- 不在任何一个函数内定义的变量拥有全局作用域
局部作用域(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
声明的变量只能是全局或者整个函数块的。 var
和 let
的不同之处在于后者是在编译时才初始化(见下面)。
Note:
let has hoisting?
- 提升是变量在作用域顶部的耦合声明和初始化阶段,
let
生命周期分离声明和初始化阶段, 产生了暂存死区, 所以此时变量无法访问。
- 提升是变量在作用域顶部的耦合声明和初始化阶段,
const
常量是块级作用域,很像使用 let 语句定义的变量。常量的值不能通过重新赋值来改变,并且不能重新声明。
下面我们将边通过babel
对var
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
testLet
testConst
更多详情请移步 babeljs playground
通过上述代码,我们知道块级作用域的作用范围只能在它定义的{}
内部使用。
函数作用域
在函数内声明的所有变量在函数体内始终是可见的,可以在整个函数的范围内使用及复用。
什么是作用域链?
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
我们知道执行上下文的生命周期如下图示
更多关于执行上下文、变量对象等上述的相关问题请转到 Javascript 执行上下文 阅读。
作用域链是由当前环境与上层环境的一系列变量对象组成,它保证对执行环境有权访问的所有变量和函数的有序访问。
词法作用域
词法作用域 就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。 -- 参考
参考资料
ECMA 262: sec-global-environment-records
Execution context and the call stack — visually illustrated by a slice of tasty cake