js作用域和声明提升 !!!
哈喽哈喽,我是新手小白金樽清酒。相信大家在初学js的时候的总会遇到一个奇怪的问题,那就是变量提升。那什么是变量提升呢,就是在我们在我们将输出语句放在用var声明的一个变量之前编译器还能正常执行。有接触过其他语言的的大佬就知道,如c,c++,java等语言这样不就会报错嘛。因为程序是自上而下运行的,输出语句怎么会在声明语句之前呢?诶,别着急,这就是js的特别之处,让我们来一探究竟吧。
首先js是一门弱数据类型的语言
什么叫弱数据类型的语言呢?就是声明的时候不带上数据类型的,在我们给变量赋值之前,我们并不知道它是什么类型的。就在下面的这串代码,我声明了四个变量,在未赋值之前是不知道这些变量是什么数据类型的。相较于其他语言,如c,c++定义之前是不是就确定了变量的数据类型呢。比如,int a=10。前面的int 就会告诉我们它是整型。
那js是如何知道数据类型的呢?诶,别急,它有自己的小秘书。在执行之前,js首先会请秘书编译一下看不懂的东西。所以在执行之前它会进行编译,但是秘书只会编译老板需要的东西,所以啊,有一定的工具区间,不然看到老板的隐私就不好啦。诶,那就是作用域。
什么是作用域呢?
作用域就是限制一个变量在程序中的使用范围。一般分为三种,全局作用域,函数体作用域和块级作用域。 为什么要叫作用域呢?因为在js中分为两步编译和执行。编译和执行的顺序不同,所执行出来的结果也不同,我们就用作用域来解释。比如下面的代码。
大家觉得这个代码的结果是什么呢?大家可以自己去试一下,相信有很多小伙伴都会说执行了foo()函数,将a的值修改为2,最后的结果应该是2吧。其实结果是1。
我们来分析一下吧,前面我们提到先编译再执行首先编译全局变量,a=1,和foo函数名,但未执行函数,然后执行调用的foo()函数,foo是什么呢?诶,又得叫小秘书编译一下,是个函数就会形成自己的小作用域叫函数作用域,里面有什么呢?编译一下,a=2。诶,再往下执行,有个输出语句,输出那个a呢,是全局变量的,还是局部变量呢?看这个输出的位置是定义在全局的,所以输出的也是全局的变量a=1.因为我们要牢记,作用域只能从内到外不能从外到内。假如我们把输出放在函数里面,那结果就大不一样了。
从结果我们能大概理解一下啊全局作用域和函数作用域吧。让我们看一下从作用域从内到外吧
我们在函数体并没有声明变量a,但是输出变量a会发生什么结果呢?
结果是全局变量的a=1,因为在函数作用域没找到变量,就会从内向外找,于是找到了全局的变量a。所以我们要牢记,作用域只能从内向外,不能从外向内。接下来让我们看一下进阶版的吧。新手小白自己思考一下。
var的声明提升
刚才我们简单的了解了一下作用域,现在呀,我们就可以揭开声明提升的面目啦。声明提升到底是啥? 我们先看一个简单的例子
我们先执行,再声明会发生什么事情呢?我们会发现,根本不会报错,输出10。 为什么呢?因为var定义的变量会声明提升,等同于下面的代码
var a=10
console.log(a)//变量提升
再来一个更复杂一点的代码,来将作用域和声明提升结合一下 函数作用域var定义的也会变量提升
foo()
function foo(){
console.log(a)
var a=10
}
但是结果是undefined,为什么呢? 因为这里的变量提升只会将变量提升下去,而值再输出之后,所以是undefined,等同于下面的代码
foo()
function foo(){
var a
console.log(a)
a=10
}
通过上面的例子我们会明白了var定义的变量存在变量提升,那么有无解决办法呢?
用let定义取代var可以避免变量提升
let和var一样是来声明变量的,那么两者有什么区别呢?
- 1.let 不会声明提升
- 2.let 不能重复声明同一变量
让我们来举例一一看一下吧。同样的代码
console.log(a)
let a=10
执行结果:
js出现报错,为声明变量a,说明let声明的变量不会变量提升
再让我们看一下声明同一个变量
这是var声明,重复声明同一变量会覆盖
这是let声明的,会报错,提示重复定义,这更符合语法要求。
const 定义也不会发生声明提升
首先const定义有以下几个特点,我们简单的了解一下
-
1.不会声明提升
-
2.不能重复声明同一变量
-
3.声明的是一个常量,值不能被修改,必须初始化 让我们看下面的代码
由此可见,const也不会变量提升
欺骗词法作用域
- 1.eval() 让原本不属于这里的代码变成就是写在这里的代码
- 2.with 当修改对象中不存在的属性时,该属性会泄露到全局变量 我们先来看一下eval()
function foo(str){
eval(str)
var a=1
console.log(a,b)
}
foo('var b=2')
函数内并没有变量b,但通过eval()函数写过去了,让不属于这里的代码出现
再让我们看一下啊with
var obj={
a:1,
b:2,
c:3,
}
with(obj){
a=2,
b=3,
c=4
}
console.log(obj)
但是,with定义的变量在对象中没有的话会泄露到全局的位置
var obj={
a:1,
b:2,
c:3,
}
with(obj){
a=2,
b=3,
c=4
d=5
}
console.log(d)
执行结果:
变量d是定义在with里门的,但对象里面并没有d对象,已经泄露到全局了,所以可以输出
结语
在成为大佬的路上你我相伴,一份耕耘一份收获。在一次次的尝试中,总能发现新的知识,在千万次的代码敲打中,你我也在不断的变强。加油代码人。