在讲作用域之前,我们先来聊聊js的编译和执行
编译
- 找到某个域当中的有效标识符。注意函数在没有调用的时候是不执行的,即并不是和全局一起编译,而是先不编译,等要执行的时候再编译(编译永远只发生在执行的前一步).虽说函数在全局编译的时候是不编译的,但是他有声明在.
执行
- 变量的查找会先从内到外的作用域中查找(即先查找自己的作用域,找不到再去外层查找),不能从外到内
作用域
- js是弱类型动态语言
弱类型动态语言即创建变量时不需要声明数据类型,所以一个变量可以被赋值为其他任意一种类型 - 执行代码之前是需要先编译的
全局作用域
-
全局作用域即定义在当前文件的内容所在的一个场所,通俗来讲,就是你打开一个文件,写了这样几行代码,其中 a 和 fd()函数所在的场所就是全局,a 也叫全局变量
var a = 1 function fd(){ console.log(a); } fd()
函数体作用域
-
因为函数在没有调用的时候是不执行的,即并不是和全局一起编译,所以等我们第一次编译完开始执行的时候,执行到函数时,编译器会先停在这,对这个函数进行再次编译,等这个函数编译完,再继续执行。对这个函数编译的时候,该函数会产生一个自己的作用域,这就叫做函数体作用域
举个例子
var a = 1 function fd(){ console.log(a); } fd()
如上述代码,画个图更好理解全局作用域和函数体作用域
我们来判断一下上述代码能否执行,如果能执行那么输出结果是什么
答案是上述代码能执行,并且输出结果为1。我们来梳理一下思路,当第一遍代码编译的时候,全局作用域声明了一个变量 a 和函数 fd(),函数fd()里面的内容并没有进行编译,之后代码开始执行,执行到第一行代码,给变量 a 赋值为1,再往后执行到读取函数fd()这个代码,此时对这个函数进行第二次编译,这时这个函数产生了函数体作用域,因为这个函数里面没有标识符,所以第二次编译结束,继续进行执行,此时执行到console.log(a)这行代码,按理说我们要在这行代码本身的作用域里找 a 这个标识符,但是因为函数体作用域没有这个标识符,所以我们往外找,这时全局变量里面有这个标识符,并且有值且值为1,所以最终输出结果为1
再来个例子
var a = 1
function fd(){
var a = 3
function bar(){
console.log(a);
}
}
fd()
bar()
判断一下上述代码能否执行,如果能执行那么输出结果是什么
答案是上述代码不能执行,会报错,报的错为 bar 未被定义。因为 bar()函数是在 fd() 的函数体作用域内的,而bar()这行代码是在全局作用域中执行的,又因为执行不能从外到内,所以上述代码执行不了
声明提升
在说块级作用域之前我们先来聊聊声明提升以及 let , const 和 var 的区别
-
var 声明的变量存在声明提升,提升到当前作用域的顶端
如:
//我们写的: console.log(a); var a = 1 //事实上编译的: var a console.log(a); a = 1 //输出:undefined
-
函数声明会整体提升
如:
//我们写的: function fd(){ console.log('123') } fd() //事实上编译的: fd() function fd(){ console.log('123') }
let
-
不会声明提升
如:
console.log(a) let a = 1 //输出:报错
-
不能重复声明同一变量
如:
let a = 1 let a = 2 //输出:报错
const
-
不会声明提升
-
不能重复声明同一变量
-
不能改值,所有一般用来声明常量
如:
const a = 1 a = 'hello' //输出:报错
块级作用域
-
{}和let会将整个{}变成块级作用域,{}和const也会形成作用域
如:
if(1){ var a = 1 } console.log(a); //输出:1 if(1){ let a = 1 } console.log(a); //输出:报错 let a = 1 if(true){ console.log(a); //暂时性死区 let a = 2 } //输出:报错
欺骗词法作用域
-
eval()让原本不属于这里的代码变成就是写在这里的
如:
function fd(str){ eval(str) //相当于var b = 2 var a = 1 console.log(a,b); } fd('var b = 2') //输出:1,2
-
with()可以批量化修改对象的属性 如:
var obj = { a: 1, b: 2, c: 3, } //我们要把obj里面所有元素都加1 //直接的写法 obj.a = 2 obj.b = 3 obj.c = 4 //简洁的写法 with(obj) { a = 2, b = 3, c = 4 }
但当修改对象中有不存在的属性时,该属性会泄露到全局,变成全局变量
如:
function fd(obj) { with(obj){ a = 1 }\ } var o1 = { b: 3 } fd(o1) console.log(o1);
输出为{b: 3},但当我们去打印 a 的时候,你会发现竟然能打印出来值,并且值为1,因为这个 a 已经变成了全局变量
最后聊一个热知识,当你声明变量不加关键字时,不管你写在哪都是将他当做全局变量