参考文章:
[译] 理解 JavaScript 中的执行上下文和执行栈
彻底明白作用域、执行上下文
JavaScript 执行上下文和执行栈
深入理解 JavaScript 作用域和作用域链
谈谈 JavaScript 的作用域
1. 作用域是什么?
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
例:
var variable_G = "global";
function fn() {
var variable_G = "function_global"
var variable_F = "function";
console.log(variable_G, variable_F);
}
fn(); // function_global function
console.log(variable_G); //global
console.log(variable_F); //ReferenceError: variable_F is not defined
变量 variable_F
只在 fn
中定义了,所以只能在 fn
函数作用域中使用,在全局作用域下使用则会报错未定义。而变量 variable_G
在 fn
和全局中都定义了,但 fn
调用的是自己内部定义的。
即:作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
2. 词法作用域和动态作用域
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。
因为 JavaScript 采用的是词法作用域——函数的作用域在函数定义的时候就决定了。
这就意味着,无论函数在哪里调用,取参数时都是到函数定义时的作用域中查找。
上面这两句话就是理解闭包的关键。
和词法作用域相对的就是动态作用域——函数的作用域在函数调用的时候才决定。
虽然JavaScript 采用词法作用域,但是 this
的指向就有点动态作用域的感觉。不过其实 this
指向是在创建执行上下文时确定。
经典的一道面试题
var a = 1
function out(){
var a = 2
inner()
}
function inner(){
console.log(a)
}
out() //====> 1
inner 函数在全局作用域中定义,虽然在 out 函数中调用,但是打印 a 变量的值时,还是先在定义时的作用域(即全局作用域)下查找。
3. 全局作用域和函数作用域、块级作用域
因为JavaScript 采用词法作用域,所以全局作用域和函数作用域都属于词法作用域
- 全局作用域
全局作用域下的变量,也可称为全局变量。浏览器环境下全局变量会自动挂载到
window
对象下
- 显式声明:在最外层函数外面用
var
关键字声明的变量都属于全局作用域
var variable = "global";
console.log(variable); //global
console.log(window.variable); //global
- 隐式声明:不带有声明关键字的变量无论在哪声明都属于全局作用域
function fn() {
variable_F = "function";
}
fn(); //要先调用 fn 这个时候就隐式声明了 variable_F
console.log(variable_F); //global
console.log(window.variable_F); //global
- 函数作用域
函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域。但是也有几种间接的方法访问。
1. 通过 return 返回函数内部变量访问
function returnVariable(params) {
let fn_variable = params;
returnfn_variable;
}
let out_variable = returnVariable("out_variable"); //====>out_variable
2. 通过闭包访问函数内部变量
function out_fn() {
let out_variable = "out";
return function inner_fun() {
let inner_variable = "inner";
return {
out_variable,
inner_variable
}
}
}
out_fn()(); //====> {out_variable:"out", inner_variable: "inner"}
同时还可以利用函数内部作用域内变量,外部是无法访问的这个特性来封装库函数,使它脱离全局作用域,从而形成一个单独的作用域。
(function(){
})();
// 如果需要传参,还可以这样写:(param代指形参,data代指实参)
(function(param){
})(data);
-
块级作用域
在 ES6 之前是没有块级作用域的,以for
循环为例:
// var 声明的变量没有块级作用域
for(var i=0; i<3; i++) {
var a = 3;
}
console.log(i); //====> 3
console.log(a); //=====> 3
// let 和 const 声明的变量存在块级作用域,花括号外无法访问花括号内部的变量
for(let i=0; i<3; i++) {
const a = 3;
}
console.log(a); //Uncaught ReferenceError: a is not defined
console.log(i); //Uncaught ReferenceError: i is not defined
4. 作用域链
认识作用域之前还有一个自由变量的概念,网上是这么解释自由变量的:
当前函数作用域内调用了一个变量 a,但是 a 变量并不在当前作用域内而是在上一级作用域内定义,那么这个变量 a 就是自由变量,以代码为例:
var a = 1;
function sum(b) {
return a + b;
}
sum(2);
//=======> 这个 a 就可以称之为自由变量
那么作用域链就可以从自由变量这里来拓展
var global_variable = "global";
function out_fn() {
var out_fnVariable = "out_fn";
console.log(global_variable);
//console.log(inner_fnVariable); ===> Error
function inner_fn() {
var inner_fnVariable = "inner_fn";
console.log(out_fnVariable);
console.log(global_variable);
}
inner_fn();
}
out_fn();
//====>
//global
//out_fn
//global
总结:函数在当前作用域找不到的变量,就会向定义这个函数的作用域去寻找(有些地方也称为父级作用域,其实是不严谨的,应该是去定义这个函数的作用域里寻找)这种层层向上,类似冒泡的结构就是作用域链。
网友评论