探索 JavaScript 作用域:解析变量访问的奥秘
在JavaScript的学习过程中,理解作用域的概念是至关重要的。本文将深入探讨不同类型的作用域及其规则,以及一些常见的误区和误用。
作用域
js中的作用域分为三种
- 全局作用域
- 函数作用域
- 块级作用域
我们来看一段代码
var a=1
function foo() {
var b=2
console.log(a)
}
foo();
console.log(b)
直接运行,结果如下
执行了函数中的 console.log(a)输出a后,执行console.log(b)报错了 所以可以很明显的看出,作用域间的访问规则是:
内部作用域可以访问外部作用域,反之则不行
块级作用域
全局作用域和函数作用域比较好理解,这里我们重点介绍一下块级作用域
- 块级作用域是 {} + let || const形成的
如if{ }中let或const声明变量形成块级作用域,只要有{}且里面用let或const声明了变量,就会形成一个块级作用域。
但要特别注意的是,只有{}中用let和const声明的变量才会形成块级作用域,并不会影响到{}其它如用var声明的变量
举个栗子
{
let a = 1
var b = 2
}
console.log(b);
console.log(a);
可以发现b可以正常输出,而输出a时则报错了,这点需要特别注意。
暂时性死区
关于let和const还有一个想和大家唠的点,就是let和const是ES6新增的关键字,而ES6明确规定,如果块作用域存在let或者const声明的变量,这个块对这些变量会形成封闭作用域,let和const不会变量提升(关于变量提升大家可以去看我写的有关预编译的那篇文章,这里可以简单理解变量提升就是执行前把变量声明提到所处作用域顶部),在let和const执行之前不可以访问到,这个区域就叫做暂时性死区(TDZ temporal dead zone)
比如下面的代码
let c = 1
{
console.log(c)//先找自己家的,自己家有所以不能访问外面的,但自己家的又访问不到
let c = 2//暂时性死区
}
很多人肯定会觉得输出1对吧,我们运行一下试试看
答案是报错了,这是因为console.log(c)要输出c,肯定先到自己所处的作用域查找对吧,因为let在作用域形成的暂时性死区,又访问不到c,但自己家确实有,所以不能访问外面的,就比如你有女朋友,但她现在生气不理你,你也不能到外面沾花惹草,所以只能苦苦报错了。
词法作用域
词法作用域并不是一种新的作用域类型,而是一种作用域的工作原理和规则。
词法作用域(Lexical Scope)是指作用域在代码编写时就已经确定,而不是在运行时动态决定的。换句话说,词法作用域是指变量的作用范围基于变量的声明位置,而不是函数的调用位置。
欺骗词法作用域
虽然词法作用域在大多数情况下是静态的,但有一些方法可以“欺骗”它:
eval() 函数
eval()
函数可以动态执行字符串形式的代码。它能够让代码在执行时“看起来”好像是定义在当前作用域内的,但这会导致性能问题和难以维护的代码。
function foo(a,str) {
eval(str);
console.log(a,b);//输出1 2
}
foo(1,'var b=2') ;
with语句
有关作用域最后想和大家说的就是with语句,算是作用域考点中的一个坑吧,with
语句是一种可以简化对对象属性访问的方法。它允许将对象的所有属性临时作为局部变量使用,这样就不需要重复写对象名称,如下图所示
var obj ={
a:1,
b:2 ,
c:3
}
console.log(obj)
with(obj) {
a=3
b=4
c=5
}
console.log(obj)
with
语句让我们可以直接使用 obj
对象的属性 a
、b
和 c
,而不需要写 obj.a
、obj.b
、obj.c
。不就是个简化代码的方法吗,能有什么猫腻。我们再来看一段代码
function foo(obj) {
with(obj) {
a=2
}
}
var obj = {
b:2
}
foo(obj)
console.log(obj)
console.log(a)
我们用with
语句修改对象中的a
为2,但实际上对象obj
中并没有属性a
,输出对象obj
,确实没什么影响,但我们输出a的时候却有值为2,全局不是不能访问函数作用域内的变量吗,这就是with语句的问题了
- 当with(){}修改对象中不存在的属性时,这个属性会泄露到全局
结论
理解作用域的机制对于编写高效、可维护的JavaScript代码至关重要。通过掌握词法作用域的原理以及如何正确使用var
、let
和块级作用域,开发者可以避免常见的陷阱,并编写出更具安全性和可读性的代码。同时,尽量避免使用eval()
和with
等可能破坏作用域规则的特性,以确保代码的性能和安全性。
转载自:https://juejin.cn/post/7399835971996942390