前言
我们在学习JavaScript时往往会漏掉许多基础的细节,就去学习更加深层的东西,在系统报错时就初略地改下代码而并不知道为什么这里会报错。我将在这篇文章告诉大家var let const这些关键词的区别,还有作用域以及预编译方面的细节,平时常出现的问题。对于每个方面的阐述我都会给出相应的例子,以供大家更好地理解。
我们先从作用域入手,再讲关键词,将较难的预编译放到最后。
作用域
作用域分为全局作用域、函数作用域、块级作用域和词法作用域,先分别介绍一下:
- 全局作用域:最大的作用域级别,定义在全局作用域中的变量和函数可以在代码的任何地方被访问。
- 函数作用域:也称为局部作用域,顾名思义,就是在一个函数内部定义的变量的作用域,当函数被调用时,一个新的函数作用域被创建,当函数执行完毕时,这个作用域也随之销毁。
- 块级作用域:使用let和const声明的变量具有块级作用域,它们只能在它们声明的代码块中有效,代码块是由花括号括起来的那块代码,如if语句、for循环中都算一个代码块。
- 词法作用域:变量声明的地方,也就是变量声明时所处的作用域。
var a = 1
function foo(){ //
var a = 2 //函数作用域
} //
foo()
console.log(a); //输出 1
其中函数体内的便称为函数作用域,而整一块就是全局作用域。
怎么理解作用域呢 ?作用域就是对象所能访问到的区域,内部作用域可以访问外部作用域,反之则不行。所以全局作用域中的a访问不到函数作用域,便输出1。内部作用域和外部作用域就像儿子与父亲一样,儿子向爸爸要东西很正常,但爸爸向儿子要东西就不应该了。
var vs let vs const
为什么先讲作用域呢,就是为了让我们更好地了解这些关键词的区别,毕竟它们的主要区别就在这里。
- var:具有函数作用域,并且在同一个函数内可以多次声明同名变量不会报错。声明提升现象,即在声明之前就可以访问到变量,但其初始值为undefined。
console.log(a); //输出undefined
var a = 1;
//上面的代码可看作为
var a;
console.log(a);
a = 1; //这就是所谓的声明提升
- let:具有块级作用域、不具有声明提升、不允许重复声明。
- const:具有块级作用域、不具有声明提升、不允许重复声明。
预编译
我们先看下面的代码:
function fn(a) {
console.log(a); // 1
var a = 123
console.log(a); // 123
function a(){} //
console.log(a); // function a(){}
var b = function () {}
console.log(b); // function b(){}
function d() {} //
var d = a //
console.log(d); // function a(){}
}
fn(1)
如果你觉得上面注释的是正确的输出结果,那么现实很残酷,其实上面的答案只有一个是正确的,正确答案将在最后揭晓。
先开始我们的预编译学习,预编译分为 全局的预编译 和 函数中的预编译,先以专业的口吻来给大家介绍它们的步骤:
- 全局的预编译
- 创建 全局执行上下文对象 (称为GO)
- 找变量声明,变量名作为GO的属性名,值为undefined
- 在全局找函数声明,函数名作为GO的属性名,值为函数体
- 函数中的预编译
- 创建 函数的执行上下文对象 (称为AO)
- 找形参和变量声明,将形参和变量名作为AO的属性,值为undefined
- 将实参和形参统一
- 在函数体内找函数声明,将函数名作为AO的属性名,值赋予函数体
两者的过程非常相似,我们还是以刚刚列举的代码为例子:
function fn(a) {
console.log(a);
var a = 123
console.log(a);
function a(){} //函数声明
console.log(a);
var b = function () {} //函数表达式
console.log(b);
function d() {} //函数声明
var d = a
console.log(d);
}
fn(1)
先对全局预编译,遵循上面的步骤:
- 创建GO --> GO{}
- 没有变量声明
- 添加函数声明 -->GO{ fn:function(){} }
完成了全局编译后便开始执行,function fn(a) {...}跳过,执行fn(1),这时就会进行对fn函数的预编译:
- 创建AO --> AO{}
- 将形参和变量名作为AO的属性,值为undefined --> AO{a:undefined ,b:undefined ,d:undefined }
- 将实参和形参统一 --> AO{a:undefined->1 ,b:undefined , d:undefined }
- 找函数声明,将函数名作为AO的属性名 --> AO{a:undefined->1->function a(){} ,b:undefined ,d:function d(){} }
其中后面的值将覆盖前面的值
预编译完成,就可以执行函数中代码了。
function fn(a) {
console.log(a); //输出AO中的a的值function a() {}
var a = 123 //赋值 a = 123
console.log(a); //输出 123
function a(){} //函数声明
console.log(a); //输出123
var b = function () {} //函数表达式
console.log(b); //输出 function b() {}
function d() {} //函数声明
var d = a //赋值 d = 123
console.log(d); //输出 123
}
fn(1)
上面的注释就是输出的正确答案了,如果还是有疑问可以再结合预编译的步骤走一遍。
结语
这些就是我们在JavaScript上容易忽视或者忘记的细节,同时也是面试官在js方面可能会问到的问题,在之后我也会分享更多有关JavaScript方面的知识点,希望可以帮助到大家,感谢观看。