梦回JavaScript之作用域--小白篇

前言.

作为一名刚刚接触JavaScript的萌新小白,我们今天来聊点"干货"----作用域。

总览

  1. 全局作用域
  2. 函数作用域
  3. 欺骗词法作用域

正文.

在聊作用域之前,我们要搞清楚浏览器是怎么怎么执行我们的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()也是欺骗词法作用域的一个典型机制

最后,对于欺骗词法作用域

我们可以归纳:

  1. eval() 将原本不属于此处的代码,搬到此处,让代码执行的就像天生就定义在此处一样
  2. wiht() 当with修改对象中不存在的属性时,会将该属性直接泄露到全局作用域当中
  3. (补充)如果没有关键词声明使用的变量,一定会把该变量默认为该变量为全局

总结

# js:js是弱类型,动态语言,要先编译 代码的编译发生在代码执行的前一刻,编译理清楚规则,让v8引擎读懂,函数域调用时,才会被编译,函数调用语句直接执行,作用域里面找不到,再往外层作用域进行查找,编译要干的是找到当前域中的有效标识符。

# 全局作用域: 变量的查找是可以从内作用域往外作用域查找的(一层一层)

# 函数作用域: 变量的查找不能由外部作用域到内部作用域

# 块级作用域(es6新出的作用域,16年更新的东西,相比es5是一个大版本) : var 声明的对象存在声明提升(提升到当前域的最顶部),能重复声明,let和{}括号结合会形成块级作用。

# 欺骗词法作用域:词法作用域,是词法所在的域;

​ eval() 将原本不属于此处的代码,搬到此处,让代码执行的就像天生就定义在此处一样;

​ wiht() 当with修改对象中不存在的属性时,会将该属性直接泄露到全局作用域当中

好了,今日的学习就到这里了!欢迎大家进行指正,也希望大家给我点个小小的赞,一起学习,一起进步!!

相关推荐
昨天;明天。今天。2 小时前
案例-表白墙简单实现
前端·javascript·css
安冬的码畜日常2 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
小御姐@stella2 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing2 小时前
【React】增量传输与渲染
前端·javascript·面试
GISer_Jing2 小时前
WebGL在低配置电脑的应用
javascript
万叶学编程5 小时前
Day02-JavaScript-Vue
前端·javascript·vue.js
天涯学馆7 小时前
Next.js与NextAuth:身份验证实践
前端·javascript·next.js
HEX9CF7 小时前
【CTF Web】Pikachu xss之href输出 Writeup(GET请求+反射型XSS+javascript:伪协议绕过)
开发语言·前端·javascript·安全·网络安全·ecmascript·xss
ConardLi7 小时前
Chrome:新的滚动捕捉事件助你实现更丝滑的动画效果!
前端·javascript·浏览器