昨天我写了一个函数,内部定义的变量在外部怎么也访问不到!就像是变魔术一样消失了,原来是‘作用域’在搞鬼。
1. 变量是什么
在 JavaScript 中,变量是用于存储数据的容器。我们可以使用 var、let 和 const 关键字来声明变量。
2 .js代码的执行过程
词法分析--> 拆解代码为'词语'
-例let age = 18 --> [let, age, =, 18]
语法分析--> 构建抽象语法树
-检查语法错误,构建代码结构
生成可执行代码--> 转化为机器指令
3.作用域是什么
在JavaScript中,作用域是指变量和函数的可访问性,即在代码中可以访问这些变量和函数的部分。
JavaScript主要有两种作用域类型:全局作用域和局部作用域。但是在es6版本之后,又引入了块级作用域。
全局作用域--> 程序的顶层空间
函数作用域--> 函数内部的独立王国
块级作用域--> 包裹着的领土
访问规则:全局作用域 <-- 函数作用域 <-- 块级作用域
内层作用域可以访问外层作用域,外层不能访问内层
根据上诉的规则我们来举几个例子
var a = 1
function foo(){
console.log(a)
}
foo()
运行结果:1
在函数作用域中找不到变量a, 于是访问外层作用域,也就是全局作用域,找到了变量a, 并且值为1 ,于是打印a的值。
那大家想一想,下面这段代码是否会报错
console.log(a)
var a = 1
运行结果:undefiend
哎呀!不对呀!其实输出结果就是undefined。其实这就得牵扯到 var let const三者的区别
4. 变量声明进阶对比
1.var 声明的变量会存在声明提升(将变量的声明提升到当前作用域的顶部)
就是这个例子,用 var 声明的变量会声明提升,所以当执行输出时,全局有变量a, 但是此时还未赋值,所以打印结果为undefiend。
console.log(a)
var a = 1
其实这份代码就等同于:
var a
console.log(a)
a = 1
运行结果:undefiend
let和const声明的变量不会声明提升
console.log(a)
let a =1
运行结果:报错
2. var可以重复声明变量
var a = 1
function(){
var a = 2
console.log(a)
}
运行结果:2
因为a在函数作用域已经找到了, 所以的值为2。甚至在js里面可以不进行声明,直接写a = 1都是可以的,js会默认给你声明成var。
let 和const不可以重复声明变量,否则报错
var a = 1
let a = 2
运行结果:
3. let 和const 的区别
let和const的区别就在于其定义的变量的可变性:const定义的变量是不可变的,而let可以.
5.块级作用域
直接拿例子说话:
{
let a = 1
}
console.log(a)
运行结果:a is not defiend
其实就是let 与 {} 就形成了一个块级作用域,你要打印a, 即使全局没有声明,也不能从外往内找,相当于a 没有声明。换成一个简单的函数作用域作为参考:
function foo()
{
var a = 1;
}
console.log(a);
运行结果:a is not defiend
6.欺骗词法
aval()案例:
function foo(str, a) {
eval(str); //var b = 3;
console.log(a, b);
}
foo('var b = 3',1);
大家对eval()这个函数肯定非常陌生,其实这行代码就是把 var b =3 这行代码从全局作用域搬到了函数作用域当中,起到了一个欺骗作用。
with(obj){}案例:
var obj = {
a: 1,
b: 2,
c: 3,
}
with(obj){
a =2
b =3
c =4
}
console.log(obj)
运行结果:
这个函数作用是批量修改对象的键值对,但是我们发现,要是with(){}出现了对象中没有的属性呢?那么with修改其属性会导致其泄露到全局,但是不会增加在此对象中。
var o2 = {
b: 2,
}
// var a = 2
function foo(obj) {
with(obj){
a = 2; //在全局声明了
}
}
foo(o2);
console.log(o1);
console.log(o2);
console.log(a);
运行结果:
结语
最后的魔法咒语:
"不是变量在消失,而是作用域在生效;
不是引擎太玄妙,而是规则未参透。
从var的迷雾走向let的清明,
便是从小白到法师的飞升之路!"