- 编译器: Compiler
- 解释器: Interpreter
- 抽象语法树: AST
- 字节码: Bytecode
- 即时编译器: JIT
在执行程序之前,会将代码编译成为二进制文件;每次运行时只需要运行二进制文件即可,无需重新编译,C/C++, GO等都是编译型语言。生成过程会占用大量内存,因为有中间代码的产生。
先将源代码翻译成字节码,然后逐行解释执行;每次运行时都需要通过解释器对程序动态解释及执行; 生成过程不会占用大量内存,因为字节码比中间代码小很多。
- 生成AST:
tokenize
,parse
- 生成执行上下文
- 解释器
Ignition
将AST生成字节码 - 执行代码(
JIT
)- 如果是第一次执行该代码,则Ignition逐行解释执行
- 如果遇到HotSpot代码,则TurboFan进入编译,保存为高效的二进制文件
- 用来存储可执行代码
先进后出
以维护执行上下文
顺序- 对上下文切换
效率
有要求,只适合存储小型数据:primitive
,reference
- 垃圾回收由
ESP指针
下移来实现
- 适合存储大型数据,内存分配和回收会消耗一定时间
- 存储对象(
闭包
), 内存泄露发生在这里 - 垃圾回收由
GC
来完成, 会被分割成为新生代
(副垃圾
回收器 Scanvenger算法)和老生代
(主垃圾
回收器,增量标记算法)
全局
运行- 调用
函数
(编译阶段只会将函数存储到堆中) - 运行
eval
()
- 代码编译:
函数
调用或者全局
才会进入编译;- 创建变量环境:
var
(提升定义和初始化-undefined) - 创建词法环境: 块级作用域由词法作用域中的小型栈来维护
let
(只提升定义,状态为uninitialzied),const
(只提升定义,状态为uninitialized)func 声明
(提升且直接赋值,本身存储在heap中; 会覆盖掉同名变量, 但是可以重新赋值)func 表达式
(提升定义和初始化-undefined)func 参数
(初始化-undefined)
- 确定词法作用链: 看源代码就能确定: (具体实现是有
outer
引用实现的)- 全局作用域
- 函数作用域
- 块级作用域
- 对象并不构成作用域
- 确定
this
指向:new
>apply/call/bind
>object context
>gloabl/undefind
- 创建变量环境:
- 代码执行
- 函数参数赋值
- 标识符赋值(
const
未赋值会报错,let
未赋值会默认为undefined
) - 函数调用(会创建新的
执行上下文
)
- 垃圾回收
- 调用栈中的垃圾回收是靠ESP指针下移来实现的(不是GC)
- 在JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其
外部作用域
中声明的变量,当通过调用一个外部函数
返回一个内部函数
后,即使该外部函数的执行上下文已经销毁,但是内部函数引用的其外部的变量依然以对象
的形式保存在内存堆中,我们就把这些变量的集合
称为闭包。比如外部函数是foo
,那么这些变量的集合就称为foo
函数的闭包。 - 闭包的本质是返回
函数
及其所引用的外部变量
,外部变量的查询依然遵照作用域链
的原则
- 编译期间: 预先扫描函数、
- 遇到函数时,要先
预扫描
一遍 - 如果引用外部变量,则会形成闭包的引用,这是闭包并未在内存中建立
- 只有
- 1号栗子
function a() {
const x = 123;
function b(){
console.log(x);
}
return b;
}
a()(); // x 是b()的闭包;
- 2号栗子
var bar = {
myName:"time.geekbang.com",
// 不会close over上面的myName,因为上层是对象
printName: function () {
console.log(myName)
}
}
function foo() {
let myName = "极客时间"
return bar.printName
}
let myName = "极客邦"
let _printName = foo()
_printName()
bar.printName()
- 数据封装(data as private)
- 工厂模式(Factory Pattern)
- 模块基础(Module Pattern)
- 伴随着
引用它的函数
的销毁而销毁 - 如果引用闭包的函数是一个
全局变量
,则闭包会一直存在直到页面关闭
,因此可能会造成内存泄露
function play1() {
const x = 1;
console.log(x);
}
function get(){
return play1;
}
get()();
- 如果引用闭包的函数是个
局部变量
,等函数销毁后,在下次 JavaScript引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么JavaScript引擎的垃圾回收器就会回收这块内存。
const x = 1;
function play1() {
console.log(x);
}
function get(){
function play2() {
console.log(x);
}
return play2;
}
get()();
- 如果该
闭包
会一直使用,那么它可以作为全局变量
而存在; - 如果使用频率不高,且占用内存较大的话,那就尽量让它成为一个
局部变量
; - 可以沟通设置闭包函数为null来释放闭包
new
call
/bind
/apply
死绑定object context
global or Window
(non-strict)/undefined
(strict)
- 保存
this
为self
变量然后传入(从外层到内层) - 使用
Arrow Function
本质上都是将
this
机制转化为scope chain
机制
- 会创建新的执行上下文
- 会创建新的作用域
- 从外部函数返回后,会产生闭包
this
的查找依赖于Scope Chain
var myObj = {
name : "极客时间",
showThis: function(){
console.log(this)
var bar = () => {
this.name = "极客邦"
console.log(this)
}
bar()
}
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)
- 函数调用时才会对其进行编译
- 函数编译时,变量和内部函数声明会被赋值,var定义及初始化为被提升,let,const的定义会被提升,不会被初始化。
- 变量和函数重名时,函数提升优先级更高; 但是可以被重新赋值
alert(a);//输出:function a(){ alert('我是函数') }
function a(){ alert('我是函数') }//
var a = '我是变量';
alert(a); //输出:'我是变量'
- let在未赋值后使用,将自动化初始化为undefined
let b;
console.log(b);