JS 小白报到!今天聊聊我初识作用域和预编译的踩坑笔记。
js 引擎
通俗来说 js 引擎分为两种
- 浏览器
- node
在此就不得不引出一个新名词 V8 —— V8 是 Google 为 Chrome 开发的开源 JavaScript 和 WebAssembly 引擎,用 C++ 写成。用一句话概括:它是最快、最广泛使用的 JS 引擎,让 Chrome 和 Node.js 飞起来的“发动机”。后面我会将它们统称为 V8 引擎。
在讲解 js 的执行前,我先引入今天的主角之一:作用域
1. 全局作用域:顾名思义就是概括整个代码的一整局区域叫做全局作用域
2. 函数作用域 (参数也是该作用域的一个有效标识):同上理解就是概括该函数所选区域叫做函数作用域
如下图显示
3. 块级作用域 (let,const 和 {} 语法配合使用会导致“声明的变量”处在一个作用域中)
如图
注意:let 和 const 的暂时性死区:当一个{} 语句中存在 let x 时,在该{} 中访问x,永远都只能访问{}内部的 x ,就算内部访问不到,也不能访问外部的x。这种规则称为暂时性死区
如图我们可以看得到打印出来的是 2 ,并非 1 ,这就是暂时性死区,好我知道你现在还不能直观的感受到它,那么我为你准备了第二个图
在这里我们能看到他没办法打印出来结果,直接显示报错,为啥他不输出上面的 1 呢?这正好印证了什么是暂时性死区
最后再注意一点:外层作用域不能访问内层作用域,作用域只能由内往外查找
这是正常执行逻辑:由内到外
如果由外到内则会直接报错(如图)
js的执行
- 代码被 v8 读取到的第一时间,并不是执行,而是会先编译(梳理)
- 梳理:
- 分词/词法分析
- 解析/语法分析 -- AST(抽象语法树) 获取有效标识符
- 生成代码
为了更好地了解 js 的执行,咱们不得不引出今天的第二个主角—— js 的预编译
编译
- v8引擎读取到 js 代码,先编译,再次执行
- js 代码被编译时,声明部分会提升到当前作用域顶部
- 我们先来谈有关函数体的编译
- 创建函数的执行上下文 AO 对象
- 找形参和变量声明,将形参和变量声明作为 AO 的属性名,值为 undefined
- 将实参和形参统一
- 在函数体内找函数声明,函数名作为 AO 的属性名,值为函数体
这四步看似很复杂,实则一点也不简单。哈哈开个玩笑,听我跟你一一道来,听完定能一目了然
1.先随便写一串代码
function fn(a) {
console.log(a);
var a = 123
console.log(a);
function a() {}
var b = function() {}
console.log(b);
function c() {}
var c = a
console.log(c);
}
fn(1)
2.创建函数的执行上下文 AO 对象,找形参和变量声明,将形参和变量声明作为 AO 的属性名,值为 undefined
AO {
a: undefined
b: undefined
c: undefined
}
3.将实参和形参统一,在函数体内找函数声明,函数名作为 AO 的属性名,值为函数体
function fn(a) {
console.log(a); // function a() {}
var a = 123
console.log(a); // 123
function a() {}
var b = function() {}
console.log(b); // function() {}
function c() {}
var c = a
console.log(c); //123
}
// AO {
// a: undefined 1 function a() {} 123,
// b: undefined function() {} ,
// c: undefined function c() {} 123 ,
// }
fn(1)
最后输出
- 接下来要讲的就是有关全局的编译
- 创建全局执行上下文 GO 对象
- 找变量声明,将变量名作为 GO 的属性,值为undefined
- 找函数体声明,函数名作为 GO 的属性,值为函数体
从步骤数来看它比函数体的编译还少一步,但事实当真如此吗?还请听我我缓缓道来
1. 依旧先随便写一串代码
var a = 1
function a() {}
console.log(a);
var b = a
console.log(b);
a = 2
var c = b
console.log(c);
2. 创建全局执行上下文 GO 对象,找变量声明,将变量名作为 GO 的属性,值为undefined
GO: {
a: undefined,
b: undefined,
c: undefined,
}
3. 找函数体声明,函数名作为 GO 的属性,值为函数体(由于变量名和函数名都是 a ,所以无需重复,放在一起即可)
GO: {
a: undefined function a() {},
b: undefined 1
c: undefined 1
}
最后的总代码以及输出结果
// GO: {
// a: undefined, function a() {}, 1, 2
// b: undefined,1
// c: undefined,1
// }
var a = 1
function a() {}
console.log(a); // 1
var b = a
console.log(b); // 1
a = 2
var c = b
console.log(c); // 1
最后我再将两者结合在一起放一道简单的代码题,观众老爷们可以自己动手做做看(评论区放输出结果)
g = 100
function fn() {
console.log(g);
g = 200
console.log(g);
var g = 300
}
fn()
var g
纯属个人学习笔记,才疏学浅,欢迎轻拍。 承蒙各位大佬垂阅,若肯赏个👍,感激不尽🙇🏻