运行原理 & 作用域
浏览器内核
- Webkit
- WebCore 负责 HTML 解析、布局、渲染相关工作
- JSCore 解析、执行 JS 代码
- Blink 前者的一个分支
...
V8 引擎
JSCore 是由苹果公司开发的,同时也是 WebKit 渲染引擎的一部分,负责处理 JavaScript 代码的解释和执行。
V8 是由 Google 开发的高性能 JavaScript 引擎,用于执行 JavaScript 代码。
JS 代码执行原理
思考以下代码,它在 JS 中时如何被执行的
js
var name = "ice";
function bar() {
var age = 25;
}
var age = 24;
bar();
名词概念
- 全局对象 (Global Object)GO
- 执行上下文栈 (Execution Context Stack)(ESC)
- 全局执行上下文 (Global Execution Context)(GEC)
- 函数执行上下文 (Function Execution Context)(FEC)
- 变量对象 (Variable Object)VO
- 激活对象 (Activation Object)AO
- 每一个执行上下文,都会关联一个 VO 对象,变量/函数声明都会添加到这个 VO 对象中
Init Global Object
JS 引擎会初始化全局对象,在执行代码之前,会在堆 (heap) 内存中,创建一个全局对象(Global Object)
- Date
- Array
- String
- Number
- setTimeout ...
ECS
js 引擎内部有一个执行上下文栈,是代码的调用栈
那么现在开始,执行谁的代码呢? => 全局代码
- 执行全局代码,会构建出一个全局执行上下文栈(GEC)
- parser -> AST 树过程中,将全局定义的变量、函数加入到 Global Object 中,但是不会赋值,也称 hoisting (作用域提升)
- 执行代码,对变量赋值或者执行其他的函数
- 此时的 VO == GO
解析图:
解析阶段
代码开始执行
FEC
前面分析到了 ESC,当执行到函数的时候会创建一个函数执行上下文 (FEC),并且压入到 stack 中
- 当进入到函数执行上下文,会创建一个 AO 对象
- 使用 arguments 作为初始化,初始值是传入的参数
- 此时的 scope 已经产生,作用域跟哪里定义的有关,跟哪里执行无关
- 此时的 VO == AO
解析图:
解析阶段
代码开始执行
作用域
全局作用域
js
var name = "ice";
console.log(name); // ice
通过 var 定义的变量,会赋值在 window 上
函数作用域
js
var name = "ice";
function bar() {
var name = "panpan";
console.log(name); // panpan
}
函数拥有自己的作用域,即 name = 'panpan'
块级作用域
早期 var 是没有没有独立的作用域的,从而导致不可预料之外的结果
js
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), i * 1000); // 5次 5
}
// 利用函数作用域 + 闭包 变相解决
for (var i = 0; i < 5; i++) {
(function (i) {
setTimeout(() => console.log(i), i * 1000); // 0 1 2 3 4
})(i);
}
let/const 有块级作用域,代码会变得简洁明了
js
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), i * 1000); // 0 1 2 3 4
}
作用域链
js
const name = "panpan";
function bar() {
const name = "ice";
function foo() {
const name = "iceweb";
console.log(name); // iceweb
}
foo();
}
bar();
作用域链(Scope Chain)则是 JavaScript 中用于确定变量访问顺序的机制。作用域链是由函数创建时的词法环境(Lexical Environment)决定的,它决定了变量的查找顺序,当在当前作用域找不到变量时,会沿着作用域链向上查找直到全局作用域。