前言
假设你是皇帝,你有一个太监,由于你每天起来将会有很多事需要等着你去做,所以你不会在一件事情上花费过多,就比如批奏折,你不会一份一份地去看,你会先让军机处帮你去进行初步的整理等工作,而你只需审阅呈上来的奏折。在这里面,军机处做的事情就叫做编译。
编译
通俗来说就是整理代码中的规则,编译做的事情有三。
- 一.词法分析
这是编译过程的第一步,也称为扫描或分词。在这个阶段,源代码被分解成一系列称为词法单元的小片段,每个词法单元对应着源代码中的一个关键字、标识符、运算符、标点符号或者字面量等。如
var a = 123 // 被分解为var, a, =, 123
- 二是解析
表示了源代码的结构,包括程序的各个部分如何组合在一起,以及它们之间的关系。
- 三是生成代码
生成目标代码。
话不多说,我们直接来认识作用域。
作用域
什么是作用域?
作用域(Scope)在编程语言中是一个基本而重要的概念,它定义了变量、函数以及对象等标识符在何处可以被访问。简单来说,作用域规定了代码中哪些部分可以“看到”或者访问到哪些变量。通俗讲,即皇上能看到什么,军机处能看到什么。
作用域主要有两种类型:①全局作用域,②函数作用域;但在JavaScript中还引入了两种额外的作用域概念:③块级作用域,④词法作用域(也叫静态作用域)。
全局作用域
- 全局作用域:在整个程序范围内都可访问的变量和函数定义于此。在浏览器中,全局变量属于
window对象。
函数作用域
- 函数作用域:在函数内部声明的变量只能在该函数内部访问。
词法作用域
- 词法作用域(静态作用域) :变量的作用域在编写代码时就已经确定,由其在源代码中的位置决定,而不是在运行时决定。
我们举个例子来更好认识这词法作用域。
例子:
var a = 1
function foo() {
var a = 2
}
foo()
console.log(a);//输出1
代码从上往下运行,代码先执行a=1,再执行foo(),此时将会执行foo()函数内的内容,再输出a。但由于外部作用域a为1,外部作用域不能访问内部作用域,所以输出的结果a为1。
再比如这个例子
function foo(a) {
var b = a * 2
function bar(c) {
console.log(a, b, c)// 输出2,4,12
}
bar(b * 3)
}
foo(2)
这两个例子告诉我们词法作用域的规则:内部作用域可以访问外部作用域,反之则不行。
块级作用域
- 块级作用域:这是通过
let和const关键字引入的。块级作用域限定了变量的生命周期于最近的一对花括号{}内,比如在if语句、for循环或自定义的块中。
{
let a = 1
}
console.log(a);
这段代码将会输出
-- ReferenceError: a is not defined
因为{}限定了变量的生命周期于最近的一对花括号 {} 内。
但如果是以下这种情况,代码也不能正常运行。
let a = 1
{
console.log(a); // 暂时性死区
let a = 2
}
这种情况会输出--ReferenceError: Cannot access 'a' before initialization。这种情况是因为发生了暂时性死区。
暂时性死区
什么是暂时性死区?
暂时性死区(Temporal Dead Zone, TDZ)是JavaScript中的一个概念,特指在使用let和const关键字声明变量时,变量在声明之前无法被访问或使用的那段代码区域。
console.log(a);
const a = 1
这段代码也同样输出--ReferenceError: Cannot access 'a' before initialization
但是如果是用var声明,这将是不一样的情况。
{
var b = 2
}
console.log(b);
这段代码将正常输出2
补充知识
欺骗词法作用域
eval()可以执行一个字符串作为JavaScript代码。由于eval()执行的代码能够访问自身作用域,它可以访问或修改封闭作用域中的变量,从而“欺骗”词法作用域。通俗讲就是让原本不属于这里的代码,变得好像天生就定义在了这里一样。
例如:
function foo(a, str) {
eval(str); // var b = 2
console.log(a,b);
}
= foo(1, 'var b = 2')
这段代码中的eval(str);相当于var b = 2
2. with() {} 这个语句可以改变作用域链,允许直接访问某个对象的属性,仿佛它们是当前作用域的一部分。当修改对象中不存在的属性时,这个属性会被泄露到全局,变成全局变量。
例如:
var obj = {
a: 1
};
with (obj) {
console.log(a); // 输出1,因为a被当作obj的属性访问
b = 2; // 如果b不存在于obj中,这行代码可能意外地创建了一个全局变量b
}
var与let的不同
- var 声明的变量存在声明提升 例如:
{
var b = 2
}
console.log(b);// 输出2
{
let a = 1
}
console.log(a);//输出-- ReferenceError: a is not defined
- 使用
let声明的变量遵循块级作用域规则,出了定义它的块就不可访问。 - 使用
var声明的变量尽管在块内定义,但由于变量提升,因此可以在块外访问。
- var 在全局声明的变量会添加到window对象上
在浏览器环境中,任何函数外部声明一个var变量,它将成为全局变量(在浏览器中,全局变量是window对象的属性);let只在声明它的块中有效。
3. 重复声明
var: 允许在同一作用域内多次声明同一个变量,这可能会导致意外覆盖变量值,增加代码的不确定性。let: 禁止在同一作用域内重复声明同一个变量,如果尝试再次声明,会抛出语法错误(SyntaxError),这有助于防止意外覆盖和提高代码的健壮性。
var a = 123
console.log(a); // 输出123
var a = 321
代码正常运行
let b = 123
console.log(b);
let b = 321
抛出异常--SyntaxError: Identifier 'b' has already been declared 'b'已经被声明。
以上内容,如有错误,请大佬纠正。