前言.
作为一名刚刚接触JavaScript的萌新小白,我们今天来聊点"干货"----作用域。
总览
- 全局作用域
- 函数作用域
- 欺骗词法作用域
正文.
在聊作用域之前,我们要搞清楚浏览器是怎么怎么执行我们的js代码,上代码:
javascript
var a = 1
function foo()
{
console.log(a);
}
foo()
这是一段很简单的代码,那么浏览器到底是怎么识别我们的js代码的呢?
- 首先,浏览器要使用我们的js代码,一定是先**编译**(编译要干的是找到当前域中的有效标识符 )再执行,从** 上到下**执行。
- 当我们的浏览内置的V8引擎(假设浏览器内置的引擎为V8引擎)执行我们代码时,首先对我们的代码进行**编译**
- 当V8引擎执行到第一行,它会识别到我们第一行,定义了一个变量a =1。
- 紧接着来到我们的第二行,V8引擎它会识别到我们这里定义了一个函数。在这里,V8引擎没有执行这一段函数
- 再往下一行,这里我们V8引擎发现,这一行是一个函数调用,现在就开始执行了,它就从上开始寻找我们调用的函数,找到了foo()这一段,开始执行函数内部的代码。
到这里关于浏览器是怎么识别我们的js代码就结束了,开始步入正题----作用域
什么是作用域?
一般来说,作用域表示表示了变量作用的范围,规定了变量能够在哪一块使用。在javascript中,有三个作用域:全局作用域,函数作用域,块级作用域(es6新出的作用域)
1.全局作用域
全局作用域是指函数、变量、常量等对象的作用范围在整个应用程序中都是可用的。上代码:
javascript
var a = 1
function foo()
{
console.log(a);
}
foo()
这的输出结果是什么?毫无疑问这里将会输出 1,因为a变量的声明在整个代码块中都起作用,这样的变量我们称为:作用在全局作用域
2.局部作用域
在局部作用域中,我们首先看代码来方便我们理解:
javascript
var a = 1
function foo(){
var a = 2
console.log(a)
}
foo()
这里的输出结果是啥呢?看结果:
javascript
2
为什么这里输出的是2而不是1呢?我们要知道:
js是弱类型,动态语言,要先对代码进行编译,代码的编译发生在代码执行的前一刻,编译理清楚规则,让v8引擎读懂,只有当函数域调用时,才会被编译,函数调用语句直接执行,作用域里面找不到,再往外层作用域进行查找。
变量的查找是可以从内作用域往外作用域查找的(一层一层)
所有当我们执行foo()函数内部的代码,它要打印变量a的值,它会先从自身的函数域出发,如果没有找到该变量,再跳到全局作用域进行查找该变量,如果在函数域已经找到了该变量,便无需跳到外部作用域了。
函数作用域和全局作用域的关系就好比你家,和你家所在小区,把变量比作你的包裹,你要找到你的包裹,首先肯定是在自己家找,在自己家找到了的话,就无需出门,如果没找到,你就得到小区的快递收发站拿包裹。
再看代码:
javascript
function foo(){
var a = 2
}
foo()
console.log(a)
当a定义在函数作用域当中,我们这样的代码会输出什么?
javascript
ReferenceError: a is not defined
这里会直接执行报错!!!为什么?因为在js语言当中,变量的查找不能由外部作用域到内部作用域。
也就是说,我们使用的变量要在调用的作用域 或者父级作用域当中进行定义!!
我们再看一组实例:
javascript
var b = 1
function foo(a,b){//这里的参数是形参
//编译要干的是找到当前域中的有效标识符
console.log(a+b)
}
foo(2)//里面的参数实参,实参传到形参
我们定义了一个带参的方法foo(a,b),但是我们调用的时候会出现什么结果?
javascript
NaN
这里的输出结果为NaN,为什么这里显示的 这样的结果?
当我们调用foo()方法时,我们只给它返回一个实参,a=2,而b的值没有定义,所有在function中b变量为undefined
这就导致我们要输出的是一个2+undefined,至此,结果就变为了NaN
注意!NaN也是个值,代表没有值,是Number类型
3.块级作用域
下面我们来到块级作用域。
上才艺!
javascript
var a = 1
var a = 2
console.log(a)//可以用var重复声明对象
javascript
2
好,在这一段简短的代码,我们用var重复定义a变量,但是程序没有报错,识别了最后一次定义的a = 2,这就是js中var一个鸡肋的点,变量名可以重复定义。
假如某一天我拿到了一段代码,我要在后面进行补充和完善,我们用var定义变量,哎,一不小心,定义了一个与前文一样的变量名,这就糟糕了,可以会导致整段代码发生逻辑性错误!
所有,我们这里引入了 let 进行变量声明
4.let语句
首先还是,看代码:
javascript
let a1 =1
let a1 =2
console.log(a1)//let 声明的变量无法重复声明对象
这里我的代码会直接报错!原因是:无法重新声明块范围变量"a1"
块范围变量?什么是块范围变量?
也就是说当我们使用 let 语句声明变量时,该变量只作用在这一块作用域或者说范围当中,在其他作用域或者外部作用域无法使用该变量,这样的变量就称为:块范围变量,这样的作用域就称为:块级作用域
而且,这个块作用域的范围,就是{}包裹let的区间。
javascript
var a = 2
function foo(){
if(true){
let a= 1
}//let和{}括号结合会形成块级作用域
}
console.log(a)
foo()
输出:
javascript
2
在这一段代码当中,let和{}会形成一个块级作用域,a = 1只能在这一段if语句中进行调用!
好,说完基本的内容,我们接下来进行一些补充!
补充!!!
1.暂时性死区
上代码:
javascript
let a1 = 1
function foo(){
console.log(a1)
// var a1 = 2//编译的时候,不会执行赋值,声明提升
let a1 = 2//暂时性死区
}
foo()
输出:
javascript
ReferenceError: Cannot access 'a1' before initialization
这里直接执行会直接报错,因为let a1 = 2语句在输出语句之后,导致输出语句在执行的时候,找不到a1这个变量,(甚至是找不到变量,更不用说值了!),这样的 let 赋值语句的情况,就被称之为:暂时性死区
上述代码中,也提到了声明提升!下面我们就了解一下声明提升是何含义 。
2.声明提升
看代码:
javascript
function foo(){
console.log(a1)
var a1 = 2//编译的时候,不会执行赋值,声明提升
}
foo()
输出:
javascript
undefined
为什么这里的输出是undefined?而不是出现暂时性死区的情况?
这是因为 var 语句在定义变量时,会导致a1变量在全局进行了定义,只有当执行到这一行时才会进行赋值。
也就是说:在执行过程中,我已经知道了,一个声明提升的var 定义了一个a1作用在全局,但是我还没给它赋值,所以在输出时,我有变量而无值,所以最后的输出的结果是:undefined
最后!!补充一个:欺骗词法作用域
词法作用域,是词法所在的域
欺骗词法作用域是指通过某些机制在运行时修改词法作用域。下面列举一些常见的机制
1.eval()语句
上代码:
javascript
function foo(str,a){
eval(str)//把str代表的代码,搬到这里
console.log(a,b)
}
foo('var b =2',1)
输出:
javascript
1 2
啊?为什么这里会输出(1,2)?这是因为eval()语句把str代表的代码放在eval(str)这一块,就相当于把eval(str)变为了 var b =2 因此a获取了1,而b进行了赋值b = 2 所以最后的输出结果为(1,2)
2.with()语句
上代码:
javascript
function foo(obj){
with(obj){
a= 2
}
}
var o2 ={b:3}
foo(o2)
console.log(a)//with把a的属性泄露到了全局作用域
输出:
2
为什么在全局作用域中输出函数作用域中的a能够读取到a=2?
究其原因,还是因为with()把a的属性从函数作用域泄露到了全局作用域,所以说,with()也是欺骗词法作用域的一个典型机制
最后,对于欺骗词法作用域
我们可以归纳:
- eval() 将原本不属于此处的代码,搬到此处,让代码执行的就像天生就定义在此处一样
- wiht() 当with修改对象中不存在的属性时,会将该属性直接泄露到全局作用域当中
- (补充)如果没有关键词声明使用的变量,一定会把该变量默认为该变量为全局
总结
# js:js是弱类型,动态语言,要先编译 代码的编译发生在代码执行的前一刻,编译理清楚规则,让v8引擎读懂,函数域调用时,才会被编译,函数调用语句直接执行,作用域里面找不到,再往外层作用域进行查找,编译要干的是找到当前域中的有效标识符。
# 全局作用域: 变量的查找是可以从内作用域往外作用域查找的(一层一层)
# 函数作用域: 变量的查找不能由外部作用域到内部作用域
# 块级作用域(es6新出的作用域,16年更新的东西,相比es5是一个大版本) : var 声明的对象存在声明提升(提升到当前域的最顶部),能重复声明,let和{}括号结合会形成块级作用。
# 欺骗词法作用域:词法作用域,是词法所在的域;
eval() 将原本不属于此处的代码,搬到此处,让代码执行的就像天生就定义在此处一样;
wiht() 当with修改对象中不存在的属性时,会将该属性直接泄露到全局作用域当中
好了,今日的学习就到这里了!欢迎大家进行指正,也希望大家给我点个小小的赞,一起学习,一起进步!!