Javascript - 3 背后的运行原理
-
High level
-
Garbage-collected
js引擎内部的算法,为了不被不必要的东西堵塞,会从计算机内存中 自动删除旧的、未使用的对象
- 解释型的 / 即时编译的语言(为了更快做出的调整) interpreted or just-in-time compiled
通过 解释器(Interpreter) 逐行翻译并执行代码的编程语言,它不需要预先将代码全部编译成机器码,而是直接运行源代码,边翻译边执行
- 多范式
程序化的,面向对象的(OOP),函数式编程(FP)
命令式,声明式
- 基于原型,面向对象的
除了原始值,js中的东西都是继承自某个原型对象的对象
Object 对象继承自
Object.prototype
;Array 对象继承自Array.prototype
;Function 对象继承自Function.prototype
- first-class functions (函数式编程)
函数被当作变量,可以把函数传给函数,也可以让函数返回函数
-
Dynamic
-
单线程
Do one thing at a time 只有一个主线程执行代码,同一时间只能执行一个任务
- 非阻塞 事件循环 并发模型 Non-blocking event loop concurrency model
避免主线程阻塞
在后台执行需要长时间运行的任务,一旦完成,放回主线程
- 跨平台
只要目标平台安装了对应的解释器,同一份代码即可运行
JS 引擎
执行js代码的程序
每个浏览器都有自己的js引擎
包含
- 调用栈 Call Stack
代码实际 执行 的地方,执行上下文 execution context 堆叠在一起,为了跟踪程序在执行中所处的位置(栈顶就是当前代码执行的地方,当它执行完成就会从栈顶删除,执行会回到之前的执行上下文)
- 内存堆 Memory Heap
非结构化内存池,存储对象
作用
- Parsing 解析:code -> AST
将每行代码分成对语言有意义的部分(const, function),然后把这些部分以结构化的方式存入树中

-
Compilation 编译:AST -> 机器码
-
Execution 执行
-
Optimization 优化
为了尽快开始执行,在开始,创建一个未优化的机器码版本;在后台优化这段机器码,并在运行的程序执行期间重新编译
优化可以做很多次,在每一次优化后,未优化的代码被替换

解析,编译,优化发生在引擎内部的 无法从代码访问的 特殊线程中
JS Runtime
浏览器

Node.js

JS 执行
创建 全局执行上下文 for the top-level code(顶级代码:不在任何函数中的代码)
在最开始,只有在函数外面的代码才会被执行

执行上下文
js总是在执行上下文中执行
-
在执行其中一段js时的环境
-
存储所有必要信息的盒子
在任何js项目中,只有一个全局执行上下文
每个函数,在被调用时,有自己的执行上下文

执行上下文的构成
- 可变环境 variable environment
所有的变量(let, const, var declarations) 函数声明(functions) 和 传递的参数(arguements object) 都被存储
- 作用域链 scope chain
对于 函数外变量 的引用
- this 关键字

相反,箭头函数可以使用 来自 最近的常规父级函数的 arguements object 和 this 关键字

call stack追踪执行顺序
- 编译完成,顶级代码开始执行,创建全局执行上下文
- 全局执行上下文放入stack
- 函数创建自己的执行上下文,放入stack
- 执行完毕,弹出执行上下文

执行完毕,程序会保持在这个状态,直到真正结束(关闭浏览器选项卡或关闭浏览器窗口)

Scope Chain
Scoping: 程序的 变量 如何被 (js引擎) 组织和访问
lexical scoping 词法作用域: scoping 完全 由函数和代码块的位置控制
写在一个函数中的函数,可以访问其父函数的变量
Scope: space or environment in which a certain variable is declared
全局作用域global scope,函数作用域,块作用域
var 是函数作用域的
Scope of variables 变量的作用域: 该变量可以被访问的代码区域
变量查找
(js引擎 create Scope Chain behind the scenes)如果一个scope需要使用某个变量,但在当前scope找不到,它将在scope chain中查找,看是否能在parent scope中找到变量

变量,函数,arguements object 都是一样的
scope chain 只能向上工作,不能横向工作
Scope Chain VS Call Stack
Hoisting 提升
提升: 使变量在实际声明它们之前 可以被访问

var 会创建一个属性在全局窗口对象上
WHY TDZ
- 更容易避免和捕获错误
- 使 const 变量真正地工作
WHY HOISTING
- 使用函数在实际声明它之前
this 关键字
const jonas = {firstName: 'Jonas',year: 1991,calcAge: function(){console.log(2024 - this.year);// Solution 1// const self = this;// const isMillenial = function(){// console.log(self);// }// Solution 2const isMillenial = () =>{console.log(this);}}
}
arguments 关键字
箭头函数没有 arguments
关键字
const addExpr = function(a,b){console.log(arguments);return a + b;
}
addExpr(2, 5);
addExpr(2, 5, 8, 12); // 也是合法的,只是后面传进去的两个值没有名字
原始值 VS 对象
原始值 - 原始类型 - 存储在 Call Stack(在它们声明的执行上下文中)
对象 - 引用类型 - 存储在 Heap
原始值的存储
-
创建 具有变量名称 的 唯一标识符
-
分配一块 有一个特定地址 的 内存,值被存储在 这块 内存中
-
唯一标识符指向地址
-
内存中的值是不可变的,变量值改变,会分配一块新的内存,存入新的值,唯一标识符指向这个新的地址

对象的存储
- 对象的唯一标识符 不直接指向 存储对象的地址,而是指向一个存储对象地址的内存地址
对象可能太大无法存储在stack中,于是存储在heap中(一个几乎无限的内存池),stack只存储一个引用以便找到它
- 当在复制一个对象的时候,只是在创建一个指向完全相同对象的新变量

复制对象
// Copy objects
const jessica = {firstName: 'Jessica',lastName: 'Williams',age: 27,
};const jessicaCopy = Object.assign({}, jessica2); // 创建一个全新的对象
jessicaCopy.lastName = 'Davis';
console.log(jessica.lastName); // Williams
console.log(jessicaCopy.lastName); // Davis
浅拷贝:只会拷贝第一级的属性(如果有在对象中的对象,那 Object.assign()
就没用了)
深度克隆:复制所有内容(使用外部库)