JS的心脏:JS引擎
单刀直入地说:JavaScript 是一门灵活全面的解释型语言,而这个多面手执行代码,依赖于引擎。目前最主流的引擎有两类:
1.浏览器内置引擎:Chrome的V8引擎等等
2.Node.js引擎:基于V8引擎构建的服务器端JS运行环境
我们以V8引擎为例,它就像JS的心脏,可以读取并执行JS代码,是JS能适配多种环境的核心驱动。
JS的执行过程
要了解JS的作用域,首先我们要知道JS怎样执行的。v8引擎执行 JS 代码就像一个高效的流水线。分为四个阶段:
1.v8首先会读取JS代码,不会立刻执行,先编译,把要加工的材料送上流水线。
2. 分词:即词法分析。把代码拆分开为一个个词法单元 ,把流水线上的原材料进行分拣
3.解析:即语法分析。将词法单元组织成一个 语法树 AST(抽象语法树) ,构建一个产品的设计图。
4. 执行:根据 AST 生成代码并执行,合成出一个完整的产品。
话不多说,我们实战一下,看看这个引擎怎样工作
var a=10
console.log(a)
引擎执行过程:
1.读取代码,把语句拆成零件,准备送上流水线了: 发现'var'、'console.log()'等词法单元。
2.构建AST,表示函数声明和调用关系。
3.生成机器码并执行,返回结果'10'。
可以看到,我们先定义了一个变量a,并赋值为10,然后我们将其输出了。假如我有点叛逆,把这两句倒过来写呢?
也就是变成这样:
console.log(a)
var a=10
会输出10吗?不该吧。 答案却让你大跌眼镜:不会报错!
不是吧?有丰富编程经验的你觉得,使用还未声明的变量,就是天王老子来了也该报错!这就来到另一个概念:JS声明提升 。
JS声明提升:JS在编译阶段会将变量声明和函数声明提升到当前作用域的顶部,但赋值留在原地。
讲了这么多,到底什么是作用域?
作用域:变量的可访问范围,有以下几种:
全局作用域:
- 代码最外层声明的变量,整个代码都能访问
var globalVar = '我是全局变量'
function test() {
console.log(globalVar) // ✅ 可以访问
}
test() // 输出:我是全局变量
函数作用域:
- 在函数内部用
var声明的变量,只在函数内部有效 - 函数的参数也是函数作用域的一部分
function test() {
var localVar = '我是局部变量'
console.log(localVar) // ✅ 可以访问
}
test()
console.log(localVar) // ❌ 报错!访问不到
那我们来看这么一段代码:
function outer() {
const outerVar = '外层'
function inner() {
const innerVar = '内层'
console.log(outerVar) // ✅ 能访问外层
console.log(innerVar) // ✅ 能访问自己
}
console.log(innerVar) // ❌ 报错,访问不到内层
}
当函数作用域嵌套时该怎么办才好?请你牢记:
内层函数可以访问外层函数的变量,外层函数无法访问内层函数的变量。
聪明的你发现:使用var来声明好像不太对劲?有点反直觉:var可以重复声明一个变量、可以不声明变量之前就使用这个变量。这对开发者来说太别扭了。
因此在ES6的更新中,引入了新的关键词let与const,也带来了块级作用域。(文末附ES6入门教程链接)
3.块级作用域:指 {} 代码块内部的作用域,在该作用域内声明的变量, 只在当前代码块内有效 ,超出代码块外部无法访问。
{
let a = 10 // 只在这个 {} 内有效
const b = 20 // 只在这个 {} 内有效
}
console.log(a) // ReferenceError: a is not defined
console.log(b) // ReferenceError: b is not defined
值得注意的是,这些语句可以形参块级作用域:
| 代码块 | 示例 |
|---|---|
| if 语句 | if () { let x = 1 } |
| for 循环 | for (let i = 0; i < 3; i++) { } |
| while 循环 | while () { let x = 1 } |
| 普通 {} | { let x = 1 } |
| 函数内部 | function() { let x = 1 } |
let与const
let:
-
let + {} = 块级作用域,哪怕就写个光秃秃的{}也成立
-
let 不会带来声明提升
这修复了使用 var 不会出bug的bug,那么:
{
let a=10
}
console.log(a)
终于会报错了,let回避了声明提升带来的许多弊端。
const:const是常量,不能重新赋值,也不能重新声明,例如:
let a=10
a=20
//不会报错,可以重新赋值
const a=10
const a=12
//则会报错,不可以赋值了
暂时性死区:就是上面说的,let、const变量在声明之前的这段区域,再次区域内访问变量会报错。暂时性是因为这个区域是临时的,一旦执行到变量声明行,死区就结束了
再来个例题吧
var a=1
function foo(b){
var a=3;
console.log(a,b)
}
foo(2)
会输出什么?
(3,2)
解析:
- var a = 1 是全局变量
- var a = 3 是 foo 函数内部的局部变量
在函数内部,局部变量会遮蔽(覆盖)全局变量
当 JavaScript 查找变量时,会先在当前作用域查找,找到了就停止查找。
如果去掉函数内部的 var 呢?
var a = 1
function foo(b) {
a = 3 // 注意:没有 var
console.log(a, b)
}
foo(2) // 输出: 3 2
console.log(a) // 输出: 3 —— 全局变量被修改了!
⚠️ 注意:没有 var 声明的赋值,会修改全局变量!这是 var 声明提升带来的常见坑。
总结
- JS 引擎:V8 引擎负责编译和执行 JS 代码
- 执行过程:编译(分词→解析)→ 执行
- 声明提升:变量/函数声明会提升到作用域顶部
- 三种作用域:全局作用域、函数作用域、块级作用域(ES6)
- 作用域链:查找变量时逐级向上查找
- 声明方式:优先使用
const,其次let,避免var
| 声明方式 | 作用域 | 重复声明 | 声明提升 |
|---|---|---|---|
| var | 函数作用域 | 允许 | 有 |
| let | 块级作用域 | 禁止 | 暂时性死区 |
| const | 块级作用域 | 禁止 | 暂时性死区 |
📚 延伸阅读:ES6 入门教程 - 阮一峰
如果觉得本文对你有帮助,欢迎点赞、收藏、关注!你的支持是我创作的动力 🌹🌹